libevent创建tcp服务端与tcp客户端

libevent的tcp回射(echo)程序

libevent的tcp服务端程序源码

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* @brief 使用libevent编写的tcp服务端程序
* 接受客户端的链接,读取客户端发送的数据,并返回给客户端。
* 默认监听端口为9090
*
* 实现了异步非阻塞的网络通信,可以高效地处理多个并发连接。以下是具体的步骤:
* 1. 初始化事件基:
* 创建事件基对象,用于管理事件的调度和执行。
* 2. 创建监听套接字:
* 创建并配置监听套接字,使其准备好接受客户端的连接请求。
* 3. 注册监听事件:
* 创建一个事件,用于监听新连接的到来,并将该事件添加到事件基中。
* 4. 启动事件循环:
* 启动事件基的事件派发循环,监听内核中的事件,并将事件分发给对应的回调函数。
* 5. 接受连接:
* 当有新的连接请求到来时,触发 `accept_callback` 回调函数,接受连接,并为每个新连接创建一个用于读取数据的事件对象。
* 6. 读取数据:
* 当有可读事件发生时,触发 `read_callback` 回调函数,从连接中读取数据,并将接收到的数据回显给客户端。
*
* 通过这种方式,服务端可以高效地处理多个并发连接,并且在每个连接上有数据可读时及时响应。
*/

#include <event2/event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PORT 9090
#define BACKLOG 10
#define BUF_SIZE 1024

// 全局变量
struct event_base *base = NULL; // 事件基
int client_count = 0; // 客户端计数

// 回调函数声明
static void accept_callback(evutil_socket_t fd, short event, void *arg); // 接受连接回调
static void read_callback(evutil_socket_t fd, short event, void *arg); // 读取数据回调

struct event_wrapper{
struct event* ev;
};

int main(void) {
// 初始化事件基
base = event_base_new();
if (!base) {
fprintf(stderr, "无法初始化事件基对象。\n");
return EXIT_FAILURE;
}

// 创建监听套接字
evutil_socket_t listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket < 0) {
perror("创建套接字失败");
return EXIT_FAILURE;
}

// 绑定套接字到端口
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // 清空结构体
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(PORT); // 端口号
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有接口

if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("绑定套接字失败");
return EXIT_FAILURE;
}

// 开始监听连接
if (listen(listen_socket, BACKLOG) < 0) {
perror("监听套接字失败");
return EXIT_FAILURE;
}

// 创建一个事件,用于接收新连接
struct event *listen_event = event_new(base, listen_socket, EV_READ | EV_PERSIST, accept_callback, &client_count);
if (!listen_event) {
perror("创建监听事件失败");
return EXIT_FAILURE;
}
event_add(listen_event, NULL); // 将事件添加到事件基中

printf("开始监听:127.0.0.1:%d\n", PORT);

// 派发事件
event_base_dispatch(base);

// 清理资源
event_base_free(base);

return EXIT_SUCCESS;
}

// 接受连接的回调函数
static void accept_callback(evutil_socket_t fd, short event, void *arg) {
int *client_count = (int *)arg; // 客户端计数指针
struct sockaddr_storage remote_addr;
socklen_t addrlen = sizeof(remote_addr); // 地址长度
evutil_socket_t conn_socket = accept(fd, (struct sockaddr *)&remote_addr, &addrlen); // 接受连接
if (conn_socket < 0) {
perror("接受连接失败");
return;
}

// 获取客户端 IP 和端口
char addr_str[INET6_ADDRSTRLEN]; // 存储 IP 地址字符串
struct sockaddr *sa = (struct sockaddr *)&remote_addr;
inet_ntop(remote_addr.ss_family, (void *)&(((const struct sockaddr_in *)sa)->sin_addr), addr_str, sizeof(addr_str));
printf("接受来自 %s 的连接\n", addr_str);

struct event_wrapper* connect_ev = (struct event_wrapper*)malloc(sizeof(struct event_wrapper));
// 创建一个事件,用于读取数据
connect_ev->ev = event_new(base, conn_socket, EV_READ | EV_PERSIST, read_callback, connect_ev );
if (!connect_ev->ev) {
perror("创建连接事件失败");
close(conn_socket);
return;
}
event_add(connect_ev->ev, NULL); // 将事件添加到事件基中

(*client_count)++; // 客户端计数加一
}

