1. select函数
该函数默认是阻塞的,在网络编程中要么阻塞在select/poll/epoll上, 要么阻塞在accept/recv等IO函数上。
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, 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);
在linux中,fd的增长是连续的(除0,1,2外),所以利用对fd_set的bit位的设置,来确定想要的关注的事件(读事件,写事件和错误事件);
关于fd_set 在内核 include/uapi/linux/posix_types.h 中被定义:
单个进程下 select 监控的最大文件描述符个数是 1024 .
但是可以创建多个select以达到支持多连接的目的,但是在增加内存和增加机器性能的前提下,select 模型还是有无法突破 C10K 的问题。
当 FD_SET(listenfd, &rset)设置后, select函数就把要关注的 fd_set 集合,拷贝到内核中,然后等待内核准备就绪数据. 当内核中有可读、可写、出错事件后,内核就会准备就绪事件集合 fd_set,然后从内核拷贝到用户态,程序收到 fd_set 集合后做进一步的处理。
参数:
nfds:是当前fd最大值+1
readfds: 当前关注的读事件的集合
writefds: 当前关注的写事件的集合
exceptfds: 当前关注的错误/异常事件的集合
struct timeval *timeou: 超时时间的设置
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
timeout 是 NULL, 则select函数在没有读、写、异常事件时时阻塞的,一直等待;
timeout的值为0,: 没有事件不等待,立即返回;
timeout不为0: 等待一定时间后返回;
返回值:
-1: 失败
0:超时
>0: 返回就绪的个数
fd 辅助函数:
void FD_CLR(int fd, fd_set *set); //从集合中删除fd
int FD_ISSET(int fd, fd_set *set); //查看fd在集合中是否被设置
void FD_SET(int fd, fd_set *set); //fd 加入集合中
void FD_ZERO(fd_set *set); //清空集合
测试程序:
int main(int argc, char **argv)
{
int listenfd, connfd, n;
struct sockaddr_in servaddr;
char buff[MAXLNE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
int reuse = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
return -1;
}
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
fd_set rfds, rset, wfds, wset;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(listenfd, &rfds);
int max_fds = listenfd;
while (1) {
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
rset = rfds;
wset = wfds;
/* int nready = select(max_fds+1, &rset, NULL, NULL, NULL); */
int nready = select(max_fds+1, &rset, &wset, NULL, &timeout);
if (0 == nready) {
continue;
}
else if (nready < 0) {
return -1;
}
if (FD_ISSET(listenfd, &rset)) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
FD_SET(connfd, &rfds);
if (max_fds < connfd) {
max_fds = connfd;
}
if (--nready == 0) continue;
}
int i = 0;
for (i = listenfd+1; i <= max_fds; i++) {
if (FD_ISSET(i, &rset)) {
n = recv(i, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
/* send(i, buff, n, 0); */
FD_SET(i, &wfds);
}
else if (n == 0) {
FD_CLR(i, &rfds);
printf("disconnect. \n");
close(i);
}
if (--nready == 0) break;
}
else if (FD_ISSET(i, &wset)) {
send(i, buff, n, 0);
FD_SET(i, &rfds);
}
}
}
close(listenfd);
return 0;
}