linux下使用select函数创建单线程tcp服务器

一、select函数

1.1、select函数的作用

  • select函数允许程序监听多个文件描述符,直到其中一个文件描述符处于就绪状态(有数据可读或可写)。
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
头文件:  #include <sys/select.h>
         #include <sys/time.h>
        #include <sys/types.h>
        #include <unistd.h>

  • select参数说明
  1. nfds表示最大文件描述符的值加一,
  2. readfds表示读文件描述符的集合,
  3. readfds表示写文件描述符的集合
  4. exceptfds表示监视的异常文件句柄集合,
  5. timeout表示超时时间,如果为NULL表示永久等待;
    需要注意的是,select函数会逐渐减少timeout的值以记录剩余时间,因此每次调用select函数需要重新初始化超时时间。
  • 返回值

    成功返回对应的文件描述符,超时返回0,失败返回-1。
  • 注意事项

    在调用select函数之后,select会将文件描述符集合中没有数据到来的文件描述符清除掉,只保留有数据的文件描述符在集合中,因此每次调用select之前必须重新调用FD_CLR和FD_SET重新对文件描述符集合进行初始化。

1.2、select函数需要和以下函数组合使用

  • FD_SET() 将文件描述符fd放到文件描述符集合set中
  • FD_ISSET() 判断指定的文件描述符fd是否存在于set中
  • FD_CLR()将文件描述符fd从件描述符集合set中清除
  • FD_ZERO()初始化文件描述符集合,在文件描述符集合创建之后,其初始值是未知的,必须进行初始化
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);

二、使用select创建TCP服务器,监听多个客户端

#include <unistd.h>
#include <pthread.h>
#include  <netdb.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>

#define MAX_CLIENT_FD 100
int g_client_fd[MAX_CLIENT_FD] = {-1}; 
#define SERVER_PORT 8089
#define MAX_LISTEN_CLIENT_NUM 10
pthread_t g_server_tid;
pthread_t g_client_tid;



void *tcp_server(void *param)
{
	int ret = -1;
	int i=0;
	int sockfd = -1;
	int select_fd = -1;
	int client_fd = -1;
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	struct hostent *ip;
	char buffer[1024];
    struct timeval time_out;
	fd_set server_fd_set;
	int max_fd = 0;
	
	FD_ZERO(&server_fd_set);
	memset(&server_addr, 0, sizeof(struct sockaddr_in));
	server_addr.sin_family = AF_INET; //使用IPV4
	server_addr.sin_port = htons(SERVER_PORT);//服务器端口号
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//主机可能有多个网卡,INADDR_ANY表示绑定所有网卡
	sockfd = socket(AF_INET,SOCK_STREAM,0);//创建TCP,指定ip类型为IPV4
	printf("create socket %d\r\n",sockfd);
	if(sockfd<0)
	{
		printf("socket create error\r\n");
	}

	 time_out.tv_sec = 60;
     time_out.tv_usec = 0;
#if 0
	 if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &recv_time, sizeof(recv_time))<0)
   	{
        printf("failed to set socket receiving timeout!\r\n");
	}
#endif
	ret = bind(sockfd,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));//将socket和服务器地址绑定
	if(0 != ret)
	{
		printf("server bind error\r\n");
		goto exit;
	}
	ret = listen(sockfd,MAX_LISTEN_CLIENT_NUM);//开启监听服务
	if(0 != ret)
	{
		printf("listen error\r\n");
		goto exit;
	}
	printf("create server sucess!\r\n");
	socklen_t addr_len = sizeof(struct sockaddr);
	max_fd = sockfd;
	while(1)
	{
		max_fd = client_fd > sockfd?client_fd:sockfd;
	    time_out.tv_sec = 60;//select会更改timeout的值,所以需要重新初始化超时时间
        time_out.tv_usec = 0;
		FD_ZERO(&server_fd_set);
        FD_SET(sockfd,&server_fd_set);
		for(i=0;i<MAX_CLIENT_FD;i++)
		{
			if(g_client_fd[i]>0)
			{
				FD_SET(g_client_fd[i],&server_fd_set);
			}
		}
		
		printf("maxfd:%d\r\n",max_fd);
		select_fd = select(max_fd+1,&server_fd_set,NULL,NULL,&time_out);
		printf("select fd:%d\r\n",select_fd);
        
		switch(select_fd)
		{
			case 0:printf("select timeout\r\n");break;
			case -1:printf("error occured\r\n");break;
			default:
			{
				if(FD_ISSET(sockfd,&server_fd_set))
				{
					client_fd = accept(sockfd,(struct sockaddr*)&client_addr,&addr_len);
					if(client_fd >= 0)
					{
						printf("new client fd = %d\r\n",client_fd); 
						for(i=0;i<MAX_CLIENT_FD;i++)
						{
							if(g_client_fd[i] == -1)
							{
								g_client_fd[i] = client_fd;
								break;
							}
						}
					}
					else
					{
						printf("accept error\r\n");
					}
				}
				else
				{
					for(i=0;i<MAX_CLIENT_FD;i++)
					{
						if(FD_ISSET(g_client_fd[i],&server_fd_set))
						{
							memset(buffer,0,sizeof(buffer));
							ret = recv(g_client_fd[i],buffer,sizeof(buffer), 0);//最后一个参数为0,表示默认阻塞接收,前面select解除了阻塞说明有数据可读
							if(ret > 0)
							{
								printf("recv(%d):%s",ret,buffer);
								fflush(stdout);
								send(g_client_fd[i],buffer,ret,0);
							}
							else
							{
								printf("client disconnected\r\n");
								g_client_fd[i]  = 0;
								close(g_client_fd[i]);
							}
						}
					}
				}
				
			}break;
			
		}


	}
exit:
    close(sockfd);
    return NULL;	
}
void main(int argc,char *argv[])
{
	int ret = -1;

	ret = pthread_create(&g_server_tid,NULL,&tcp_server,NULL);
	if(0 != ret)
	{
		printf("tcp client create error\r\n");
	}
	pthread_join(g_server_tid,NULL);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值