2020-08-21 多路复用TCP双向通信

                     多路复用TCP双向通信

首先说一下多路复用

         多路的作用:监测文件描述符的状态变化(监测文件描述符是否有数据可读写)
                               状态变化: 有数据可读,有数据可写,异常

     有可能需要监测多个文件描述符--》多个文件描述符该如何存储--》linux中定义了一个变量类型fd_set (文件描述符集合,  专门用来存放要监测的文件描述符)
        第一步:定义文件描述符集合变量
                               fd_set  myset;
       第二步:往集合中添加要监测的文件描述符
                               FD_ZERO(&myset);
                               FD_SET(你要监测的文件描述符, &myset);

       第三步:调用select去监测刚才你添加的文件描述符状态
       第四步(重点):判断你监测的文件描述符是否发生了状态改变
                              通过判断文件描述符在不在集合中即可实现
                             if(FD_ISSET(文件描述符,&myset))  //说明文件描述符在集合中--》说明是这个文件描述符发生了状态改变
                             {

                             }


相关的接口函数
         int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
             返回值:成功  >0
                            失败  -1
                            超时  0
              参数:nfds(重点) --》你要监测的所有的文件描述符中最大的文件描述符+1
                       readfds --》我想监测文件描述符是否有数据可读
                                  select(最大+1,&myset,NULL,NULL)  //表示我只想监测myset中的文件描述符是否有数据可读
                      writefds --》我想监测文件描述符是否有数据可写
                                    select(最大+1,NULL,&myset,NULL)  //表示我只想监测myset中的文件描述符是否有数据可写
                      exceptfds --》我想监测文件描述符是否发生异常
                                    select(最大+1,NULL,NULL,&myset)  //表示我只想监测myset中的文件描述符是否发生异常
                       timeout --》超时时间,设置NULL表示永久阻塞等待
                                         struct timeval
                                         {
                                                 tv_sec;  //秒
                                                 tv_usec;  //微秒
                                         }

             特点
             第一:select阻塞当前程序,直到想要监测的文件描述符发生了状态改变才解除阻塞
             第二:如果select监测的了多个文件描述符(比如:A,B,C三个),如果某个文件描述符发生了状态改变,那么select会自动将没有发生状态改变的文件描述符从集合中剔除, 也就是说:select会将发生状态改变的文件描述符保留在集合,其它的删除

       void FD_CLR(int fd, fd_set *set);  //从set中把fd删除
       int  FD_ISSET(int fd, fd_set *set);  //判断fd在不在set集合中    返回1  在集合中    返回0  不在集合中
       void FD_SET(int fd, fd_set *set);  //把fd添加都set集合中
       void FD_ZERO(fd_set *set);  //清空set集合


                                  多路复用实现TCP双向通信

代码:客户端

#include "myhead.h"

/*
	多路复用实现双向通信---》tcp客户端代码
	    
*/


int main(int argc,char **argv)
{
	int tcpsock;
	int ret;
	char buf[100];
	//定义客户端的ipv4地址结构体变量
    struct sockaddr_in bindaddr;
    bzero(&bindaddr,sizeof(bindaddr));
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_port = htons(atoi(argv[2]));  //自己指定一个端口号
    bindaddr.sin_addr.s_addr = inet_addr(argv[1]); //指定自己的ip
  
    //定义服务器的ipv4地址结构体变量
    struct sockaddr_in serveraddr;
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[4]));  //服务器端口号
    serveraddr.sin_addr.s_addr = inet_addr(argv[3]); //服务器的ip
	//创建tcp套接字
	tcpsock=socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)
	{
		perror("创建tcp套接字!\n");
		return -1;
	}
	//设置端口重复使用
	int on=1;
	setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	
	//绑定ip和端口号
	ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
	if(ret==-1)
	{
		perror("绑定失败!\n");
		return -1;
	}
	
	//连接服务器
	ret=connect(tcpsock,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	if(ret==-1)
	{
		perror("连接服务器!\n");
		return -1;
	}
	//定义文件描述符集合变量
	fd_set myset;
	//多路复用监视文件描述符0--》键盘输入   文件描述符tcpsock是否可读
	 
	while(1)
	{
		//添加要监测的文件描述符
		FD_ZERO(&myset);
		FD_SET(0,&myset); 
		FD_SET(tcpsock,&myset);
		//printf("阻塞在select!\n");
		//调用select去监测   电子警察
		
		ret=select(tcpsock+1,&myset,NULL,NULL,NULL);
		if(ret>0) //监测成功,说明有文件描述符发生了状态变化
		{
			bzero(buf,100);
			//进一步去判断究竟是哪个文件描述符发生了状态改变
			if(FD_ISSET(0,&myset))
			{
				
				//printf("相信我,键盘一定有输入,不骗你的!\n");
				//主动调用scanf读取键盘输入的内容
				scanf("%s",buf);
				//发送给服务器
				write(tcpsock,buf,strlen(buf));
			}
			if(FD_ISSET(tcpsock,&myset))
			{
				//printf("相信我,tcpsock文件描述符可读,不骗你的!\n");
				//主动调用scanf读取键盘输入的内容
				ret=read(tcpsock,buf,100);
				if(ret==0)
				{
					printf("服务器已挂!\n");
					return -1;
				}
				printf("服务器发送过来的信息是:%s\n",buf);
			}
		}
		else if(ret==0)
		{
			
		}	
		else
		{
			perror("select监测失败!\n");
			return -1;
		}
	}
	
	close(tcpsock);
	return 0;
}

 服务器:

