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