网络学习5

这段代码展示了如何使用IO多路复用函数`select`实现在客户端与服务器端之间的通信。客户端创建套接字,连接服务器,通过`select`监控标准输入和服务器的文件描述符,实现数据的双向传输。服务器端创建套接字,监听指定端口,接受客户端连接,同样使用`select`处理多个客户端连接,接收并回应数据。
摘要由CSDN通过智能技术生成

IO多路复用

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>


#define IP "192.168.0.105"
#define PORT 6666


int main(int argc, const char *argv[])
{
	//创建套接字
	int cfd = socket(AF_INET,SOCK_STREAM,0);
	if(cfd < 0)
	{
		perror("cfd");
		return -1;
	}
	printf("套接字文件描述符cfd=%d\n",cfd);


	//启用端口快速重用
	int optval =1;
	if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)) < 0)
	{
		perror("setsockopt");
		return -1;
	}
	printf("端口快速重用启用成功\n");

	//定义通用地址结构变量填充所需数据
	struct sockaddr_in cin;
	cin.sin_family =AF_INET;
	cin.sin_port =htons(PORT);
	cin.sin_addr.s_addr=inet_addr(IP);
	
	//开始向服务器发送链接数据
	if(connect(cfd,(struct sockaddr*)&cin,sizeof(cin)) == -1)
	{
		perror("connect");
		return -1;
	}
	

	//设置一个读集合(一个用来写入,一个用来执行)
	fd_set readfds,tmpfds;

	//将随机值清空
	FD_ZERO(&readfds);
	FD_ZERO(&tmpfds);

	//添加需要的文件描述符(cfd,0)
	FD_SET(0,&readfds);
	FD_SET(cfd,&readfds);

	//开始发送数据
	char buf[128]="";
	ssize_t res = 0;
	int s_res = 0;
	while(1)
	{
		//重新赋予集合初始值
		tmpfds = readfds;
		printf("请输入信息-----:");
		fflush(stdout);
		s_res = select(cfd+1,&tmpfds,NULL,NULL,NULL);
		if(s_res < 0)
		{
			perror("select");
		}else if( 0 == s_res)
		{
			printf("time out ....\n");
		}
		
		//发送数据程序体
		if(FD_ISSET(0,&tmpfds))
		{
			fgets(buf,sizeof(buf),stdin);
			buf[strlen(buf)-1]=0;
			if(send(cfd,buf,sizeof(buf),0) <0)
			{
				perror("send");
				return -1;
			}
			printf("信息发送成功\n");

		
		}
		
		//接收数据结构体
		if(FD_ISSET(cfd,&tmpfds))
		{
			res = recv(cfd,buf,sizeof(buf),0);
			if(res < 0 )
			{
				perror("res");
				return -1;
			}
			else if(0  == res)
			{
				printf("cfd=%d    服务器下线或者宕机\n",cfd);
				break;
			}
			printf("\ncfd=%d    收到信息:%s\n",cfd,buf);

		
		}
	
	}
			
	//关闭文件描述符
	close(cfd);
	return -1;
}

服务器端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>

#define IP "192.168.0.199"
#define PORT 6666
int main(int argc, const char *argv[])
{
	//创建TCP套接字文件描述符
	int sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd <0 )
	{
		perror("socket");
		return -1;
	}
	printf("套接字文件描述符sfd=%d\n",sfd);

	//启用端口快速重用