#include "myhead.h"

/*
	多路复用实现双向通信---》tcp客户端代码
*/

int main(int argc,char **argv)
{
	int tcpsock;
	int newsock;
	int ret;
	pthread_t id;
	char buf[100];
	//定义服务器的ipv4地址结构体变量
    struct sockaddr_in bindaddr;
    bzero(&bindaddr,sizeof(bindaddr));
    bindaddr.sin_family=AF_INET;
    bindaddr.sin_port=htons(atoi(argv[2]));  //服务器自己的端口号
    bindaddr.sin_addr.s_addr=inet_addr(argv[1]); //服务器自己的ip
 

	struct sockaddr_in clientaddr;
	bzero(&clientaddr,sizeof(clientaddr));
	int addrsize=sizeof(clientaddr);

	//创建tcp套接字
	tcpsock=socket(AF_INET,SOCK_STREAM,0);
	if(tcpsock==-1)
	{
		perror("创建tcp套接字!\n");
		return -1;
	}
	//printf("旧的文件描述符:%d\n",tcpsock);
	//设置端口重复使用
	int on=1;
	setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
	//绑定ip和端口号
	ret=bind(tcpsock,(struct sockaddr *)&bindaddr,sizeof(bindaddr));
	if(ret==-1)
	{
		perror("绑定失败!\n");
		return -1;
	}
	//监听
	ret=listen(tcpsock,8);
	if(ret==-1)
	{
		perror("监听失败!\n");
		return -1;
	}
	//接收客户端的连接请求
	newsock=accept(tcpsock,(struct sockaddr *)&clientaddr,&addrsize);
	if(newsock==-1)
	{
		perror("接收客户端的连接请求失败!\n");
		return -1;
	}
	//printf("新的文件描述符:%d\n",newsock);
	//定义文件描述符集合变量
	fd_set myset;
	//多路复用监视文件描述符0--》键盘输入   文件描述符tcpsock是否可读
	while(1)
	{
		//添加要监测的文件描述符
		FD_ZERO(&myset);
		FD_SET(0,&myset); 
		FD_SET(newsock,&myset); 
		//printf("阻塞在select!\n");
		//调用select去监测   电子警察
		ret=select(newsock+1,&myset,NULL,NULL,NULL);
		if(ret>0) //监测成功,说明有文件描述符发生了状态变化
		{
			bzero(buf,100);
			//进一步去判断究竟是哪个文件描述符发生了状态改变
			if(FD_ISSET(newsock,&myset))
			{
				//printf("相信我,newsock文件描述符可读,不骗你的!\n");
				//主动调用scanf读取键盘输入的内容
				ret=read(newsock,buf,100);
				if(ret==0)
				{
					printf("客服端已断开!\n");
					return -1;
				}
				printf("客户端发送过来的信息是:%s\n",buf);
			}
			if(FD_ISSET(0,&myset))
			{
				
				//printf("相信我,键盘一定有输入,不骗你的!\n");
				//主动调用scanf读取键盘输入的内容
				scanf("%s",buf);
				//发送给服务器
				write(newsock,buf,strlen(buf));
			}
			
		}
		else if(ret==0)
		{
			
		}	
		else
		{
			perror("select监测失败!\n");
			return -1;
		}
	}
	close(tcpsock);
	close(newsock);
	return 0;
}

myhead.h

#ifndef MYHEAD_H_
#define MYHEAD_H_
//自定义的头文件,把其它常用的头文件都包含进来
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <pthread.h>
#include <linux/input.h>
#include <semaphore.h>
#include<stdbool.h>
#include <dirent.h>
#include <time.h>
#include <sys/timeb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#endif

重点:重点:重点:

          一定要记得文件描述符集合的设置放在while里面,因为每次:select会将发生状态改变的文件描述符保留在集合,其它的删除

  

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值