epoll是I/O复用模型中相对epoll和select更高效的实现对套接字管理的函数。

epoll有两种模式 LT 和 ET

二者的差异在于 level-trigger 模式下只要某个 socket 处于 readable/writable 状态,无论什么时候进行 epoll_wait 都会返回该 socket;而 edge-trigger 模式下只有某个 socket 从 unreadable 变为 readable 或从unwritable 变为 writable 时,epoll_wait 才会返回该 socket。

LT和ET模式下对于accept和read,write函数的使用有所不同

一、ET模式下(转载)
Q1:调用accept时,到底TCP完成队列里有多少个已经建立好的连接?
这里又得分情况来说:
  • 没有连接。这种情况发生在TCP连接被客户端夭折,即在服务端调用accept之前客户端给出一个RST该RST导致刚刚建立好的连接从服务器端的TCP完成队列中被移出。源自berkeley的实现会在内核处理该事件,并不会将该事件通知给服务程序,如果套接口被设置为阻塞模式,就会导致accept函数被阻塞,程序挂起,一直要等到下一个连接到来,这也是为什么采用非阻塞的accept的原因;其他的实现通常会返回ECONNABORTED或EPROTO错误。

解决方法采用非阻塞的accept,在accept返回后处理ECONNABORTED、EPROTO、EINTR错误。

  • 一个以上的连接。一个连接很容易理解,多个连接的情况出现在有多个连接请求同时到达的情况,由于是边缘触发,epoll_wait只返回一次,然后调用accept处理一个请求这会导致TCP完成队列中依然存在连接未被处理。

解决办法:

1):将该套接口设置为非阻塞模式,然后将accept用while循环包住,处理完TCP完成队列中的所有连接再跳出循环。

2):将accept放入一个单独的线程中,这样还可以采用阻塞模式。

Q2:read函数应当如何处理?

read函数有可能不能够一次将内核缓冲的数据读完,由于采用的ET模式,epoll_wait不会因为内核缓冲区还有数据而继续读取。这就会导致内核数据不能及时、完全地读到服务程序。

解决办法将套接口设置为非阻塞,再用while循环包住read一直读到产生EAGAIN错误,采用非阻塞套接口的原因在于防止read被阻塞住。网上看见有的朋友说也可以采用阻塞套接口,判断read的返回值是否小于期望值,小于就证明数据读完,但是个人认为这里存在一个临界情况,比如内核缓冲有2048字节,期望读1024字节,读完第二次后read返回的是1024,等于期望值,于是再读,但此时内核已经没有数据,于是read被阻塞(自个的思考,不知有务否)。

Q3:写缓冲区什么时候可以导致epoll_wait函数返回可写状态?

个人对这个问题不是很清楚,按资料说法是该缓冲区从不可写变为可写则返回,那么该套接口刚创建的时候处于可写状态,是否会导致一直返回不了可写状态,也就是说第一次永远返回不了,因为没有任何调用会使该套接口从不可写变为可写。

另外,写的时候还有些什么要注意的么?

二、LT模式下(转载)

Q1:accept函数

accept函数同样存在连接被夭折的情况,和ET模式下类似。但这种模式下不会存在处理不完多个请求的情况。因为只要有连接存在TCP完成队列中,epoll_wait就会一直返回。

Q2:read函数

同样可以避免因为一次读不完内核缓冲区的数据的情况。

Q3:当内核写缓冲空闲时,可写状态会一直返回,如何处理?(这个曾是tencent的一个面试题)

A1:不将可写条件加入epoll集中,采用非阻塞I/O,有数据可写时,直接写,如果返回是EAGAIN,则将可写加入epoll集中,写完后移出。

通常都是写失败情况下,才会将写加入epoll中操作。

//此回射服务器用边缘触发的模式使用epoll
#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <fcntl.h> void myerr(char *m)
{
perror(m);
exit();
} int main(int argc, char** argv)
{
int listenfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, )) < )
myerr("socket error");
int flags = fcntl(listenfd, F_GETFL, );
fcntl(listenfd, F_SETFL, flags | O_NONBLOCK); int on = ;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < )
myerr("setsockopt error"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_port = htons();
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < )
myerr("bind error"); if(listen(listenfd, SOMAXCONN) < )
myerr("listen error"); int epollfd;
epollfd = epoll_create(EPOLL_CLOEXEC); struct epoll_event event;
event.data.fd = listenfd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event); struct epoll_event events[]={};
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);;
int conn, counter=;
char recvbuf[];
int i; int nready;
for(;;)
{
nready = epoll_wait(epollfd, events, , -);
if(nready == -)
{
if(errno == EINTR)
continue;
myerr("epoll_wait");
} for(i=; i<nready; i++)
{
if(events[i].data.fd == listenfd)
{
while((conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen)) > )
{
printf("ip: %s port: %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
printf("%d\n", ++counter);
}
if (conn == -)
if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
perror("accept"); event.data.fd = conn;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event);
}
else if(events[i].events & EPOLLIN)
{
conn = events[i].data.fd;
if(conn < )
continue;
memset(recvbuf, , sizeof(recvbuf));
int ret = read(conn, recvbuf, sizeof(recvbuf));
if(ret == )
{
printf("client close\n");
close(conn);
event = events[i];
epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);
continue;
}
write(conn, recvbuf, ret);
fputs(recvbuf, stdout);
}
}
} return ;
}

