基于多路复用select和套接字非阻塞实现tcp server处理多个客户端

本文介绍了一种基于select和非阻塞套接字的TCP服务器实现方式,该方法允许服务器同时处理多个客户端连接,避免了因单个连接阻塞而导致的服务不可用问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为了实现tcp服务器能够同时处理多个服务器,除了使用多进程、多线程的方法以外,还可以借助select和设置套接字为NONBLOCK,以防止服务器阻塞在某个函数上。

代码如下

头文件

#ifndef _TCP_SELECT_H_
#define _TCP_SELECT_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h> //文件io read write close
#include <netinet/in.h>//socket() struct sockaddr_in  htonl htons 
#include <netinet/ip.h>//socket()
#include <arpa/inet.h>//inetaddr() inet_pton()
#include <sys/time.h>
#include <errno.h> 
#include <fcntl.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.152.130"
typedef int data_t;
typedef struct node_t
{
	data_t data;
	struct node_t * next;
}linknode_t, * linklist_t;
extern int FD_SET_LINKLIST(linklist_t H,fd_set * prset);
extern int LINKLIST_INSERT(linklist_t H,int newfd);
extern void LINKLIST_DESTORY(linklist_t H);
extern int find_maxfd(linklist_t H);
#endif

服务器端:使用链表对已经建立连接的套接字进行维护

#include "tcp_select.h"
int main()
{
	int fd = -1;
	//创建socket
	fd = socket(AF_INET,SOCK_STREAM,0);//流式套接字-TCP
	//设置套接字为非阻塞
	int flags = fcntl(fd,F_GETFL,0);
	fcntl(fd,F_SETFL,flags|O_NONBLOCK);
	if(fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2绑定socket
	//2.1填充struct sockaddr_in
	struct sockaddr_in sin;
	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 1 
	sin.sin_addr.s_addr = htonl(INADDR_ANY);//代替某个IP inet_addr(SERV_IP_ADDR) 让服务器能绑定在任意IP
#else
	if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) != 1)
	{
		perror("inet_pton");
		exit(-1);
	}
#endif
	//2.2开始绑定socket就是给fd带上属性
	if(bind(fd,(const struct sockaddr *)&sin,sizeof(sin)) < 0)
	{
		perror("bind");
		exit(-1);
	}
	//3 调用listen()将主动套接字变成被动套接字
	if(listen(fd,5) == -1)
	{
		perror("listen");
		exit(-1);
	}
	//链表存储已经建立连接的newfd
	linklist_t H;
	if((H = (linklist_t)malloc(sizeof(linknode_t))) == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	H->next = NULL;
	char buf[BUFSIZ];
	bzero(buf,sizeof(buf));
	int ret;
	fd_set rset;
	struct timeval tout;
	int maxfd = -1;
	int newfd = -1;
	struct sockaddr_in cin;
	socklen_t addrlen = sizeof(cin);
	linklist_t p = H;
	linklist_t q;
	char  ipv4_addr[16];
	int res;
	while(1)
	{
		//IO复用集合预处理
		maxfd = find_maxfd(H);
		FD_ZERO(&rset);
		FD_SET_LINKLIST(H,&rset);
		tout.tv_sec = 0;
		tout.tv_usec = 0;
		res = select(maxfd+1, &rset,NULL,NULL,&tout);
		if(res == -1)
		{
			perror("select");
			exit(-1);
		}
		if (res == 0)
		{
			printf("no socket ready to read\n");
		}
		p = H;
		while(p->next)
		{
			if(FD_ISSET(p->next->data,&rset))
			{
				do
				{
					bzero(buf,sizeof(buf));
					ret = read(p->next->data,buf,BUFSIZ-1);
					puts("test");
				}while(ret < 0 && EINTR == errno);
				if(ret < 0 )
				{
					printf("fd=%d: read failed\n",p->next->data);
					p = p->next;
					continue;
				}
				if(strncmp(buf,"quit\n",4) == 0 || !ret)
				{
					printf("client(fd=%d) is exiting\n",p->next->data);
					FD_CLR(p->next->data,&rset);
					q = p->next;
					p->next = p->next->next;
					free(q);
					continue;
				}
				write(p->next->data,buf,BUFSIZ);
			}
			p = p->next;
		}
		newfd = accept(fd,(struct sockaddr *)&cin,&addrlen);
		if (newfd == -1 && errno == EAGAIN) 
		{
			puts("no client connections yet");
			continue;
		}
		else if(newfd == -1)
		{
			perror("accept");
			exit(-1);
		}
		if(!inet_ntop(AF_INET,(void *)&cin.sin_addr.s_addr,ipv4_addr,sizeof(cin)))
		{
			perror("inet_ntop");
			exit(-1);
		}
		if(newfd > 0 )
		{
			if(LINKLIST_INSERT(H,newfd) == -1)
				printf("client(%s:%d)(fd=%d) failed to connect\n",ipv4_addr,ntohs(cin.sin_port),newfd);
			else
				printf("client(%s:%d)(fd=%d) is connected\n",ipv4_addr,ntohs(cin.sin_port),newfd);
		}
	}
	close(newfd);
	close(fd);
	LINKLIST_DESTORY(H);
	return 0;

#if 0
		//5通过newfd进行cs通信
		char buf[BUFSIZ];
		bzero(buf,BUFSIZ);
		int ret;
		FILE * fp;
		fp = fopen("./readme.txt","a+");
		while(1)
		{

			while((ret = read(newfd,buf,BUFSIZ)) > 0)
			{
				if(strncasecmp(buf,"quit\n",4) == 0)
				{
					printf("client is exiting\n");
					goto _exit;
				}	
				fputs(buf,fp);
			}

			if(ret == 0 )
			{
				break;
			}
			else
			{
				perror("read");
				break;
			}

		}
_exit:
		//6关闭套接子
		close(newfd);
		close(fd);
		fclose(fp);	
		return 0;
#endif

	}

