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)
參考