Linux网络编程-select实现多点连接的回射

本文介绍了一个简易的聊天服务器实现,使用C语言编写,基于TCP协议,通过select机制处理多个客户端连接。服务器可以接受多个客户端的连接,并能将客户端发送的消息回传给发送者。

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

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>

#define MAX_LOGIN 20     //设置客户端连接的最大数量
#define BUFLEN 1024      //设置缓存的大小
#define PORT 5000        //设置电脑端口

ssize_t readn(int fd,void *buf,size_t count);
ssize_t writen(int fd,void *buf,size_t count);
void ehandle(char *mesg);

int main()
{
	int listenfd,connfd;
	struct sockaddr_in seraddr,cliaddr;
	int addrlen = 0;
	int nread = 0;
	char readbuf[BUFLEN];
	int opt = 1;
	fd_set rfds,readfds;
	int maxfd,nready,i;
	int clifd[FD_SETSIZE];
	
	if(-1 == (listenfd = socket(AF_INET,SOCK_STREAM,0)))   //生成监听套接字
	  ehandle("socket");

	bzero(&seraddr,sizeof(struct sockaddr_in));     //清零
	seraddr.sin_family = AF_INET;                   //选择IPv4协议
	seraddr.sin_port = htons(PORT);                  //选择5000端口
	seraddr.sin_addr.s_addr = htonl(INADDR_ANY);     //选择IP

	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));  //端口重用,避免因为端口处于time_wait状态而不能够bind成功(同一端口)

	if(-1 == bind(listenfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr))) //端口和套接字绑定
	  ehandle("bind");

	if(-1 == listen(listenfd,MAX_LOGIN))    //开始监听
	  ehandle("listen");

	
	for(i = 0;i < FD_SETSIZE-1;i++)          //用一组数组保存客户端套接字,FD_SETSIZE为select所能监听的最大数量,监听套接字占了一个
	{
		clifd[i] = -1;
	}
	FD_ZERO(&rfds);
	FD_ZERO(&readfds);
	FD_SET(listenfd,&readfds);
	maxfd = listenfd;

	while(1)
	{
		rfds = readfds;               //保险
		do
		{
			nready = select(maxfd +1,&rfds,NULL,NULL,NULL);
		}while(nready < 0 && EINTR == errno);  //避免因为其他中断引起select异常
		if(-1 == nready)
		  ehandle("select");
		
		if(FD_ISSET(listenfd,&rfds))             //处理监听到的客户端套接字
		{
			addrlen = sizeof(struct sockaddr);
			if(-1 == (connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&addrlen)))
			  ehandle("accept");
			
			printf("Connect IP:%s;PORT :%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

			for(i = 0;i < FD_SETSIZE;i++)
			{
				if(clifd[i] < 0)              //找到一个空的组员
				{
					clifd[i] = connfd;     //存储新的客户端套接字
					FD_SET(connfd,&readfds); //加入select监听序列
					if(connfd > maxfd)       //更新maxfd
					  maxfd = connfd;
					break;
				}
			}

			if(--nready <=0)                 //nready 减一,优化程序
			  continue;
		}

		for(i = 0;i <FD_SETSIZE;i++)           //循环查找被激活的客户端套接字,处理客户端的请求
		{
			if(clifd[i] > 0)
			  if(FD_ISSET(clifd[i],&rfds))  //因为用了两个集合,rfds和readfds,而这里一定要用rfds,错了就会只能与第一个链接成功的客户端通信。
			  {
				  memset(readbuf,0,sizeof(readbuf));  //清空缓存
				  connfd = clifd[i];
				  nread = readn(connfd,readbuf,sizeof(readbuf)); //读取客户端发来的字符串
				  if(-1 == nread)
					ehandle("readn");
				  else if(0 == nread)                         //客户端关闭处理
				  {
					  printf("client closed!\n");
					  clifd[i] = -1;                    //清空该组员,以便能够存储新的客户端套接字
					  FD_CLR(connfd,&readfds);          //从select监听序列中清除
					  close(connfd);                    //关闭该客户端套接字
				  }
				  else                                      //正常处理
				  {
					  printf("Receive %s",readbuf);
					  if(-1 == writen(connfd,readbuf,sizeof(readbuf)))   //把收到的字符串回射给客户端
						ehandle("writen");
				  }

				  if(--nready <= 0)                           //nready 减一,如果不为零,则继续处理其他的,同时被激活的客户端
					break;
			  }
		}
	}
	close(listenfd);
	return 0;
}

void ehandle(char *mesg)   //错误处理
{
	perror(mesg);
	exit(1);
}

