socket多路复用应用

int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) 

功能:轮循等待的方式,从多个文件描述符中获取状态变化后的情况

readfds :包含所有可能因状态变成可读而触发select()函数返回的文件描述符

writefds :包含所有可能因状态变成可写而触发select()函数返回的文件描述符

exceptfds :包含所有可能因状态发生特殊异常(如带外数据到来)而触发select()函数返回的文件描述符

针对文件描述符集合的操作如下:

#define FD_SET(fd, fdsetp)          //把fd添加到fdsetp中
#define FD_CLR(fd, fdsetp) //从fdsetp中删除fd
#define FD_ISSET(fd, fdsetp) //检测fdsetp中的fd是否出现异常
#define FD_ZERO(fdsetp) //初始化fdsetp为空

参数1限制上面要检测的文件描述符的范围,范围在0到最大文件描述符值之间

最后一个参数:表示阻塞超时时限

struct timeval {
long tv_sec;
long tv_usec;
};

返回值:函数错误,返回-1; 超时返回0,将时间结构体清空为0;有文件需要处理,返回相应的文件描述符,在文件描述符集合中清除不需要处理的文件描述符

例子

1.检测某个socket是否可读

fd_set rdfds;        //声明一个fd_set集合来保存要检测的socket
struct timeval tv; //保存时间
int ret; //保存返回值
FD_ZERO(&rdfds); //集合清零
FD_SET(socket, &rdfds); //把要检测的文件描述符加入集合
tv.tv_sec = ;
tv.tv_usec = ; //设置select等待的最大时间为1s+500ms
ret = select(socket + , &rdfds, NULL, NULL, &tv); //检测集合中是否有可读信息
if(ret < ) //出错
perror("select");
else if(ret == ) //超时
printf("超时\n");
else //有状态变化
{
printf("ret = %d\n", ret);
//判断socket是否变成可读
if(FD_ISSET(socket, &rdfds))
{
recv(...); //读取
}
}

2.检测用户键盘输入。需要把标准输入文件描述符0放入select检测

FD_ZERO(&rdfds);
FD_SET(, &rdfds);
tv.tv_sec = ;
tv.tv_usec = ;
ret = select(, &rdfds, NULL, NULL, &tv);
if(ret < ) //出错
perror("select");
else if(ret == ) //超时
printf("超时\n");
else //有输入
scanf("%s", buf);

int pselect (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, const struct timespec *__restrict __timeout, const __sigset_t *__restrict __sigmask)

该函数与select()函数功能几乎相同,只是时间精度更高,同时设置了阻塞的信号集合。

时间的结构体声明如下:

struct timespec{
long ts_sec;
long ts_nsec; //ns
};

poll与ppoll函数

可以实现比select/pselect函数更强大的功能,更细粒的等待时间

int poll (struct pollfd *fds, nfds_t nfds, int timeout)

int ppoll (struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask)

其中文件描述符的结构体定义为:

struct pollfd{
int fd; //文件描述符
short events; //请求事件
short revents; //返回的事件
};

请求或返回的事件类型如下:

ppoll()函数也可以在阻塞过程中屏蔽某些信号,而且timeout上ppoll的精度更高。

调用

ready = ppoll(&fds, nfds, timeout_ts, &sigmask);

相当于调用

sigset_t origmask;
int timeout;
timeout = (timeout_ts == NULL) ? - : (timeout_ts.tv_sec * + timeout_ts.tv_nesc / );
sigprocmask(SIG_SETMASK, &sigmask, &origmask);
ready = poll(&fds, nfds, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);

示例

基于多路复用的服务器客户端聊天程序

这个的效果是目前为止最好的,可以实现一对多的通信。消息也比较清晰。

