select方式简单实现tcp server
#include <sys/select.h>
#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <stdbool.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define TRUE 1
#define FALSE 0
int main(int argc, char *argv[])
{
struct timeval rtv;
fd_set master_set,working_set;
int server_sd,client_sd,max_sd;
struct sockaddr_in server_addr;
struct sockaddr_in new_addr;
int ret=0;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(12345);
inet_pton(AF_INET, "192.168.99.111", (struct sockaddr *)&server_addr.sin_addr);
rtv.tv_sec=1*60;
rtv.tv_usec=0;
server_sd=socket(AF_INET, SOCK_STREAM, 0);
if(-1 == server_sd){
perror("socket error:");
exit(1);
}
FD_ZERO(&master_set);
FD_SET(server_sd, &master_set);
printf("create socket...\n");
max_sd=server_sd;
ret=bind(server_sd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(-1 == ret){
perror("bind error:");
exit(1);
}
printf("bind socket...\n");
ret=listen(server_sd, 3);
if(-1 == ret){
perror("listen error:");
exit(1);
}
printf("listen socket...\n");
int i=0;
bool sd_need_close=false;
while(TRUE){
memcpy(&working_set, &master_set, sizeof(fd_set));
printf("working_set:0X%X\n", master_set);
ret=select(max_sd+1, &working_set, 0, 0, &rtv);
if(0 == ret){
perror("select timeout:");
exit(0);
}
else if(-1 == ret){
perror("select error:");
exit(0);
}
else{
for(i=0;i<=FD_SETSIZE;i++){
if(FD_ISSET(i, &working_set)){
if(i == server_sd){
client_sd=accept(i, NULL, NULL);
if(-1 == client_sd){
perror("accept error:");
exit(1);
}
if(client_sd>max_sd){
max_sd=client_sd;
}
FD_SET(client_sd, &master_set);
printf("accepted new socket...\n");
}
else{
char rbuf[100]={0};
ret = recv(i, rbuf, sizeof(rbuf), 0);
if(ret<0){
perror("recv error:");
}else if(0 == ret){
perror("socket seem to be closed!:");
sd_need_close=true;
}else{
printf("recv data:%s\n", rbuf);
ret=send(i, rbuf, ret, 0);
if(ret<0){
perror("send error:");
sd_need_close=true;
}
}
if(sd_need_close){
sd_need_close=false;
close(i);
FD_CLR(i, &master_set);
if(i == max_sd){
while (FD_ISSET(max_sd, &master_set) == FALSE){
max_sd -= 1;
}
}
}
}
}
}
}
}
return 0;
}
思路
- 创建、绑定、监听服务器socket,以下简称server_sd
- 初始化select读操作(还有写操作和错误,暂时用不到)fd_set变量、将server_sd添加到fd_set变量中,接下来执行大循环
- 通过select以阻塞/非阻塞/倒计时方式等待可读事件到来,再通过判断当前socket描述符是否为服务器监听socket
- 若为服务器监听socket,则表示可读事件来自一个客户端连接请求,执行accept函数,建立tcp连接,并将返回的客户端socket通过FD_SET函数添加到fd_set变量中
- 其他则为已经建立连接的客户端socket,表示收到此客户端发送的数据,执行recv函数,读取数据即可。当recv函数返回0时,表示对应的客户端断开了连接,服务器这边相应要做一些处理。
说明
- 在recv函数返回0时,说明此socket已经被客户端断开,我们要调用close函数关闭,下面是关于recv函数返回值的说明:

- 在断开一个socket后,同时要将fd_set变量中对应的位清除,此外,我们还要判断当前断开的这个socket的描述符,是否为当前最大描述符——因为select函数会0开始遍历,直到最大描述符+1,如果当前关闭的socket的描述符为最大描述符,应该将max_sd值调整为下一个最大的描述符,避免多余的循环次数;方法——从当前最大描述符循环递减,通过FD_ISSET测试master_set中下一个被置位的是bit几,即为新的最大描述符,这里写的有点狗屁不通,难以理解,将就配合这段代码理解下吧:
if(sd_need_close){
sd_need_close=false;
close(i);
FD_CLR(i, &master_set);
if(i == max_sd){
while (FD_ISSET(max_sd, &master_set) == FALSE){
max_sd -= 1;
}
}
}
补充
- 为什么select函数第一个参数是最大描述符+1?
——描述符是从0开始(0-stdin、1-stdout、2-stderr),可以这么理解:第一个参数告诉select函数从0开始遍历,一共遍历多少次,所以要+1(下标索引和执行次数总是+1、-1的关系,for和while操作数组时经常会遇到),比如fd_set master_set;...(master_set)0b=(0010_0000)0b
,此时描述符为5(bit5=1),从bit0循环到bit5需要6次,所以应该执行select(5+1, &master_set, NULL, NULL, NULL)
参考