ssize_t readn(int fd,void *buf,size_t count)               //  读取固定字节数函数,避免粘包问题,这种处理方式效率不高
{
	unsigned int nleft = count;
	int nread = 0;
	char *pbuf = (char*)buf;

	while(nleft > 0)
	{
		nread = read(fd,pbuf,count);
		if(-1 == nread)
		{
			if(EINTR == errno)
			  continue;
			return -1;
		}
		else if(0 == nread)
		  break;

		nleft -= nread;
		pbuf += nread;
	}
	return (count - nleft);
}

ssize_t writen(int fd,void *buf,size_t count)     //写入固定字节数函数,避免粘包问题,这种处理方式效率不高

{
	unsigned int nleft = count;
	int nwrite = 0;
	char *pbuf = (char *)buf;

	while(nleft > 0)
	{
		nwrite = write(fd,pbuf,count);
		if(-1 == nwrite)
		{
			if(EINTR == errno)
			  continue;
			return -1;
		}
		else if(0 == nwrite)
		  continue;

		nleft -= nwrite;
		pbuf += nwrite;
	}
	return (count - nleft);
}
client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>

#define PORT 5000
#define IP "127.0.0.1"
#define BUFLEN 1024

void ehandle(char *mesg)
{
	perror(mesg);
	exit(1);
}

ssize_t readn(int fd,void* buf,size_t count)
{
	unsigned int nleft = count;
	int nread = 0;
	char *pbuf = (char *)buf;

	while(nleft > 0)
	{
		nread = read(fd,pbuf,count);
		if(-1 == nread)
		{
			if(EINTR == errno)
			  continue;
			return -1;
		}
		else if (0 == nread)
		  break;

		nleft -= nread;
		pbuf += nread;
	}

	return (count -nleft);
}

ssize_t writen(int fd,void *buf,size_t count)
{
	unsigned int nleft = count;
	int nwrite = 0;
	char *pbuf = (char *)buf;

	while(nleft > 0)
	{
		nwrite = write(fd,pbuf,count);
		if(-1 == nwrite)
		{
			if(EINTR == errno)
			  continue;
			return -1;
		}
		else if(0 == nwrite)
		  continue;

		nleft -= nwrite;
		pbuf += nwrite;
	}
	return (count - nleft);
}

int main()
{
	int myfd;
	struct sockaddr_in seraddr;
	char strbuf[BUFLEN];
	int nread,nwrite;
	int maxfd;
	int nready = 0;
	fd_set readfds;

	if(-1 == (myfd = socket(AF_INET,SOCK_STREAM,0)))
	  ehandle("socket");
	
	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(PORT);
//	seraddr.sin_addr.s_addr = inet_addr(IP);
	inet_pton(AF_INET,IP,&seraddr.sin_addr,sizeof(struct in_addr));

	if(-1 == connect(myfd,(struct sockaddr*)&seraddr,sizeof(struct sockaddr)))
	  ehandle("connect");

	FD_ZERO(&readfds);
	while(1)
	{
		FD_SET(STDIN_FILENO,&readfds);
		FD_SET(myfd,&readfds);
		maxfd = myfd;
		nready = select(maxfd + 1,&readfds,NULL,NULL,NULL);

		if(-1 == nready)
		  ehandle("select");
		else if(0 == nready)
		  continue;
		else
		{
			if(FD_ISSET(STDIN_FILENO,&readfds))
			{
				memset(strbuf,0,BUFLEN);
				fgets(strbuf,BUFLEN,stdin);
				nwrite = writen(myfd,strbuf,BUFLEN);
				if(-1 == nwrite)
				  ehandle("writen");
			}
			if(FD_ISSET(myfd,&readfds))
			{
				memset(strbuf,0,BUFLEN);
				nread = readn(myfd,strbuf,BUFLEN);
				if(-1 == nread)
				  ehandle("readn");
				else if(BUFLEN != nread)
				{
					printf("Server ending!\n");
					break;
				}
				printf("Receive :%s",strbuf);
			}
		}
	}
/*
	while(1)
	{
		memset(strbuf,0,BUFLEN);
		fgets(strbuf,BUFLEN,stdin);
		nwrite = writen(myfd,strbuf,BUFLEN);
		if(-1 == nwrite)
		  ehandle("writen");

		memset(strbuf,0,BUFLEN);
		nread = readn(myfd,strbuf,BUFLEN);
		if(-1 == nread)
		  ehandle("readn");
		else if(BUFLEN != nread)
		{
			printf("server ending!\n");
			break;
		}

		printf("Receive :%s\n",strbuf);

	}
*/	
	close(myfd);
	return 0;
}


PS:

1 在debian 7上进行过简单测试,成功。

2 如果客户端或者服务器端用readn 和writen函数,则服务器端或客户端也必须用这个两个函数,否则不能正常回射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值