一.I/O复用

在《TCP套接字编程》的同步聊天程序中,我们看到TCP客户同时处理两个输入:标准输入和TCP套接字。考虑在客户阻塞于标准输入fgets调用时,服务器进程被杀死,服务器TCP虽然会给客户TCP发送一个FIN,但是客户客户进程正阻塞于标准输入读入过程,它将看不到这个EOF,直到从套接字读时为止。这样的进程需要一种预先告知内核的能力,使得内核一旦发现内核指定的一个或多个I/O条件就绪,它就通知进程。这个能力就称之为I/O复用,由select和poll这两个函数支持。

I/O复用通常应用在下列场合:

  • 当客户处理多个应用场合时(交互式输入和网络套接字);
  • 一个客户同时处理多个套接字;
  • 一个TCP服务器既要处理监听套接字,又要处理已连接套接字;
  • 一个服务器既要处理TCP,又要处理UDP;
  • 一个服务器要处理多个服务或者多个协议。

二.select函数

select函数允许进程指示内核等待多个事件中的任意一个发生,并只在有一个或多个事件发生或经历一段指定时间后才唤醒它。

#include<sys/select.h>
#include<sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

返回:若有就绪描述符则返回其数目,若超时返回0,若出错返回1。

头文件<sys/select.h>中定义的FD——SETSIZE常值是数据类型fd_set中的描述符总数,其值通常是。

maxfdp1参数指定带测试的描述符个数,它的值是带测试最大描述符加1,描述符0,1,2,...,maxfdp1-1均将被测试。

中间三个参数readset,writeset和exceptset指定我们要让内核测试读/写和异常条件的描述符。

void FD_ZERO(fd_set *fdset);         //clear all bits in fdset
void FD_SET(int fd,fd_set *fdset); //turn on the bit for fd in fdset
void FD_CLR(int fd,fd-set *fdset); //turn off the bit for fd in fdset
int FD_ISSET(int fd,fd_set *fdset); //is the bit for fd on in fdset?

其中,描述符集的初始化非常重要。

参数timeout告知内核等待所指定描述符中的任何一个就绪可花多少时间,timeval结构用于指定这段时间的秒数和微妙数。

struct timeval
{
long tv_sec; //second
long tv_usec; //microsecond
};

这个参数有三种可能:

  • 永远等待下去:仅在有一个描述符准备好I/O时才返回,为此,把该参数设为空;
  • 等待一段固定时间:在有一个描述符准备好I/O时返回,但是不超过timeval所指定时间;
  • 根本不等待:检查描述符后立即返回,这个称为轮询(polling),为此,秒数和微妙数必须为0。

三.异步聊天程序

写一个TCP异步聊天程序来加深理解。

