首页 » 开发 » Linux网络编程

Linux网络编程

Linux网络编程的基础是Socket,以及各种网络协议,如TCP等。这章的笔记,主要是实际开发中的一些常用模式、经验等。

select()

select()用来监控多个文件描述符,当fd变得可读/可写时,select()将标记可读/可写fd。select()现在被认为是低效的fd监控接口,在实际项目中通常用epoll()来代替select()。

#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>

int select(int maxfd, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

void FD_CLR(int fd, fd_set* set);
int  FD_ISSET(int fd, fd_set* set);
void FD_SET(int fd, fd_set* set);
void FD_ZERO(fd_set* set);

select()的参数解析

一个参数maxfd是加入select()的最大文件描述符值+1,最大值为1024。可以修改FD_SETSIZE的值以使select()支持更多文件描述符监控,但必须重新编译内核,否则结果未知。

中间3个参数是3个fd集合,分别是你想监听的可读fd、可写fd、异常fd。

最后一个参数timeout是指定select()的超时时间。timeout的取值可以是:

  • NULL - 永久等待,直到有读/写/异常事件发生。
  • 0 - 立即返回,此时select()为非阻塞状态。
  • 其他值 - 指定select()等待时间。注意,timeout指定最长等待时间,但一旦有1个或多个fd可读/写/异常时select()就会返回。

select()的返回值

  • 0 - 超时,且没有任何读/写fd。
  • > 0 - 有读/写fd,用FD_ISSET()进一步判断。
  • -1 - select()出错。常见的错误包括:
    • EINTR - 捕获到信号。通常可忽略。
    • EBADF - 有无效的文件描述符。

Socket可读/写的常见情况

select()返回sockfd可读:

  1. Receive缓冲区的数据大于或等于low-water mark的值。low-water mark的值可通过SO_RCVLOWAT选项控制,默认是1。
  2. TCP连接接收到FIN,即Read half of the connections is closed。此时对sockfd的读操作将返回0,即EOF。
  3. 如果sockfd是一个监听套接字,则表明有新连接,可调用accept()函数建立新连接。
  4. Socket出错,此时对sockfd的读操作将返回-1。

select()返回sockfd可写:

  1. Send缓冲区的数据大于或等于low-water mark的值。low-water mark的值可通过SO_SNDLOWAT选项控制,默认是2048。
  2. Write half of the connection is closed,对sockfd的写操作将产生SIGPIPE信号。
  3. 对非阻塞的sockfd调用connect(),connect()完成或失败。
  4. Socket出错,此时对sockfd的写操作将返回-1。

分析。若读缓冲有数据,则socket可读;若写缓冲有空间,则socket可写。如果socket出错,则它本身处于可读写状态,且调用read()/write()返回-1。若是Listen Socket,则有新连接来时它可读;若是Non-block Connect Socket,则连接成功时它可写。这些情况都不难理解,只有以上列出情况3,需要进一步说明。

epoll()

epoll()用来取代select(),是一种更高效的多fd监控接口。

初始化epoll:

#include <sys/epoll.h>

int epoll_create(int size);

将fd加入epoll监控:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

epoll_event的结构如下:

typedef union epoll_data{
    void*       ptr;
    int         fd;
    __uint32_t  u32;
    __uint64_t  u64;
} epoll_data_t;

struct epoll_event{
    __uint32_t      events;
    epoll_data_t    data;
};

进入epoll等待:

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

这里有一段epoll的代码框架:

int epfd = epoll_create(epsz);

struct epoll_event ev;
ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET;

epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

struct epoll_event events[N];
while(1){
    int nfds = epoll_wait(epfd, events, epsz, -1);
    for(int i = 0; i < nfds; ++i){
        if(events[i].events & EPOLLIN){
            // ...
        }
    }
}

epoll详述

依赖epoll的程序要注意文件描述符耗尽的问题。在建立连接后,如果Client主机崩溃或网络断开,没有任何数据包的交互,则epoll不能检测到事件,因此fd就可能死在epoll中。解决方案是对fd设置一个timeout值,如果超时没有任何事件,就关闭之。

分享

0