首页 > 代码库 > Select单进程非阻塞TCP echo服务器
Select单进程非阻塞TCP echo服务器
Select单进程非阻塞TCP echo服务器
1. select 描述
#include <sys/select.h>
#include <sys/time.h>
int select( int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
返回:正确:就绪描述符数目;超时: 0;出错: -1
参数说明:
timeout: 内核等待指定描述符集合中的任意一个就绪的时间。
struct timeval {
long tv_sec; //seconds
long tv_usec; //microseconds
};
timeout = NULL: 永远等待下去:仅在有一个描述符准备好I/O才返回。
timeout != NULL: 等待timeval中指定的时间返回。
timeout != NULL, timeval == 0 : 不等待:检查描述符后立即返回。这称为轮循(polling);
前两种情况会被信号中断,并从信号处理程序返回。
timeout为const表示函数返回时不会修改timeval,但是有的linux版本会修改timval值。
考虑到可移植性,每次都应对timeout进行初始化。
readset, writeset, exceptset:指定我们让内核测试读写异常条件的描述符:
可读:
(1) 套接字缓冲区数据字数 >= 套接字接受缓冲区低水位标记的当前大小。 不会阻塞,返回>0的值(准备好读入的数据)。
可用SO_RCVLOWAT套接字选项设置标志,TCP和UDP默认1。所以read时可能返回1字节数据,需要反复读。
(2) 接受到FIN,读操作不阻塞返回0(EOF)。
(3) 监听套接字已完成的连接数不为0,新的连接到达。accept不阻塞。
(4) 有一个套接字错误要处理。读不阻塞返回-1。设置errno
可写:
(1) 发送缓冲区可用空间字节数 >= 最低水位。写不阻塞返回>0(例如:传输层接受的字节数)。 TCP和UDP最低水位2048。
(2) 连接的写半部关闭,对这样的套接字写产生SIGPIPE信号。
(3) 非阻塞的connect套接字已建立连接,或者connect已失败告终。
(4) 有一个套接字错误带处理。写不阻塞返回-1.设置errno。
异常:带外数据到达。
maxfdp1: 指定待测试的描述符个数,为最大描述符 + 1, 从0开始。
2. select 宏和相关说明
select使用描述符集fd_set,通常为一个整数数组,其中每个整数中的每一位对应一个描述符。
4个宏设置fd_set:
void FD_ZERO(fd_set *fdset); //清空fdset中所有位
void FD_SET(int fd, fd_set *fdset); //fd_set中fd位置位
void FD_CLR(int fd, fd_set *fdset); //fd_set中fd位复位
void FD_ISSET(int fd, fd_set *fdset); //fd_set中fd是否置位
select中的readset,writeset,exceptset = NULL,表示我们不关心这个。select会修改这三个描述符集和,时值——结果参数。
调用select时指定我们关心的描述符值,返回时指示那些已经就位,未就位的置0。因此每次调用select时都需要把我们关心的描述符置1。
3. select 缺陷
#include <sys/types.h> 定义了FD_SETSIZE,通常为1024。当描述符超出时,可用poll解决。
通过修改内核的FD_SETSIZE大小,需要重新编译内核,可能带来扩展性问题,不推荐使用。
4. 一个echo服务器程序示例
#include "unp.h"
int main(int argc, char **argv)
{
if (argc != 2) {
err_quit("Usage: a.out <#port>");
}
int listenfd = Tcp_listen(NULL, argv[1], NULL);
int flags = Fcntl(listenfd, F_GETFL, 0);
Fcntl(listenfd, F_SETFL, flags | O_NONBLOCK);
//最大文件描述符
int maxfd = listenfd;
//连接的客户端描述符
int client[FD_SETSIZE];
memset(client, -1, FD_SETSIZE);
//client已使用的描述符最大下标
int maxi = -1;
//要监听的描述符集合
fd_set allset;
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for (;;) {
//select会修改描述符集合,每次重新初始化
fd_set rset = allset;
DPRINTF("Start to select()");
//select阻塞到可读
int nready = Select(maxfd + 1, &rset, NULL, NULL, NULL);
//监听描述符可读
if (FD_ISSET(listenfd, &rset)) {
DPRINTF("Start to accept a connection");
//int connfd = Accept(listenfd, NULL, NULL);
//非阻塞方式忽略若干错误
int connfd = accept(listenfd, NULL, NULL);
DPRINTF("connfd %d", connfd);
if (connfd < 0) {
#ifdef EPROTO
DPRINTF("EPROTO");
if (errno == EINTR || errno == EWOULDBLOCK ||
errno == ECONNABORTED || errno == EPROTO) {
#else
if (errno == EINTR || errno == EWOULDBLOCK ||
errno == ECONNABORTED) {
#endif
continue;
} else {
err_sys("accept() failed");
}
}
DPRINTF("Accept a connection");
//设置非阻塞
int flags = Fcntl(connfd, F_GETFL, 0);
Fcntl(connfd, F_SETFL, flags | O_NONBLOCK);
int i = 0;
while (i < FD_SETSIZE) {
if (client[i] < 0) {
client[i] = connfd;
break;
}
++i;
}
if (i == FD_SETSIZE) {
err_quit("Too many client");
}
FD_SET(connfd, &allset);
if (connfd > maxfd) {
maxfd = connfd;
}
if (i > maxi) {
maxi = i;
}
//检测是否还有准备好的连接
if (--nready <= 0) {
continue;
}
} //end if(FD_ISSET)
//循环检测已连接的客户端
DPRINTF("maxi = %d", maxi);
for (int i = 0; i <= maxi; ++i) {
int sockfd = client[i];
if (sockfd < 0) {
continue;
}
DPRINTF("client[]: i %d, sockfd %d", i, sockfd);
if (FD_ISSET(sockfd, &rset)) {
char line[MAXLINE];
int n;
again:
while ((n = read(sockfd, line, MAXLINE)) > 0) {
Write(sockfd, line, n);
}
if (n < 0 && errno == EINTR) {
goto again;
} else if (n < 0 && errno == EWOULDBLOCK) {
;
} else if (n <= 0) { //0: FIN, close by client; -1: RST, error
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
DPRINTF("read n = %d", n);
if (n < 0) {
perror("n < 0");
}
}
}
if (--nready <= 0) {
break;
}
} //end for(0..maxi)
} //end for(;;)
return 0;
}