Libevent基本通訊程式碼詳細介紹

2020-08-14 19:09:35

Libevent基本通訊程式碼詳細介紹

程式碼和解釋只是本人從別的地方收集之後總結的,並不是本人寫的,侵刪

實現效果

給client端輸入數據,client端將數據發送到server端,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端程式碼

/*********************** 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;
}