服务器之家

服务器之家 > 正文

Windows下实现简单的libevent服务器

时间:2020-08-21 16:29     来源/作者:有梦想不彷徨

最近再学习Libevent由于自己使用的是windows系统,遗憾的是有关在vs下可以参考的程序少之又少。在参考了许多的博客文章后。自己摸索写了一个简单的Libevent Server程序。并且在网上找了一个简单的客户端程序,测试该代码成功。今天在此做一个记录。

Libevent的确是一个非常好用的东西,还在继续学习中,后续还要在windows下实现Libevent的多线程使用。今天先把自己搞出来的东西贴上来,仅供学习参考。在vs2015上编译通过。

默认情况下是单线程的(可以配置成多线程,如果有需要的话),每个线程有且只有一event base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。

?
1
2
3
//创建一个event_base
struct event_base *base = event_base_new();
assert(base != NULL);

event_base内部有一个循环,循环阻塞在epoll / kqueue等系统调用上,直到有一个 / 一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类(这里只讲fd了,其他的看manual吧)。struct event使用event_new来创建和绑定,使用event_add来启用:

?
1
2
3
4
5
6
7
//创建并绑定一个event
struct event *listen_event;
//参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数
 
listen_event = event_new(base, listener, EV_READ | EV_PERSIST, callback_func, (void*)base);
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置)
event_add(listen_event, NULL);

注: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
然后需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动event base dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak() / event_loopexit()函数。
//启动事件循环
event_base_dispatch(base);

接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。其原型是:
typedef void(*event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg) 

对于一个服务器而言,上面的流程大概是这样组合的:
1. listener = socket(),bind(),listen(),设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,
    实际上libevent提供了统一的包装evutil_make_socket_nonblocking)
2. 创建一个event_base
3. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)
    。对于listener socket来说,只需要监听EV_READ | EV_PERSIST
4. 启用该事件
5. 进入事件循环
-------------- -
6. (异步)当有client发起请求的时候,调用该回调函数,进行处理。
/*接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。*/ 

服务器端代码:Server.cpp

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#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>
 #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);
//error回调函数
void error_cb(struct bufferevent *bev, short event, void *arg);
//write 回调函数
void write_cb(struct bufferevent *bev, 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("error accept");
    return;
  }
  printf("ACCEPT: fd = %u\n", fd);
  ////注册一个bufferevent_socket_new事件
  struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
  ////设置回掉函数
  bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
  ////设置该事件的属性
  bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);
}
////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);
  //
  while (n = bufferevent_read(bev, line, MAX_LINE))
  {
    line[n] = '\0';
    printf("fd=%u, read line: %s\n", fd, line);
    //将获取的数据返回给客户端
    bufferevent_write(bev, line, n);
  }
}
////error回调函数
void error_cb(struct bufferevent *bev, short event, void *arg)
{
  //通过传入参数bev找到socket fd
  evutil_socket_t fd = bufferevent_getfd(bev);
  //cout << "fd = " << fd << endl;
  if (event & BEV_EVENT_TIMEOUT)
  {
    printf("Timed out\n"); //if bufferevent_set_timeouts() called
  }
  else if (event & BEV_EVENT_EOF)
  {
    printf("connection closed\n");
  }
  else if (event & BEV_EVENT_ERROR)
  {
    printf("some other error\n");
  }
  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("输入数据!");
  scanf_s("%d", &str);
  bufferevent_write(bev, &str, sizeof(str));
}
 
int main()
{
  int ret;
  evutil_socket_t listener;
  WSADATA Ws;
  //Init Windows Socket
  if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
  {
    return -1;
  }
  listener = socket(AF_INET, SOCK_STREAM, 0);
  assert(listener > 0);
  evutil_make_listen_socket_reuseable(listener);
  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");
    return 1;
  }
  if (listen(listener, 1000) < 0) {
    perror("listen");
    return 1;
  }
 printf("Listening...\n");
  evutil_make_socket_nonblocking(listener);
  struct event_base *base = event_base_new();
  assert(base != NULL);
  struct event *listen_event;
  listen_event = event_new(base, listener, EV_READ | EV_PERSIST, do_accept_cb, (void*)base);
  event_add(listen_event, NULL);
  event_base_dispatch(base);
  printf("The End.");
  return 0;
}

客户端代码:Client.cpp

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/******* 客户端程序 client.c ************/
#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>
 
 #pragma comment (lib,"ws2_32.lib")
 int main(int argc, char *argv[])
 {
   WSADATA Ws;
   //Init Windows Socket
   if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
   {
     return 0;
   }
   int sockfd;
   char buffer[1024];
   struct sockaddr_in server_addr;
   struct hostent *host;
   int portnumber, nbytes;
 
   if ((host = gethostbyname("127.0.0.1")) == NULL)
   {
     fprintf(stderr, "Gethostname error\n");
     exit(1);
   }
 
   if ((portnumber = atoi("9999"))<0)
   {
     fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
     exit(1);
   }
 
   /* 客户程序开始建立 sockfd描述符 */
   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
   {
     fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
     exit(1);
   }
 
   /* 客户程序填充服务端的资料    */
   memset(&server_addr,0, sizeof(server_addr));
   server_addr.sin_family = AF_INET;
   server_addr.sin_port = htons(portnumber);
   server_addr.sin_addr = *((struct in_addr *)host->h_addr);
 
   /* 客户程序发起连接请求     */
   if (connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
  {
     fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
     exit(1);
   }
 
   while (true)
   {
     char MESSAGE[] = "hello server..\n";
     //bufferevent_write(buf_ev,MESSAGE,strlen(MESSAGE));
     //
     if (-1 == (::send(sockfd, MESSAGE, strlen(MESSAGE), 0)))
     {
       printf("the net has a error occured..");
       break;
     }
 
     if ((nbytes = recv(sockfd, buffer, 1024,0)) == -1)
    {
       fprintf(stderr, "read error:%s\n", strerror(errno));
       exit(1);
     }
 
     buffer[nbytes] = '\0';
     printf("I have received:%s\n", buffer);
     memset(buffer, 0, 1024);
 
     Sleep(2);
 
   }
   /* 结束通讯   */
   closesocket(sockfd);
   exit(0);
 
   return 0;
 }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

相关文章

热门资讯

2022年最旺的微信头像大全 微信头像2022年最新版图片
2022年最旺的微信头像大全 微信头像2022年最新版图片 2022-01-10
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整 2021-08-24
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
暖暖日本高清免费中文 暖暖在线观看免费完整版韩国
暖暖日本高清免费中文 暖暖在线观看免费完整版韩国 2021-05-08
返回顶部