服务器代码:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include <unistd.h>
#include<time.h> #define MAXSIZE 1024
#define PORT 8080
#define BACKLOG 10 int main(int argc,char **argv)
{
int listenfd,connfd;
struct sockaddr_in servaddr,cliaddr;
socklen_t len;
char message[MAXSIZE]; fd_set rfds;
// struct timeval tv;
int retval,maxfd=-1; if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
else printf("socket create success!\n"); bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(PORT); if((bind(listenfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr)))==-1)
{
perror("bind");
exit(1);
}
else printf("bind success!\n"); if(listen(listenfd,BACKLOG)==-1)
{
perror("listen");
exit(1);
}
else printf("sever is listening!\n"); for( ; ; )
{
printf("等待连接...\n");
len=sizeof(struct sockaddr);
if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))==-1)
{
perror("accept");
exit(1);
}
else printf("客户端:%s: %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
printf("开始聊天!\n");
for( ; ; )
{
FD_ZERO(&rfds);
FD_SET(0,&rfds);
maxfd=0;
FD_SET(connfd,&rfds);
if(connfd>maxfd) maxfd=connfd;
retval=select(maxfd+1,&rfds,NULL,NULL,NULL);
if(retval==-1)
{
printf("select出错!%s",strerror(errno));
break;
}
else if(retval==0)
{
printf("等待对方输入...\n");
continue;
}
else
{
if(FD_ISSET(0,&rfds))
{
bzero(message,MAXSIZE);
printf("输入:");
fgets(message,MAXSIZE,stdin); if(!strncasecmp(message, "quit", 4))
{
printf("终止聊天!\n");
break;
}
else len=send(connfd,message,strlen(message),0);
if(len<0)
{
printf("发送失败");
break;
}
}
if(FD_ISSET(connfd,&rfds))
{
bzero(message,MAXSIZE);
len=recv(connfd,message,MAXSIZE,0);
if(len>0) printf("客户端:%s",message);
else
{
if(len<0) printf("接受消息失败!\n");
else printf("客户端不在线!\n");
break;
}
}
}
}
close(connfd);
printf("是否退出服务器[Y/N]:");
bzero(message,MAXSIZE);
fgets(message,MAXSIZE,stdin);
if(!strncasecmp(message, "Y", 1))
{
printf("服务器已退出!\n");
break;
}
}
close(listenfd);
return 0;
}

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h> #define MAXSIZE 1024
#define PORT 8080 int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
socklen_t len;
fd_set rfds;
// struct timeval tv;
int retval,maxfd=-1; char message[MAXSIZE]; if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
else printf("socket create success!\n"); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT); inet_aton(argv[1],&servaddr.sin_addr); if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))==-1)
{
perror("connect");
exit(1);
}
else printf("conncet success!\n"); for( ; ; )
{
FD_ZERO(&rfds);
FD_SET(0,&rfds);
maxfd=0;
FD_SET(sockfd,&rfds);
if(sockfd>maxfd) maxfd=sockfd;
retval=select(maxfd+1,&rfds,NULL,NULL,NULL);
if(retval==-1)
{
printf("select出错!%s",strerror(errno));
break;
}
else if(retval==0)
{
printf("等待对方输入...\n");
continue;
}
else
{
if(FD_ISSET(sockfd,&rfds))
{
bzero(message,MAXSIZE);
len=recv(sockfd,message,MAXSIZE,0);
if(len>0)
printf("服务器:%s",message);
else
{
if(len<0) printf("接受消息失败!\n");
else printf("服务器已退出!\n");
break;
}
}
if(FD_ISSET(0,&rfds))
{
bzero(message,MAXSIZE);
printf("输入:");
fgets(message,MAXSIZE,stdin); if(!strncasecmp(message, "quit", 4))
{
printf("client 请求终止聊天!\n");
break;
}
else len = send(sockfd,message,strlen(message),0);
if(len<0)
{
printf("消息发送失败!\n");
break;
}
}
}
}
close(sockfd);
return 0;
}

编译:

gcc -Wall server.c -o server
gcc -Wall client.c -o client

服务器运行结果:

./server
socket create success!
bind success!
sever is listening!
等待连接...
客户端:127.0.0.1: 50235
开始聊天!
客户端:
客户端:你好啊,服务器!
客户端:我是客户。 输入:你好啊,客户端!
输入:客户端:
客户端:Byebye!
客户端不在线!
是否退出服务器[Y/N]:Y
服务器已退出!

客户端运行结果:

./client 127.0.0.1
socket create success!
conncet success! 输入:你好啊,服务器!
输入:我是客户端。
输入:服务器:
服务器:你好啊,客户端! 输入:Byebye!
输入:quit
输入:client 请求终止聊天!

