select方式socket編程記錄

select方式簡單實現tcp server

/*
 * main.c
 *
 *  Created on: Nov 16, 2019
 *      Author: cust
 */
#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.0.111", (struct sockaddr *)&server_addr.sin_addr);
	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);
		//timeout
		if(0 == ret){
			perror("select timeout:");
			exit(0);
		}
		//error
		else if(-1 == ret){
			perror("select error:");
			exit(0);
		}
		//
		else{
			for(i=0;i<=FD_SETSIZE;i++){
				if(FD_ISSET(i, &working_set)){
					//new connect request
					if(i == server_sd){
//						client_sd=accept(i, (struct sockaddr *)&new_addr, sizeof(new_addr));
						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");
					}
					//new data from already connected socket
					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)

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章