// 读取数据的回调函数
static void read_callback(evutil_socket_t fd, short event, void *arg) {
struct event_wrapper* connect_ev = (struct event_wrapper*)arg; //链接事件的结构体
char buffer[BUF_SIZE]; // 读取缓冲区
memset(buffer, 0, sizeof(buffer)); // 清空缓冲区
ssize_t nread = recv(fd, buffer, sizeof(buffer) - 1, 0); // 从套接字读取数据
if (nread <= 0) {
if (nread < 0 && errno != ECONNRESET) {
perror("读取数据失败");
}
printf("客户端断开连接。\n");
event_del(connect_ev->ev); // 删除事件
free(connect_ev);
close(fd); // 关闭套接字
exit(-1);
return;
}

printf("收到数据: %.*s\n", (int)nread, buffer); // 打印接收到的数据

// 回显数据
ssize_t nwrote = send(fd, buffer, nread, 0); // 发送数据
if (nwrote <= 0) {
if (nwrote < 0 && errno != EPIPE) {
perror("发送数据失败");
}
printf("客户端断开连接。\n");
event_del(connect_ev->ev); // 删除事件
free(connect_ev);
close(fd); // 关闭套接字
return;
}

printf("发送数据: %.*s\n\n", (int)nwrote, buffer); // 打印发送的数据
}

libevent的tcp客户端程序源码

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
* @brief 该程序实现了异步非阻塞的网络通信
* 读取用户输入的数据,发送给服务端,并把服务端返回的数据打印出来
*
* 以下是具体的步骤:
* 1. 初始化事件基:
* 创建事件基对象,用于管理事件的调度和执行。
* 2. 创建客户端套接字:
* 创建并配置客户端套接字,使其准备好与服务器建立连接。
* 3. 创建连接事件:
* 创建一个事件,用于尝试与服务器建立连接,并将该事件添加到事件基中。
* 4. 启动事件循环:
* 启动事件基的事件派发循环,监听内核中的事件,并将事件分发给对应的回调函数。
* 5. 尝试连接服务器:
* 当客户端套接字变为可写时,触发 `connect_callback` 回调函数,尝试与服务器建立连接。
* 6. 读取数据:
* 当有可读事件发生时,触发 `read_callback` 回调函数,从连接中读取数据,并将接收到的数据打印出来。
* 7. 写入数据:
* 当有可写事件发生时,触发 `write_callback` 回调函数,读取用户输入的数据并发送给服务器。
* 通过这种方式,客户端可以高效地与服务器进行交互,并且在每次有数据可读或可写时及时响应。
*/
#include <event2/event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 9090
#define BUFFER_SIZE 1024

// 全局变量
struct event_base *base = NULL; // 事件基
evutil_socket_t client_socket = -1; // 客户端套接字

// 回调函数声明
static void connect_callback(int fd, short which, void *arg);
static void read_callback(int fd, short which, void *arg);
static void write_callback(int fd, short which, void *arg);


void read_user_input(int client_socket){
char buffer[BUFFER_SIZE];
memset(buffer, 0, sizeof(buffer));
// 读取用户输入
printf("请输入要发送的数据: ");
fgets(buffer, sizeof(buffer), stdin);

buffer[strlen(buffer)-2] = '\0';
// 发送数据
ssize_t nwrote = send(client_socket, buffer, strlen(buffer), 0);
if (nwrote <= 0) {
if (nwrote < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
perror("发送数据失败");
}
printf("服务器已断开连接。\n");
event_base_loopexit(base, NULL);
return;
}

// 打印发送的数据
printf("发送数据: %.*s\n", (int)nwrote, buffer);
}


void init_client() {
// 初始化事件基
base = event_base_new();
if (!base) {
fprintf(stderr, "无法初始化事件基对象。\n");
exit(EXIT_FAILURE);
}

// 创建客户端套接字
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0) {
perror("创建客户端套接字失败");
exit(EXIT_FAILURE);
}

// 设置服务器地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("服务器 IP 地址解析失败");
exit(EXIT_FAILURE);
}

// 创建连接事件
struct event *connect_event = event_new(base, client_socket, EV_WRITE, connect_callback, &server_addr);
if (!connect_event) {
perror("创建连接事件失败");
exit(EXIT_FAILURE);
}
event_add(connect_event, NULL);

// 启动事件循环
event_base_dispatch(base);
}

// 连接回调函数
static void connect_callback(int fd, short which, void *arg) {
struct sockaddr_in *server_addr = (struct sockaddr_in *)arg;
if (connect(fd, (struct sockaddr *)server_addr, sizeof(*server_addr)) < 0) {
perror("连接服务器失败");
exit(EXIT_FAILURE);
}

// 注册读事件
struct event *read_event = event_new(base, client_socket, EV_READ | EV_PERSIST, read_callback, NULL);
if (!read_event) {
perror("创建读事件失败");
exit(EXIT_FAILURE);
}
event_add(read_event, NULL);

// 注册写事件
struct event *write_event = event_new(base, client_socket, EV_WRITE, write_callback, NULL);
if (!write_event) {
perror("创建写事件失败");
exit(EXIT_FAILURE);
}
event_add(write_event, NULL);

// 输出连接成功的消息
printf("连接到服务器:%s:%d 成功。\n", SERVER_IP, SERVER_PORT);
}

