Nagle算法
Nagle算法用于对缓冲区内一定数量的消息进行自动连接,该处理过程称为Nagling,通过减少必须发送的封包的数量,提高网络程序的效率,但会引入一定的延迟。
默认情况下,发送数据采用Nagle算法。指发送方发送的数据不会立即发出,先放在缓冲区,等缓冲区满了以后再发出。发送完一批数据后,会等待接收方对这批数据的回应,然后再发送下一批数据。适用于发送方需要发送大批数据,并且对方接受会及时作出回应的场合。这种算法通过减少传输数据的次数来提高通信效率。如果发送方持续发送小批量的数据,并且接收方不一定会立即发送响应数据,Nagle算法会是发送方运行很慢。
TCP_NODELAY禁用Nagle算法
void set_nodelay(int fd, bool enabled)
{
int flag = enabled;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int));
}
非阻塞I/O
系统分为低速系统调用和其他系统调用两类。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:
1. 如果某些文件类型(如管道,终端设备,网络设备)的数据不存在,则读操作可能会使调用者永远阻塞
2. 如果数据不能被上述同样类型的文件接受,则写操作也会使调用者永远阻塞
3. 在某种条件之前,打开某些类型文件会被阻塞
4. 对已经加上强制性记录锁的文件进行读写
5. 某些ioctl操作
6. 某些进程间通信
非阻塞I/O使调用open,write,read时不会一直阻塞
指定非阻塞I/O的方法: 1)如果open获得描述符,可指定O_NONBLOCK标志
2)对于已经打开的文件描述符,调用fcntl,由该函数打开O_NONBLOCK标志
void set_nonblocking(int fd)
{
int old_flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, old_flags | O_NONBLOCK);
}
对于非阻塞描述符的操作不能无阻塞的完成,
errno会被设置成
EAGAIN/EWOULDBLOCK
close-on-exec
对文件描述符设置FD_CLOEXEC,使用exec系列执行的程序里,此文件描述符被关闭,不能在使用,但在使用fork产生的进程中,此文件描述符并不关闭,可以继续使用。
void set_cloexec(int fd)
{
int old_flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, old_flags | FD_CLOEXEC);
}
epoll
epoll是linux内核为处理大批量句柄而改进了poll。
优点:1)epoll没有进程打开的文件描述符的限制,上限是可以打开文件的数目。(select有文件描述符的限制)
2)epoll只会管“活跃”的连接,对活跃的连接进行处理。因为在内核实现中epoll是根据每个fd上面的callback函数实现的。(select、poll都会轮询完所有的fd,才进行处理)
3)内存拷贝方式较快,使用mmap加速内核和用户空间的消息传递
epoll的两种工作方式:
LT(level triggered)是缺省工作方式,并且同时支持block和no-block socket。内核告诉你一个文件描述符是否就绪了,你就可以对就绪的fd进行IO操作。如果不进行任何操作,内核还是会继续通知你。传统的select、poll是这种模型的代表。
ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下。当描述符从未就绪变为就绪时,内核就通过epoll告诉你。然后假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致文件描述符不在为就绪状态。只会通知一次。
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
常见错误
EWOULDBLOCK 用于非阻塞模式。不需要重新读或写
EAGAIN 不用管,继续处理
EINTR 系统中断,继续处理
ECONNABORTED 该错误被描述为“software caused connection abort”,即“软件引起的连接中止”。原因在于当服务和客户进程在完成用于 TCP 连接的“三次握手”后,客户 TCP 却发送了一个 RST (复位)分节,在服务进程看来,就在该连接已由 TCP 排队,等着服务进程调用 accept 的时候 RST 却到达了。POSIX 规定此时的 errno 值必须 ECONNABORTED。源自 Berkeley 的实现完全在内核中处理中止的连接,服务进程将永远不知道该中止的发生。服务器进程一般可以忽略该错误,直接再次调用accept。
ENFILE 文件表溢出
EMFILE 打开文件太多