epoll回射程序;简单实例

本文从WordPress迁移而来, 查看全部WordPress迁移文章

  1. 一个回射程序,即客户端给服务器发送信息,服务器回发相同的信息
  2. 使用I/O复用模型,用epoll代替select
  3. 服务器模型是迭代的,只有一个进程,一个线程
  4. 整个程序没有才注重错误处理
  5. 以此简单示例为基础可以改造成多进程或多线程模型
  6. 客户端可以直接用telnet测试
  7. 独立编写,有错请指出
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
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <error.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MAXLINE 10
#define MAXEPOLLEVENTS 55
#define LISTENQ 5
#define SERVPORT 7777
#define SA struct sockaddr
#define MED struct my_epoll_data

void make_file_non_blocking(int fd) {
int flags;
flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
}

struct my_epoll_data { //自定义的结构,用来保存信息
int fd;
char data[MAXLINE * 500000];
my_epoll_data() {
reset();
}
my_epoll_data(int _fd): fd(_fd) {
reset();
}
void reset() { data[0] = '\0'; }
};

int main() {
struct sockaddr_in servaddr;
int listenfd,epollfd;
struct epoll_event ev;
struct epoll_event *events;

// 设置服务器地址信息
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERVPORT);

// 创建一个epoll句柄和
events = new epoll_event[MAXEPOLLEVENTS];
epollfd = epoll_create(MAXEPOLLEVENTS);

// 创建监听socket
listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
make_file_non_blocking(listenfd); //将文件设置为非阻塞方式
bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);

// 注册socket
ev.data.ptr = new my_epoll_data(listenfd);
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);

puts("服务器启动");
while (true) {
int nfds = epoll_wait(epollfd, events, MAXEPOLLEVENTS, -1);
for (int i = 0; i < nfds; i++) {
// 附带的数据
my_epoll_data *med = (MED *)events[i].data.ptr;
// 唤醒的socket文件
int fd = med->fd;

// listen返回,新客户到达
if (fd == listenfd) {
while (true) { // accept循环
struct sockaddr_in cliaddr;
socklen_t clilen = sizeof(struct sockaddr_in);
int connfd = accept(listenfd, (SA *)&cliaddr, &clilen);
if (connfd == -1) break; // accept完毕(sockfd是非阻塞的)
/******* 在epoll中注册 ******/
make_file_non_blocking(connfd);
ev.data.ptr = new my_epoll_data(connfd);
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev);
/******* 在epoll中注册 ******/
}
}

// 有数据可读(接收到信息)
else if (events[i].events & EPOLLIN) {
bool done = false;
char *str = med->data;
while (true) { // read循环
char buff[MAXLINE+5]; //故意将buff的大小设置为10这么小的长度
int n = read(fd, buff, MAXLINE);
if (n == -1) break; // 本轮的收数据结束
else if (n == 0) { // 对端发送了FIN,正常关闭
done = true; break;
}
else { // 收到数据,保存下来(等下还要发回去给客户端)
buff[n] = '\0';
strcat(str, buff);
}
}
if (done) { // 对端已经发送FIN,断开连接
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL); // 从epoll中删除该socket
delete med; // 细节,这时候该数据结构已经不会再用到,即使释放内存,当并发量很大时会new出很多
close(fd); // 关闭文件,当并发量很大时不及时关闭可能会不够用
}
else { // 对端还没关闭,修改该socket的事件,等下要利用它发信息
ev.events = EPOLLOUT | EPOLLET; // 修改为写
ev.data.ptr = med;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev); // 修改
}
}
// 有数据可写(发送信息)
else if (events[i].events & EPOLLOUT) {
char *str = med->data;
int length = strlen(str);
write(fd, str, length); // 把信息回射给客户端
med->reset(); // 清空缓存
ev.events = EPOLLIN | EPOLLET; // 修改为读
ev.data.ptr = med;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev); // 修改
}
}
}
delete []events; // 释放内存
return 0;
}