服务器端

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/time.h>
#define MAXBUF 1024
int main(int argc, char * argv[])
{
int sockfd, new_fd;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + ];
fd_set rfds; //文件描述符集合
struct timeval tv;
int retval, maxfd = -;
if(argv[])
myport = atoi(argv[]); //参数2为端口号
else
myport = ; //默认端口号
if(argv[])
lisnum = atoi(argv[]); //命令行第3个参数为listen队列大小 即可以等待多少个客户端
else
lisnum = ;
//创建socket对象 ipv4 TCP 默认协议
if((sockfd = socket(PF_INET, SOCK_STREAM, )) == -)
{
perror("socket");
exit(EXIT_FAILURE);
}
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
if(argv[])
my_addr.sin_addr.s_addr = inet_addr(argv[]); //参数1为IP地址
else
my_addr.sin_addr.s_addr = INADDR_ANY;
//绑定地址信息
if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -)
{
perror("bind");
exit(EXIT_FAILURE);
}
//服务器监听网络
if(listen(sockfd, lisnum) == -)
{
perror("listen");
exit(EXIT_FAILURE);
}
while()
{
printf("\n---------wait for new connect\n");
len = sizeof(struct sockaddr);
//接收客户端连接
if((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &len)) == -)
{
perror("accept");
exit(EXIT_FAILURE);
}
else
{
//打印连接信息
printf("server: got connection from %s, port %d, socket %d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
while()
{
FD_ZERO(&rfds);
FD_SET(, &rfds);
FD_SET(new_fd, &rfds);
maxfd = new_fd; //只有两个文件描述符0和new_fd,最大值为sockfd
tv.tv_sec = ;
tv.tv_usec = ;
//多路复用
retval = select(maxfd + , &rfds, NULL, NULL, &tv);
if(retval == -) //函数出错
{
perror("select");
exit(EXIT_FAILURE);
}
else if(retval == ) //超时
{
continue;
}
else
{
//检测是否为标准输入引起异常
if(FD_ISSET(, &rfds))
{
bzero(buf, MAXBUF + );
fgets(buf, MAXBUF, stdin); //从标准输入读数据
if(!strncasecmp(buf, "quit", )) //如果quit退出
{
printf("i will quit!\n");
break;
}
//将数据发送给客户端
len = send(new_fd, buf, strlen(buf) - , ); //为什么要-1 ??
if(len > )
printf("send successful, %d byte send!\n", len);
else
{
printf("send failure!");
break;
}
}
//如果是当前sockfd引起的异常
if(FD_ISSET(new_fd, &rfds))
{
bzero(buf, MAXBUF + );
//从中读取数据
len = recv(new_fd, buf, MAXBUF, );
if(len > )
printf("recv success :'%s', %dbyte recv\n", buf, len);
else if(len == )
{
printf("the other one end quit\n");
break;
}
}
}
}
}
close(new_fd);
printf("need other connect (no->quit)"); //是否需要等待其他客户端连接
fflush(stdout); //刷新标准输出
bzero(buf, MAXBUF + );
fgets(buf, MAXBUF, stdin);
if(!strncasecmp(buf, "no", ))
{
printf("quit!\n");
break;
}
}
close(sockfd);
return ;
}

客户端

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/time.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + ];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -;
if(argc != )
{
printf("argv format errno, pls:\n\t\t%s IP port\n", argv[]);
exit();
}
//创建socket
if((sockfd = socket(AF_INET, SOCK_STREAM, )) < )
{
perror("Socket");
exit(EXIT_FAILURE);
}
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[])); //参数2为端口号
if(inet_aton(argv[], (struct in_addr *)&dest.sin_addr.s_addr) == ) //参数1为IP地址
{
perror(argv[]);
exit(EXIT_FAILURE);
}
//发起连接
if(connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) != )
{
perror("Connect");
exit(EXIT_FAILURE);
}
printf("\nget ready pls chat\n");
while()
{
FD_ZERO(&rfds);
FD_SET(, &rfds);
FD_SET(sockfd, &rfds);
maxfd = sockfd;
tv.tv_sec = ;
tv.tv_usec = ;
//多路复用
retval = select(maxfd + , &rfds, NULL, NULL, &tv);
if(retval == -)
{
printf("select %s", strerror(errno));
break;
}
else if(retval == ) //超时
continue;
else
{
if(FD_ISSET(sockfd, &rfds))
{
bzero(buffer, MAXBUF + );
len = recv(sockfd, buffer, MAXBUF, );
if(len > )
{
printf("recv message:'%s', %dbyte recv\n", buffer, len);
}
else if(len < )
{
printf("message recv failure\n");
}
else
{
printf("the other quit, quit\n");
break;
}
}
if(FD_ISSET(, &rfds))
{
bzero(buffer, MAXBUF + );
fgets(buffer, MAXBUF, stdin);
if(!strncasecmp(buffer, "quit", ))
{
printf("i will quit!\n");
break;
}
len = send(sockfd, buffer, strlen(buffer) - , );
if(len > )
printf("send successful, %d byte send!\n", len);
else
printf("send failure!");
}
}
}
close(sockfd);
return ;
}

服务器效果

客户端1效果

客户端2效果