// 读取数据的回调函数
static void read_callback(int fd, short which, void *arg) {
char buffer[BUFFER_SIZE];
memset(buffer, 0, sizeof(buffer));

ssize_t nread = recv(fd, buffer, sizeof(buffer) - 1, 0);
if (nread <= 0) {
if (nread < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
perror("读取数据失败");
}
printf("服务器已断开连接。\n");
event_base_loopexit(base, NULL);
return;
}

// 打印接收到的数据
printf("收到数据: %.*s\n\n", (int)nread, buffer);

// 读取用户输入
read_user_input(fd);

}

// 写入数据的回调函数
static void write_callback(int fd, short which, void *arg) {
char buffer[BUFFER_SIZE];
memset(buffer, 0, sizeof(buffer));
read_user_input(fd);
}

int main() {
init_client();
event_base_free(base);
close(client_socket);
return EXIT_SUCCESS;
}

程序运行结果

首先运行服务端,然后运行客户端程序。

客户端启动后,如无需特殊情况,可以不用输入服务端地址,直接按下两次回车,使用默认地址。

程序运行结果如下所示:

服务端

1
2
3
4
5
6
7
8
9
10
./tcp_echo_srv
开始监听:127.0.0.1:9090
接受来自 127.0.0.1 的连接
收到数据: hello
发送数据: hello

收到数据: i am clien
发送数据: i am clien

客户端断开连接。

客户端

1
2
3
4
5
6
7
8
9
10
11
./tcp_echo_cli 
连接到服务器:127.0.0.1:9090 成功。
请输入要发送的数据: hello
发送数据: hello
收到数据: hello

请输入要发送的数据: i am client
发送数据: i am clien
收到数据: i am clien

请输入要发送的数据: ^C

libevent的tcp回射程序源码解析

服务端

  1. 初始化事件基
    • 创建事件基对象 base
  2. 创建监听套接字
    • 使用 socket 函数创建一个 TCP 套接字。
    • 将套接字绑定到本地 IP 地址和端口。
    • 将套接字置于监听状态,准备接受客户端的连接请求。
  3. 注册监听事件
    • 创建一个事件对象 listen_event,用于监听新连接的到来。
    • listen_event 添加到事件基中,以便当有新的连接请求到来时,触发 accept_callback 回调函数。
  4. 启动事件循环
    • 使用 event_base_dispatch 函数启动事件基的事件派发循环。
  5. 接受连接
    • 当有新的连接请求到来时,触发 accept_callback 回调函数。
    • 使用 accept 函数接受新的连接,并为每个新连接创建一个用于读取数据的事件对象 connect_ev
    • connect_ev 添加到事件基中,以便当有可读事件发生时,触发 read_callback 回调函数。
  6. 读取数据
    • 当有可读事件发生时,触发 read_callback 回调函数。
    • 使用 recv 函数从连接中读取数据,并将接收到的数据打印出来。
    • 如果读取失败或客户端断开连接,则关闭连接并删除事件。
    • 如果读取成功,则将数据回显给客户端,并打印发送的数据。

客户端

  1. 初始化事件基
    • 创建事件基对象 base
  2. 创建客户端套接字
    • 使用 socket 函数创建一个 TCP 套接字。
    • 设置服务器的 IP 地址和端口。
  3. 创建连接事件
    • 创建一个事件对象 connect_event,用于尝试与服务器建立连接。
    • connect_event 添加到事件基中,以便当客户端套接字变为可写时,触发 connect_callback 回调函数。
  4. 启动事件循环
    • 使用 event_base_dispatch 函数启动事件基的事件派发循环。
  5. 尝试连接服务器
    • 当客户端套接字变为可写时,触发 connect_callback 回调函数。
    • 使用 connect 函数尝试与服务器建立连接。
    • 如果连接成功,则注册读事件和写事件,并输出连接成功的消息。
  6. 读取数据
    • 当有可读事件发生时,触发 read_callback 回调函数。
    • 使用 recv 函数从连接中读取数据,并将接收到的数据打印出来。
    • 如果读取失败或服务器断开连接,则关闭连接并退出事件循环。
    • 如果读取成功,则调用 read_user_input 函数读取用户输入的数据并发送给服务器。
  7. 写入数据
    • 当有可写事件发生时,触发 write_callback 回调函数。
    • 调用 read_user_input 函数读取用户输入的数据并发送给服务器。
  8. 读取用户输入
    • 读取用户输入的数据并发送给服务器。
    • 如果发送失败或服务器断开连接,则关闭连接并退出事件循环。