select方式socket编程记录

select方式简单实现tcp server

/*
 * main.c
 *
 *  Created on: Nov 16, 2019
 *      Author: cust
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#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函数返回值的说明:
    select方式socket编程记录_第1张图片
  • 在断开一个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)

参考

  • https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzab6/xnonblock.htm
    其实就是照着上面的例程抄了一遍,因为觉得眼过数遍,还是没有太深的印象,不要偷懒,自己写一遍,实在想不起来的,看一下教程,感觉会好点。

你可能感兴趣的:(linux,网络,select,socket)