【linux高级程序设计】(第十四章)TCP高级应用 2的更多相关文章

  1. 读书笔记 - js高级程序设计 - 第十五章 使用Canvas绘图

    读书笔记 - js高级程序设计 - 第十三章 事件   canvas 具备绘图能力的2D上下文 及文本API 很多浏览器对WebGL的3D上下文支持还不够好   有时候即使浏览器支持,操作系统如果缺缺 ...

  2. linux高级管理第十四章--kvm虚拟化

    案例 安装kvm所需软件 验证 注:虚拟机要开启虚拟引擎 开启服务 环境准备 安装相关软件包 启动 创建网桥 重启,reboot 安装虚拟机 完成.

  3. 第十四章:高级I/O

    14.1:引言 本章内容包括非阻塞I/O.记录锁.系统V流机制.I/O多路转接(select和poll函数).readv和writev函数以及存储映射I/O(mmap),这些都称为高级I/O. 14. ...

  4. 鸟哥的Linux私房菜——第十四章:Bash Shell

    视频链接:http://www.bilibili.com/video/av10094012/ 本章目录: 1. Bash shell1.1 什么是 shell ? (我们通过shell与Kernel核 ...

  5. 【TCP/IP详解 卷一:协议】第二十四章 TCP的未来与性能

    来到了TCP的最后一个章节,未来与性能.在当时(1991年)的未来,如今已经部分变为现实,部分就只是历史中的实验. 主要内容: 路径MTU的发现与TCP的结合. 长肥管道 和 高速千兆比网络. 窗口扩 ...

  6. 【读书笔记】C#高级编程 第二十四章 文件和注册表操作

    (一)文件和注册表 对于文件系统操作,相关的类几乎都在System.IO名称空间中,而注册表操作由System.Win32名称空间中的类来处理. (二)管理文件系统 System.MarshalByR ...

  7. 《javascript高级程序设计》第四章 Variables,scope,and memory

    4.1 基本类型和引用类型的值 primitive and reference values 4.1.1 动态的属性 dynamic properties 4.1.2 复制变量值 copying va ...

  8. JavaScript高级程序设计:第四章

    变量.作用域和内存问题 1.ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值.基本类型值指的是简单的数据段,引用类型值指的是有多个值构成的对象. 2.动态的属性:定义一个基 ...

  9. 《JAVASCRIPT高级程序设计》第四章

    javascript变量是松散类型,它只是在特定时间表示特定值的一个名字而已:变量的值以及类型,可以在脚本的生命周期内改变.变量的类型,分为基本类型和引用类型两种,具体介绍如下图所示: 执行环境是Ja ...

  10. 《JavaScript 高级程序设计》第四章:变量、作用域和内存问题

    目录 变量的引用 执行环境及作用域 作用域链延长 块级作用域 垃圾回收机制 变量的引用 当一个变量保存了基本数据类型时,此时对于变量的操作(赋值,运算)就是操作这个基本数据的本身,就算是赋值操作,赋值 ...

随机推荐

  1. linux中如何解决克隆后的电脑的问题

    1.如何解决克隆后的电脑的网络问题 克隆出来的电脑,IP地址,网卡都是重复的,不能直接使用,需要修改 1)vim  /etc/udev/rules.d/70-persistent-net.rules ...

  2. border与background定位

    1.background定位的局限 只能相对于左上角数值定位,不能相对于右下 即background-position默认相对于左上方定位的 2.怎样让图片相对于右下角? background-pos ...

  3. SVN迁移到Git原因说明

    1.Git分布式的源码管理 每位开发人员计算机本地会有一份代码库,开发人员可在不受其他人代码提交影响的前提下对源码进行提交/回滚/撤销等操作. 在独立的开发任务中即可实现对源码管理又不受其他开发人员提 ...

  4. csdn回到顶端

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. 每天一个Linux命令(3):ls命令

    ls命令用来显示目标列表,在Linux中是使用率较高的命令.ls命令的输出信息可以进行彩色加亮显示,以分区不同类型的文件. 语法 ls(选项)(参数) 选项 -a:显示所有档案及目录(ls内定将档案名 ...

  6. selenium IDE录制

    一.        安装 可以用Firefox打开https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/,在这里边找到和当前firef ...

  7. 牛客网暑期ACM多校训练营(第一场):J-Different Integers(分开区间不同数+树状数组)

    链接:J-Different Integers 题意:给出序列a1, a2, ..., an和区间(l1, r1), (l2, r2), ..., (lq, rq),对每个区间求集合{a1, a2, ...

  8. python学习总结---面向对象1

    面向对象 - 与面向过程对比 - 面向过程:数学逻辑的映射,学会做个好员工. - 面向对象:生活逻辑的映射,学会做个好领导. - 生活实例 - 类: 人 手机 电脑 - 对象: 习大大.普京 二狗的i ...

  9. sql 游标使用

    declare @PASSDate datetime,@VLPN varchar(50),@VLPNColor varchar(10),@nambers int set @VLPN='';set @V ...

  10. c语言为什么效率高

    文章:为什么和其他语言相比C语言是快速的语言 文章:C语言的应用领域有哪些? 虽然文章写的很差劲,但是仍然可以学到点知识. 计算机组成原理→DOS命令→汇编语言→C语言(不包括C++).代码书写规范→ ...