程式碼和解釋只是本人從別的地方收集之後總結的,並不是本人寫的,侵刪
給client端輸入數據,client端將數據發送到server端,server再將數據返回
/*********************** server *************************/
#include <WinSock2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include<iostream>
#include<cassert>
#include<vector>
#pragma comment (lib,"ws2_32.lib")
#include<ws2tcpip.h>
#define LISTEN_PORT 9999//埠號
#define LIATEN_BACKLOG 32
using namespace std;
/*********************************************************************************
* 函數宣告
**********************************************************************************/
//accept回撥函數(與用戶端連線成功之後執行的函數)
void do_accept_cb(evutil_socket_t listener, short event, void* arg);
//read 回撥函數(接收到數據返回的函數)
void read_cb(struct bufferevent* bev, void* arg);
//write 回撥函數(呼叫accept或者bufferevent_write函數之後執行的函數)
void write_cb(struct bufferevent* bev, void* arg);
//error回撥函數(出錯執行的函數)
void error_cb(struct bufferevent* bev, short event, void* arg);
/*********************************************************************************
* 函數體
**********************************************************************************/
//accept回撥函數
void do_accept_cb(evutil_socket_t listener, short event, void* arg) {
//傳入的event_base指針
struct event_base* base = (struct event_base*)arg;
//socket描述符
evutil_socket_t fd;
//宣告地址
struct sockaddr_in sin;
//地址長度宣告
socklen_t slen = sizeof(sin);
//接收用戶端
fd = accept(listener, (struct sockaddr*)&sin, &slen);
if (fd < 0) {
perror("accept error!\n");
return;
}
accept函數:
// 功能:接受客戶機進程呼叫connect函數發出的連線請求。
// 格式:SOCKET accept(SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen)。
// 參數:s - 處於偵聽狀態的通訊端;
// addr - 指向一個用來存放發出連線請求的客戶機進程IP地址資訊的地址結構指針;
// addrlen - addr的長度。
// 返回值:呼叫成功返回一個新的通訊端,這個通訊端對應已接受的那個客戶機進程的連線,失敗時返回INVALID_SOCKET。
// 說明:用於面向連接的伺服器進程,在IP協定族中只適用於TCP伺服器端。
printf("用戶端連線成功: fd = %u\n", fd);
//註冊一個bufferevent_socket_new事件
//建立bufferevent物件
struct bufferevent* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
struct bufferevent* bev = bufferevent_socket_new(
// base, // 事件管理器
// fd, // 關聯的控制代碼\檔案描述符
// BEV_OPT_CLOSE_ON_FREE); // 參數
//設定回撥函數
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
bufferevent_setcb(
// bev, // bufferevent物件
// read_cb, // 讀操作回撥函數
// NULL, // 寫操作回撥函數
// error_cb, // 錯誤處理回撥函數
// arg); // 參數
// read_cb和write_cb的原型是
// void read_or_write_callback(struct bufferevent* bev, void* arg)
// error_cb的原型是
// void error_cb(struct bufferevent* bev, short error, void* arg)
//設定該事件的屬性
bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);
//啓用bufferevent相關快取區
// int bufferevent_enable(struct bufferevent* bufev, short event);
//備註:新建的bufferevent預設寫快取時enable,而讀快取是disable的
}
//read 回撥函數
void read_cb(struct bufferevent* bev, void* arg) {
#define MAX_LINE 256
char line[MAX_LINE + 1];
int n;
//通過傳入參數bev找到socket fd
evutil_socket_t fd = bufferevent_getfd(bev);
//從bev讀取數據給line
//從緩衝區接收數據
while (n = bufferevent_read(bev, line, MAX_LINE)) {
函數功能:從bufferevent的讀快取區讀取數據.
// 函數原型:size_t bufferevent_read(struct bufferevent* bufev, void* data, size_t size);
// 參數說明:
// bufev - 關聯的bufferevent
// data - 數據指針,用來儲存從bufferevent讀快取區讀到的數據
// size - 數據位元組數
// 返回值:讀取的數據位元組數
line[n] = '\0';
printf("fd=%u, 接收到: %s\n", fd, line);
//將獲取的數據返回給用戶端
bufferevent_write(bev, line, n);
//函數功能:寫數據到bufferevent的寫快取區.
// 函數原型:int bufferevent_write(struct bufferevent* bufev, const void* data, size_t size);
// 參數說明:
// bufev - 關聯的bufferevent
// data - 數據指針,從此來源中獲取數據,以寫入到bufferevent寫快取區
// size - 數據位元組數
// 返回值:如果成功爲0, 失敗爲 -1
}
}
//error回撥函數
void error_cb(struct bufferevent* bev, short event, void* arg) {
//通過傳入參數bev找到socket fd
evutil_socket_t fd = bufferevent_getfd(bev);
cout << "fd = " << fd << ",";
if (event & BEV_EVENT_TIMEOUT) {
printf("超時\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("連線關閉\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
//釋放Bufferevent
bufferevent_free(bev);
}
//write 回撥函數
void write_cb(struct bufferevent* bev, void* arg) {
char str[50];
//通過傳入參數bev找到socket fd
evutil_socket_t fd = bufferevent_getfd(bev);
//cin >> str;
printf("輸入數據!\n");
scanf_s("%d", &str);
//給用戶端發送數據
bufferevent_write(bev, &str, sizeof(str));
}
int main() {
//int ret;
//儲存socket成功連線返回的編號
evutil_socket_t listener;
//Libevent定義evutil_socket_t型別爲一個整數,該整數可以表示socket或者accept函數的返回值
//並且可以在Windows上避免指針截斷的風險。
//載入winsock庫
WSADATA Ws;
//Init Windows Socket(初始化socket資源)
if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0) {
return -1;
}
// WSAStartup函數:
// 功能:用於初始化WinSock,即檢查系統中是否有Windows Sockets的實現庫。
// 格式:int WSAStartup(WORD wVersionRequest, LPWSADATA lpWSAData)。
// 參數:wVersionRequest使用WinSock的最低版本號;
// lpWSAData是WSADATA指針。
// 返回值:函數成功呼叫返回0,失敗時返回非0。
// 說明:此函數是應用程式呼叫的第一個WinSock函數,只有在該函數呼叫成功後才能 纔能呼叫其他WinSock函數。
listener = socket(AF_INET, SOCK_STREAM, 0);
建立一個socket用於連線
// 1.address family,如AF_INET
// 2.連線型別,通常是SOCK_STREAM或SOCK_DGRAM
// 3.協定型別,通常是IPPROTO_TCP或IPPROTO_UDP
// 返回值:socket的編號,爲-1表示失敗
socket函數:
// 功能:爲應用程式建立通訊端。
// 格式:SOCKET socket(int af, int type, int protocol)。
// 參數:af - 通訊端使用的協定地址族,如果使用TCP或者UDP,只能使用AF_INET;
// type - 通訊端協定型別,如SOCK_STREAM、SOCK_DGRAM;
// protocol - 通訊端使用的特定協定,如果不希望特別指定協定型別,則設定爲0。
// 返回值:函數成功呼叫後返回一個新的通訊端,是一個無符號的整型數據;失敗時返回INVALID_SOCKET。
// 說明:應用程式在使用通訊端通訊之前,必須擁有一個通訊端。
assert(listener > 0);//參數爲假的時候,終止程式執行
//埠重用
evutil_make_listen_socket_reuseable(listener);
//成功返回0,失敗返回-1
//儲存網路通訊的地址
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind error!\n");//用來將上一個函數發生錯誤的原因輸出到標準裝置
return 1;
}
bind函數:
// 功能:實現通訊端與主機本地IP地址和埠號的系結。
// 格式:int bind(SOCKET s, const struct sockaddr* name, int namelen)。
// 參數:s - 將要系結的通訊端;name - 與指定協定有關的地址結構指針;namelen - name參數的長度。
// 返回值:函數成功時返回0;失敗時返回SOCKET_ERROR。
if (listen(listener, 1000) < 0) {
perror("listen");
return 1;
}
listen函數:
// 功能:設定通訊端爲監聽狀態,準備接收由客戶機進程發出的連線請求。
// 格式:int listen(SOCKET s, int backlog)。
// 參數:s - 已系結地址,但還未建立連線的通訊端識別符號;
// backlog - 指定正在等待連線的最大佇列長度。
// 返回值:函數成功時返回0;失敗時返回SOCKET_ERROR。
// 說明:僅適用於面向連接的通訊端,且用於伺服器進程。
printf("Listening...\n");
//設定socket爲非阻塞模式
evutil_make_socket_nonblocking(listener);
//成功返回0,失敗返回-1
//建立一個event_base(事件管理器)
struct event_base* base = event_base_new();
//在使用libevent之前,需初始化一個event_base結構。每一個event_base結構體包含了events集合併選擇事件型別。
assert(base != NULL);
//建立並系結一個event
struct event* listen_event;
listen_event = event_new(base, listener, EV_READ | EV_PERSIST, do_accept_cb, (void*)base);
參數:event_base, 監聽的fd,事件型別及屬性,系結的回撥函數,給回撥函數的參數
// 注:libevent支援的事件及屬性包括(使用bitfield實現,所以要用 | 來讓它們合體)
// (a) EV_TIMEOUT: 超時
// (b) EV_READ : 只要網路緩衝中還有數據,回撥函數就會被觸發
// (c) EV_WRITE : 只要塞給網路緩衝的數據被寫完,回撥函數就會被觸發
// (d) EV_SIGNAL : POSIX號志,參考manual吧
// (e) EV_PERSIST : 不指定這個屬性的話,回撥函數被觸發後事件會被刪除
// (f) EV_ET : Edge - Trigger邊緣觸發,參考EPOLL_ET
struct event* listen_event = event_new(
// base, // 事件管理器物件
// listener, // 監聽的物件,如socket
// EV_READ | EV_PERSIST, // 事件型別及屬性
// do_accept, // 回撥函數
// (void*)base); // 傳遞給回撥函數的參數
// 回撥函數的宣告原型爲:
// typedef void(*event_callback_fn)(
// evutil_socket_t sockfd, // 關聯的控制代碼\檔案描述符
// short event_type, // 事件型別
// void* arg) // 傳遞給回撥函數的參數
//將event新增到訊息回圈佇列中。
event_add(listen_event, NULL);
參數:event,超時時間(struct timeval *型別的,NULL表示無超時設定)
event_add(
// listen_event, // 事件物件
// NULL); // struct timeval* 型別指針,用於設定超時時間,NULL表示無超時設定
//啓動事件回圈
event_base_dispatch(base);
//無限回圈,直到註冊事件個數爲0,或者event_base_loopbreak() 和 event_base_loopexit()被呼叫
//成功退出返回1。如果回圈中有無錯,則非正常退出且返回 - 1。
printf("End!");
return 0;
}
/*********************** client *************************/
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include<winsock2.h>
#include<ws2tcpip.h>
#include<iostream>
#include<string>
using namespace std;
#pragma comment (lib,"ws2_32.lib")
int main(int argc, char* argv[]) {
//載入winsock庫
WSADATA Ws;
//Init Windows Socket(初始化socket資源)
if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0) {
return 0;
}
// WSAStartup函數:
// 功能:用於初始化WinSock,即檢查系統中是否有Windows Sockets的實現庫。
// 格式:int WSAStartup(WORD wVersionRequest, LPWSADATA lpWSAData)。
// 參數:wVersionRequest使用WinSock的最低版本號,lpWSAData是WSADATA指針。
// 返回值:函數成功呼叫返回0,失敗時返回非0。
// 說明:此函數是應用程式呼叫的第一個WinSock函數,只有在該函數呼叫成功後才能 纔能呼叫其他WinSock函數。
int sockfd;//儲存socket成功連線返回的編號
char buffer[1024];//用戶端接收到的數據
struct sockaddr_in server_addr;//儲存網路通訊的地址
struct hostent* host;//記錄主機各種資訊(包括但不限於:主機名、地址列表、地址長度)
int portnumber;//埠號
int nbytes;//儲存接收到的數據
//如果IP地址轉換失敗
if ((host = gethostbyname("127.0.0.1")) == NULL) {
//gethostbyname:用域名或者主機名獲取地址
fprintf(stderr, "Gethostname error\n");
//stderr:標準錯誤輸出裝置,預設控制檯螢幕
exit(1);
}
//如果字串轉換失敗
if ((portnumber = atoi("9999")) < 0) {
//atoi():將字串改爲int型,參數是const char*
fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
//argc是命令列總的參數個數;
//argv[]是argc個參數,其中第0個參數是程式的全名;以後的參數,命令列後面跟的使用者輸入的參數。
exit(1);
}
/* 客戶程式開始建立 sockfd描述符 */
//如果socket建立失敗
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
建立一個socket用於連線
// 1.address family,如AF_INET
// 2.連線型別,通常是SOCK_STREAM或SOCK_DGRAM
// 3.協定型別,通常是IPPROTO_TCP或IPPROTO_UDP
// 返回值:socket的編號,爲-1表示失敗
socket函數:
// 功能:爲應用程式建立通訊端。
// 格式:SOCKET socket(int af, int type, int protocol)。
// 參數:af - 通訊端使用的協定地址族,
// 如果使用TCP或者UDP,只能使用AF_INET;type - 通訊端協定型別,
// 如SOCK_STREAM、SOCK_DGRAM;protocol - 通訊端使用的特定協定,
// 如果不希望特別指定協定型別,則設定爲0。
// 返回值:函數成功呼叫後返回一個新的通訊端,是一個無符號的整型數據;失敗時返回INVALID_SOCKET。
// 說明:應用程式在使用通訊端通訊之前,必須擁有一個通訊端。
fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
//strerror():將errno翻譯成描述錯誤型別的string語句
//errno:系統會根據上一條語句的執行錯誤情況,將errno賦值
//1、errno是一個系統變數,不需要我們賦值或者宣告的
//2、errno是一個int型別的變數,其中的值對應一種特定錯誤型別
exit(1);
}
/* 客戶程式填充伺服器端的資料 */
memset(&server_addr, 0, sizeof(server_addr));//清空,重置爲 0
server_addr.sin_family = AF_INET;//地址族;AF_INET:使用ipv4的方式進行通訊
server_addr.sin_port = htons(portnumber);//儲存埠號(使用網路位元組順序);
//htons:將主機的無符號短整形數轉換成網路位元組順序
server_addr.sin_addr = *((struct in_addr*)host->h_addr);//儲存IP地址
//in_addr結構體:表示一個32爲的IPv4地址。
//h_addr:儲存地址列表的第一項
/* 客戶程式發起連線請求 */
if (connect(sockfd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr)) == -1) {
1.通過呼叫 socket 函數正確執行後的返回值
// 2.執行 conenct 函數的用戶端發送請求的伺服器端網路地址變數(用戶端需連線的伺服器端的地址)
// 3.第 2 個參數伺服器端網路地址變數的長度
connect函數:
// 功能:提出與伺服器建立連線的請求,如果伺服器進程接受請求,則伺服器進程與客戶機進城之間便建立了一條通訊連線。
// 格式:int connect(SOCKET s, const struct sockaddr FAR * name, int namelen)。
// 參數:s - 欲要建立連線的通訊端;
// name - 指向通訊對方的通訊端地址結構指針,表示s欲與其建立連線;
// namelen - name參數的長度。
// 返回值:函數成功時返回0;失敗時返回SOCKET_ERROR。
// 說明:在客戶機進程呼叫該方法請求建立連線時,將啓用建立連線的3次握手,以此來建立一條與伺服器進程的TCP連線。
// 如果該函數呼叫之前沒有系結地址,系統自動系結本地地址到此通訊端。
fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
exit(1);
}
//建立執行緒()
//初始化libevent
while (true) {
//char MESSAGE[] = "h server..\n";
string MESSAGE; cin >> MESSAGE;
//bufferevent_write(buf_ev,MESSAGE,strlen(MESSAGE));
if ((send(sockfd, MESSAGE.c_str(), MESSAGE.size(), 0))==-1) {
printf("the net has a error occured..");
break;
}
send函數:
// 功能:在已建立連線的通訊端上發送數據.
// 格式:int send(SOCKET s, const char* buf, int len, int flags)。
// 參數:s - 已建立連線的通訊端(socket 函數正確執行後的返回值);
// buf - 存放將要發送的數據的緩衝區指針;
// len - 發送緩衝區中的字元數;
// flags - 控制數據傳輸方式:
// (1)0:接收的是正常數據,無特殊行爲。
// (2)MSG_DONTROUTE:表示目標主機就在本地網路中,無需路由選擇。
// (3)MSG_OOB:表示處理帶外數據。
// 返回值:發送成功時返回發送的數據長度,連線結束時返回0,連線失敗時返回SOCKET_ERROR(-1)。
if ((nbytes = recv(sockfd, buffer, 1024, 0)) == -1) {
fprintf(stderr, "read error:%s\n", strerror(errno));
exit(1);
}
recv函數:
// 功能:在已建立連線的通訊端上接收數據。
// 格式:int recv(SOCKET s, char* buf, int len, int flags)。
// 參數:s - 已建立連線的通訊端(socket 函數正確執行後的返回值);
// buf - 存放接收到的數據的緩衝區指針;
// len - buf的長度;
// flags - 呼叫方式:
// (1)0:接收的是正常數據,無特殊行爲。
// (2)MSG_PEEK:系統緩衝區數據複製到提供的接收緩衝區,但是系統緩衝區內容並沒有刪除。
// (3)MSG_OOB:表示處理帶外數據。
// 返回值:接收成功時返回接收到的數據長度,連線結束時返回0,連線失敗時返回SOCKET_ERROR。
buffer[nbytes] = '\0';//在接收到的字元末尾新增結束字元
printf("I have received:%s\n", buffer);
memset(buffer, 0, 1024);
Sleep(2);
}
/* 結束通訊 */
closesocket(sockfd);
closesocket函數:
// 功能:關閉通訊端,釋放與通訊端關聯的所有資源。
// 格式:int closesocket(SOCKET s)。
// 參數:s - 將要關閉的通訊端。
// 返回值:函數成功時返回0;失敗時返回SOCKET_ERROR。
// 說明:當通訊端s的數據緩衝佇列中還有未發出的數據時,
// 如果通訊端設定爲SO_DONTLINGER,則等待數據緩衝佇列中的數據繼續傳輸完畢關閉該通訊端;
// 如果通訊端設定爲SO_LINGER,則分以下兩種情況:
// (1)Timeout設爲0,通訊端馬上關閉,數據緩衝佇列中數據丟失。
// (2)Timeout不爲0,等待數據傳輸完畢或者Timeout爲0時關閉通訊端。
exit(0);
return 0;
}