多路复用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会将发生状态改变的文件描述符保留在集合,其它的删除
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)