/*  SO_DEBUG 打开或关闭排错模式
	SO_REUSEADDR 允许在bind()过程中本地地址可重复使用
	SO_TYPE 返回socket形态。
	SO_ERROR 返回socket已发生的错误原因
	SO_DONTROUTE 送出的数据包不要利用路由设备来传输。
	SO_BROADCAST 使用广播方式传送
	SO_SNDBUF 设置送出的暂存区大小
	SO_RCVBUF 设置接收的暂存区大小
	SO_KEEPALIVE 定期确定连线是否已终止。
	SO_OOBINLINE 当接收到OOB 数据时会马上送至标准输入设备
	SO_LINGER 确保数据安全且可靠的传送出去。*/
	int optval =1;
	if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval))<0)//成功返回0;失败返回-1
	{
		perror("setsockopt");
		return -1;
	}
	printf("端口快速重用启用成功\n");

	//定义通用地址结构变量
	struct sockaddr_in sin;
	//填充结构体
	sin.sin_family =AF_INET;//IPV4
	sin.sin_port =htons(PORT);//端口号(将主机字节序转到网络字节序然后赋值给通用地址结构体)
	sin.sin_addr.s_addr=inet_addr(IP);//IP字符地址(将主机字节序转到网络字节序然后赋值给通用地址结构体)

	//将IP和端口号绑定在套接字上
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
	{
		perror("bind");
		return -1;
	}
	printf("绑定成功\n");	

	//将填入目标套接字设置为监听状态
	if(listen(sfd,128) == -1)
	{
		perror("listen");
		return -1;
	}
	printf("监听成功\n");

	//存储链接成功的客户端地址信息
	struct sockaddr_in cin;
	socklen_t size_cin = sizeof(cin);

	//设置一个读集合、
	fd_set readfds,tmpfds;

	//将readfds 及tmpfds中的随机值清空
	FD_ZERO(&readfds);
	FD_ZERO(&tmpfds);

	//添加需要的文件描述符
	FD_SET(0,&readfds);
	FD_SET(sfd,&readfds);

	//最大的文件描述符
	int maxfd = sfd;
	
	char buf[128]="";
	int newfd =-1;

	//接收recv的返回值
	int s_res = 0;
	ssize_t res =0;
	

	//存储程序中被覆盖的地址信息
	struct sockaddr_in save_cin[1024-4];

	while(1)
	{
		//每次循环开头都将集合中的值重新赋值
		tmpfds = readfds;

		//定义多路复用函数
		s_res = select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		if(s_res < 0)
		{
			perror("select");
			return -1;
		}
		else if(0 == s_res)
		{
			printf("time out ...\n");
			return -1;
		}


		//在所有的文件符遍历符合符合要求的文件描述符
		for(int i = 0;i<=maxfd;i++)
		{
			if( !FD_ISSET(i,&tmpfds))//如果不在集合中,则直接继续向后遍历
				continue;

			//能运行到这说明i存在在集合中
			if( 0 == i)	
			{
				int sndfd = -1;
				printf("键盘触发事件----");
				res = scanf("%d %s",&sndfd,buf);
				while(getchar()!=10 );
				if(res != 2)
				{
					perror("请输入正确的格式\n");
					continue;
				}

				if(sndfd <= sfd || sndfd >=1024 || !FD_ISSET(sndfd,&readfds))
				{
					fprintf(stderr,"sndfd=%d 文件描述符错误\n",sndfd);
					continue;
				
				}
				if(send(sndfd,buf,sizeof(buf),0) < 0)
				{
					perror("send");
					return -1;
				
				}
			}	
			else if(sfd == i)
			{
				printf("客户端触发事件----\n");
			//	fflush(stdout);
				newfd = accept(sfd,(struct sockaddr*)&cin,&size_cin);
				if(-1 ==newfd )
				{
					perror("accept");
					return -1;
				}
				
				printf("[地址:%s 端口:%d]  newfd:%d  客户链接服务器成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
				save_cin[newfd-4]=cin;

				//将newfd添加到读集合中
				FD_SET(newfd,&readfds);

				//更新maxfd
				maxfd = maxfd>newfd ? maxfd:newfd;
			}
			else
			{
				bzero(buf,sizeof(buf));
				res = recvfrom(i,buf,sizeof(buf),0,(struct sockaddr*)&cin,&size_cin);
				if(res < 0)
				{
					perror("recv");
					return -1;
				}
				else if(0 == res)
				{
					printf("[地址:%s 端口:%d]  newfd:%d  客户下线\n",inet_ntoa(save_cin[i-4].sin_addr),ntohs(save_cin[i-4].sin_port),newfd);
					//关闭文件描述符
					close(i);
					//从集合中剔除该文件描述符
					FD_CLR(i,&readfds);
					//更新maxfd
					//从目前最大的文描述符往最小的判断
					int j =0;
					for(j=maxfd;j >=0;j--)
					{
						if(FD_ISSET(j,&readfds))
						{
							maxfd =j;
							break;
						}
					}
					if(j < 0)//若集合中没有要监测的文件描述符了,则让maxfd = -1
						maxfd = -1;
					continue;
				}
				printf("[地址:%s 端口:%d]  newfd:%d  send:%s \n",inet_ntoa(save_cin[i-4].sin_addr),ntohs(save_cin[i-4].sin_port),newfd,buf);
				
				//发送信息
				strcat(buf,"--- ");//改变信息
				if(send(i,buf,sizeof(buf),0)<0)//成功发送返回字节数,失败返回-1
				{
					perror("send");
					return -1;
				}
				printf("回执信息发送成功\n");


			}
		
		
		}
	}


/*
	while(1)
	{
		//读取执行体
		//bzero(temp,sizeof(temp));
		res = recv(newfd,temp,sizeof(temp),0);//以非阻塞形式
		
		//发送执行体
		}
*/

	//关闭打开的文件描述符
	close(sfd);
	close(newfd);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值