附带部分:
1.tcp粘包处理方式
(1)引入分割符
(2)在数据header中,加入数据长度
2.listen函数第二个参数介绍:半连接队列的长度。主要用于保存三次握手完成的队列,可以accept的队列,详细请期待三次握手详解部分。
3.常识部分,在大型项目编程中,分配内存,请一定要memset,防止脏区。
一、设备epoll网络socketfd设置
1.socketfd可以设置为阻塞和非阻塞,都是可以的
2.非阻塞的读写效率更高,读写返回速度更快。
二、epoll管理fd介绍
我们在创建一个epoll,在内核中,其实本质上是创建了一个红黑树,而我们的epoll_create创建的epfd为根节点,我们每次对epfd进行操作,其本质是对子节点进行操作。
epoll的触发方式有以下两种:
LT(level triggered)水平触发,是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表,使用场景为大数据块读取和发送。
ET (edge-triggered)边沿触发, 是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认, 使用场景为小数据块读取和发送。
以下两种使用相同:
ET+循环读取到返回-1结束的读取方式
LT+一次读取
三、epoll函数介绍
1.int epoll_create(int size);
功能:创建一个epoll对应的红黑树,返回对应的文件描述符
参数介绍:
size:size的值只有两种,一种等于0,一种大于0, 我们在使用时,传入大于0的任意值都可以
2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:对对应的epoll树进行操作
参数介绍:
epfd:对应的epoll树文件描述符
op:操作方式, 具体有以下三种
EPOLL_CTL_ADD:增加一个fd
EPOLL_CTL_MOD: 修改fd
EPOLL_CTL_DEL:删除fd
fd:op操作的对象
events:events成员变量: 可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
返回值:成功返回0,不成功返回-1
3.int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:阻塞等待IO事件的发生
参数介绍:
epfd:对应的epfd树文件描述符
epoll_event:用于接收代处理事件的数组;
maxevents:每次能处理的事件数,为events数组的大小,注意:此大小最好是要监控fd总量的1/100,效率值最高
timeout:等待I/O事件发生的超时值(单位我也不太清楚);
-1:相当于阻塞,一般使用-1
0:相当于非阻塞
返回值:发生事件数
四、编程实例
1 、初始化部分,此部分直接上代码,不做描述
int port = 8080;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
return -2;
}
if (listen(sockfd, 5) < 0) {
return -3;
}
// epoll opera
int epfd = epoll_create(1);
struct epoll_event ev, events[512] = {0};
ev.events = EPOLLIN;
ev.data.fd = sockfd; //int idx = 2000;
struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
si->sockfd = sockfd;
si->callback = accept_cb;
ev.data.ptr = si;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
2、epoll阻塞等待IO发生
while (1) {
int nready = epoll_wait(epfd, events, 512, -1);
if (nready < -1)
{
break;
}
int i = 0;
for (i = 0;i < nready;i ++)
{
//返回的事件处理
}
}
3.返回事件处理
(1)返回的fd分类,一种是要进行连接的sockfd,一种是可进行读写数据的clientfd,伪代码写法如下:
if (events[i].data.fd == sockfd) { //
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (clientfd <= 0) continue;
char str[INET_ADDRSTRLEN] = {0};
printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
ntohs(client_addr.sin_port));
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
} else {
int clientfd = events[i].data.fd;
char buffer[1024] = {0};
int ret = recv(clientfd, buffer, 5, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { //
continue;
} else {
}
close(clientfd);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
} else if (ret == 0) { //
//
printf("disconnect %d\n", clientfd);
close(clientfd);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
}
(2)按照返回的事件分类,分为可读和可写两部分,可读又分为连接和数据读取,伪代码如下
if (events[i].events & EPOLLIN) {
if (events[i].data.fd == sockfd) { //
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
if (clientfd <= 0) continue;
char str[INET_ADDRSTRLEN] = {0};
printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
ntohs(client_addr.sin_port));
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
} else {
int clientfd = events[i].data.fd;
char buffer[1024] = {0};
int ret = recv(clientfd, buffer, 5, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { //
continue;
} else {
}
close(clientfd);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
} else if (ret == 0) { //
//
printf("disconnect %d\n", clientfd);
close(clientfd);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
} else {
printf("Recv: %s, %d Bytes\n", buffer, ret);
}
}
}
if (events[i].events & EPOLLOUT) {
}
(3)利用回调函数处理
熟悉:epoll_data中的ptr使用,下次讲解
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};