Libevent基本通信代码详细介绍

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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下一个使用libevent实现websocket服务端的简单代码示例: ``` #include <event2/event.h> #include <event2/http.h> #include <event2/http_struct.h> #include <event2/buffer.h> #include <event2/keyvalq_struct.h> #include <event2/websocket.h> #include <event2/listener.h> static void websocket_handler(struct evhttp_request *req, void *arg) { struct evhttp_connection *conn = evhttp_request_get_connection(req); struct bufferevent *bev = evhttp_connection_get_bufferevent(conn); struct evhttp_uri *uri = evhttp_uri_parse(evhttp_request_get_uri(req)); const char *path = evhttp_uri_get_path(uri); if (strcmp(path, "/websocket") != 0) { evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request"); return; } struct evkeyvalq headers; evhttp_headers_copy(evhttp_request_get_input_headers(req), &headers); const char *sec_websocket_key = evhttp_find_header(&headers, "Sec-WebSocket-Key"); char response_key[29]; evutil_base64_encode(sec_websocket_key, strlen(sec_websocket_key), response_key, sizeof(response_key)); evhttp_add_header(evhttp_request_get_output_headers(req), "Sec-WebSocket-Accept", response_key); evhttp_add_header(evhttp_request_get_output_headers(req), "Connection", "Upgrade"); evhttp_add_header(evhttp_request_get_output_headers(req), "Upgrade", "websocket"); evhttp_send_reply(req, HTTP_SWITCHING_PROTOCOLS, "Switching Protocols", NULL); struct websocket *ws = websocket_new(bev); websocket_start(ws); evhttp_uri_free(uri); } int main(int argc, char **argv) { struct event_base *base; struct evhttp *http; struct evhttp_bound_socket *handle; struct evhttp_request *req; struct evhttp_connection *conn; struct event *ev; base = event_base_new(); http = evhttp_new(base); evhttp_set_cb(http, "/websocket", websocket_handler, NULL); handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", 8080); event_base_dispatch(base); return 0; } ``` 这个代码示例使用libevent库实现了一个简单的websocket服务端,监听本地8080端口,当有客户端请求连接时,会发送一个握手响应,建立websocket连接。你可以根据自己的需求,修改相应的代码实现更复杂的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值