epoll 实现回射服务器的更多相关文章

  1. 服务器编程入门(10)TCP回射服务器实现 - 并发

    问题聚焦:     在前面我们大概浏览了一下服务器编程需要掌握的一些知识和技术,以及架构思想.        实践,才是检验真理的唯一标准..从这节起我们将在这些技术的基础上,一步步实现以及完善一个服 ...

  2. Linux下select的用法--实现一个简单的回射服务器程序

    1.先看man手册 SYNOPSIS       /* According to POSIX.1-2001 */       #include <sys/select.h>       / ...

  3. TCP回射服务器修订版(ubuntu 18.04)

    一.需求 把https://www.cnblogs.com/soldierback/p/10673345.html中的TCP回射服务器程序重写成使用select来处理任意个客户的单进程 程序,而不是为 ...

  4. 【Unix网络编程】chapter5TCP回射服务器程序

    chapter5  5.1 概述 5.2 TCP回射服务器程序:main函数 int main(int argc, char **argv) { int listenfd,connfd; pid_t ...

  5. 第二十篇:不为客户连接创建子进程的并发回射服务器(poll实现)

    前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...

  6. 第十九篇:不为客户连接创建子进程的并发回射服务器(select实现)

    前言 在此前,我已经介绍了一种并发回射服务器实现.它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情况下,这种为每个 ...

  7. TCP客户/服务器程序实例——回射服务器

    目录 客户/服务器程序源码 POSIX信号处理 POSIX信号语义 处理SIGCHLD信号 处理僵死进程 处理被中断的系统调用 wait和waitpid函数 wait和waitpid函数的区别 网络编 ...

  8. 不为客户连接创建子进程的并发回射服务器( poll实现 )

    前言 在上文中,我使用select函数实现了不为客户连接创建子进程的并发回射服务器( 点此进入 ).但其中有个细节确实有点麻烦,那就是还得设置一个client数组用来标记select监听描述符集中被设 ...

  9. 不为客户连接创建子进程的并发回射服务器( select实现 )

    前言 在此前,我已经介绍了一种并发回射服务器实现( 点此进入 ).它通过调用fork函数为每个客户请求创建一个子进程.同时,我还为此服务器添加了自动消除僵尸子进程的机制.现在请想想,在客户量非常大的情 ...

随机推荐

  1. java设计模式-----2、工厂方法模式

    再看工厂方法模式之前先看看简单工厂模式 工厂方法模式(FACTORY METHOD)同样属于一种常用的对象创建型设计模式,又称为多态工厂模式,此模式的核心精神是封装类中不变的部分,提取其中个性化善变的 ...

  2. Java经典编程题50道之四十二

    809*??=800*??+9*??+1,其中??代表的两位数,8*??的结果为两位数,9*??的结果为3位数.求??代表的两位数,以及809*??后的结果. public class Example ...

  3. Mysql5.7在CentOs环境下定时备份数据库

    咱创建一个目录,专门用于存放Mysql的备份文件./data/mysql_bak.写个shell脚本,每分钟备份一次测试以下. 脚本代码: chmod /testdir/backup.sh //给脚本 ...

  4. Redis--配置密码

    可以通过以下方法进行密码的配置: ① 修改配置文件设置密码 ② 通过命令修改密码(重启redis后,新设置的密码会失效) 此处介绍第一种 1. 找到redis的配置文件,一般在/etc/redis.c ...

  5. Redis笔记2-发布订阅

    发布/订阅"(publish/subscribe)模式可以实现进程间通信,订阅者可以订阅一个或多个频道(channel),而发布者可以向指定的频道发送消息,所有订阅次频道的订阅者都会收到次消 ...

  6. nginx笔记6-总结

    1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除.2.weight指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况.3.ip_ ...

  7. Linux文本的处理

    Linux最最最最重要的哲学思想就是:一切皆文件.文件以及文件的操作在LInux操作系统中是非常的重要.熟练使用精悍小巧快捷的文本处理方式让效率更高. 一.文件查看命令 less 分页查看 [root ...

  8. activiti怎么实现用户自定义流程?请先看这里

    最近一两个星期收到了好几个qq好友添加的请求和csdn的私信,里面基本都是询问activiti相关的问题. 尤其是今天有个朋友给我发了私信,内容如下: 你好,请问你关于activiti工作流的问题:怎 ...

  9. 5.1 TLP的格式

    当处理器或者其他PCIe设备访问PCIe设备时,所传送的数据报文首先通过事务层被封装为一个或者多个TLP,之后才能通过PCIe总线的各个层次发送出去.TLP的基本格式如图5?1所示. 一个完整的TLP ...

  10. freemarker中的split字符串分割

    freemarker中的split字符串分割 1.简易说明 split分割:用来根据另外一个字符串的出现将原字符串分割成字符串序列 2.举例说明 <#--freemarker中的split字符串 ...