int FD_SET_LINKLIST(linklist_t H,fd_set * prset)
{
	linklist_t p = H;
	while(p->next)
	{
		FD_SET(p->next->data,prset);
		p = p->next;
	}
	return 0;
}

int LINKLIST_INSERT(linklist_t H,int newfd)
{
	linklist_t p ;
	if((p = (linklist_t)malloc(sizeof(linknode_t))) == NULL)
	{
		perror("malloc");
		return -1;
	}
	//链表头部插入
	p->data = newfd;
	p->next = H->next;
	H->next = p;
	return 0;
}

void LINKLIST_DESTORY(linklist_t H)
{
	linklist_t p;
	while(H)
	{
		p = H;
		H = p->next;
		free(p);
	}

}

int find_maxfd(linklist_t H)
{
	linklist_t p = H->next;
	int maxfd = 0;
	for(;p != NULL;p = p->next)
	{
		if(p->data > maxfd)
			maxfd = p->data;
	}
	return maxfd;

}

客户端

#include "tcp_select.h"
int main()
{
	int fd = -1;
	//创建socket
	fd = socket(AF_INET,SOCK_STREAM,0);//流式套接字-TCP
	if(fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2 connect()申请连接
	//填充sockaddr_in()结构体
	struct sockaddr_in sin;
	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 0 
	sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
	if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr.s_addr) != 1)
	{
		perror("inet_pton");
		exit(-1);
	}
#endif
	//申请连接
	if((connect(fd,(const struct sockaddr *)&sin,sizeof(sin))) < 0)
	{
		perror("connect");
		exit(-1);
	}
	//
	char buf[BUFSIZ];
	fd_set rset;
	int maxfd = fd;
	int ret;
	struct timeval tout;
	while(1)
	{
		//对集合进行预操作
		FD_ZERO(&rset);
		FD_SET(fd,&rset);//将套接字加入到IO复用集合中
		FD_SET(0,&rset);//将标准输入流加入到io复用集合中
		tout.tv_sec = 5;//填充struct timeval 结构体
		tout.tv_usec = 0;

		//使用select进行多路IO选择
		select(maxfd+1, &rset,NULL,NULL,&tout);
		//当标准输入流有输入时进行处理
		if(FD_ISSET(0,&rset))
		{
			do
			{
				ret = read(0,buf,BUFSIZ);
			}while(ret < 0 && EINTR == errno);//工程写法
			if(ret < 0 )
			{
				perror("read");
				continue;
			}
			if(!ret)
				continue;
#if 0
		if(fgets(buf,BUFSIZ,stdin) == NULL)
			{ 
				continue;
			}
#endif
			if(write(fd,buf,BUFSIZ) < 0 )
			{
				perror("write() to socket");
				continue;
			}
			if(!strncasecmp(buf,"quit\n",4))
			{
				printf("client is exiting\n");
				break;
			}
		}

		if(FD_ISSET(fd,&rset))
		{
			
			do
			{
				bzero(buf,sizeof(buf));
				ret = read(fd,buf,BUFSIZ);
			}while(ret < 0 && EINTR == errno);//工程写法
			if(ret < 0 )
			{
				perror("read");
				continue;
			}
			if(!ret)//服务器被关闭
			{
				printf("server is shutdown\n");
				break;
			}
			fprintf(stdout,"server: %s\n",buf);
		}
	}
	//关闭套接字
	close(fd);

	return 0;
}

参考链接

http://velep.com/archives/1137.html

https://blog.youkuaiyun.com/u014453898/article/details/53888800



                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值