I/O复用:异步聊天的更多相关文章

  1. C# Socket异步聊天例子

    最近在配合游戏服务器端搞一个客户端通信,客户端是unity搞的,理所当然就高C#了,上手之前先看了一下C# Socket通信这一块,基本不考虑同步方式,而异步方式,微软也提供了两套API,一套是Beg ...

  2. Django项目--web聊天室

    需求 做一个web聊天室,主要练习前端ajax与后台的交互: 一对一聊天和群组聊天 添加用户为好友 搜索并添加群组 管理员可以审批用户加群请求,群管理员可以有多个,群管理员可以删除,添加禁言群友 与聊 ...

  3. WebSocket聊天室demo

    根据Socket异步聊天室修改成WebSocket聊天室 WebSocket特别的地方是 握手和消息内容的编码.解码(添加了ServerHelper协助处理) ServerHelper: using ...

  4. nginx、swoole高并发原理初探

    阅前热身 为了更加形象的说明同步异步.阻塞非阻塞,我们以小明去买奶茶为例. 同步与异步 同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式. 同步:当一个同步调用发出去后,调用者要一直等待调 ...

  5. PHP并发IO编程之路

    并发IO问题一直是服务器端编程中的技术难题,从最早的同步阻塞直接Fork进程,到Worker进程池/线程池,到现在的异步IO.协程.PHP程序员因为有强大的LAMP框架,对这类底层方面的知识知之甚少, ...

  6. [转]PHP并发IO编程之路(深度长文)

    原文:https://www.imooc.com/article/8449 -------------------------------------------------------------- ...

  7. Linux 的 Socket IO 模型

    前言 之前有看到用很幽默的方式讲解Windows的socket IO模型,借用这个故事,讲解下linux的socket IO模型: 老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系. 他 ...

  8. PHP如何解决网站大流量与高并发的问题(四)

    动态语言的并发处理 相关概念 什么是进程.线程.协程 什么是多进程.多线程 同步阻塞模型 异步非阻塞模型 php并发编程实践 什么是进程.线程.协程 进程 进程是一个执行中的程序 进程的三态模型:多道 ...

  9. Python基础+Pythonweb+Python扩展+Python选修四大专题 超强麦子学院Python35G视频教程

    [保持在百度网盘中的, 可以在观看,嘿嘿 内容有点多,要想下载, 回复后就可以查看下载地址,资源收集不易,请好好珍惜] 下载地址:http://www.fu83.cc/ 感觉文章好,可以小手一抖 -- ...

随机推荐

  1. QPBOC扩展应用交易流程

    1 Q扩展部分数据 增加3个DGI,分别为:A001,8020,9020 9103中增加DF60(9F38中),DF61 增加DF62,DF63 1.1  A001扩展应用配置 DGI 长度 值(示例 ...

  2. .NET开源工作流RoadFlow-流程设计-流程步骤设置-按钮设置

    按钮设置是配置当前步骤的处理者可以执行哪些操作,每个按钮都有对应的执行脚本(javascript脚本). 从左边的按钮列表中选择当前步骤需要的按钮. 注意:如果是流程最后一步则要配置完成按钮而不是发送 ...

  3. Weka链接Mysql数据库

    Weka简介 Weka的全名是怀卡托智能分析环境(Waikato Environment for Knowledge Analysis),是一款免费的,非商业化(与之对应的是SPSS公司商业数据挖掘产 ...

  4. hdu 2578 Dating with girls(1)

    题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=2578 Dating with girls(1) Description Everyone in the ...

  5. 九度oj 1184 二叉树遍历

    原题链接:http://ac.jobdu.com/problem.php?pid=1184 简单的二叉树重建,遍历. 如下: #include<cstdio> #include<cs ...

  6. 微信扫码支付asp.net(C#)实现步骤

    支付提交页面: [HttpPost] public ActionResult index(decimal amount) { //生成订单10位序列号,此处用时间和随机数生成,商户根据自己调整,保证唯 ...

  7. EF6 在原有数据库中使用 CodeFirst 总复习(二、新的需求,简单修改原有表)

    一.为当前实体模型启用数据迁移 基础搭建好了,也就是原有的数据库有了,原有数据库的实体对象也有了,但生成的上下文中并没有标记当前数据库是否已经存在,当前实体是否修改过(以前版本好像有标记的),所以,要 ...

  8. Oracle之sql追踪

    select * from v$sqlarea t where t.sql_text like '%_070%' order by t.LAST_ACTIVE_TIME desc SELECT * F ...

  9. 二、secureCRT的 使用过程

    准备工作: win7与linux能互相ping通 linux安装了ssh被登陆服务 关闭window 防火墙,,控制面板 下载secureCRT 参考资料:http://zhidao.baidu.co ...

  10. [LAMP]【转载】——PHP7.0的安装

    ***原文链接:http://my.oschina.net/sallency/blog/541287 php编译过程报错解决可参考:http://www.cnblogs.com/z-ping/arch ...