一.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. POJ 2960 S-Nim<博弈>

    链接:http://poj.org/problem?id=2960 #include<stdio.h> #include<string.h> ; ; int SG[N];//S ...

  2. 菜鸟学习Spring——60s利用JoinPoint获取参数的值和方法名称

    一.概述 AOP的实现方法在上两篇博客中已经用了两种方法来实现现在的问题来了虽然我们利用AOP,那么客户端如何信息传递?利用JoinPoint接口来实现客户端给具体实现类的传递参数. 二.代码演示. ...

  3. RMAN 完全恢复

    OS:ORACLE-LINUX 5.7 DB:11.2.0.3.0 完全恢复 查看现有的数据文件SQL> select name from v$datafile; NAME----------- ...

  4. 源码解析之setContentView

    分享一下我以前学习时做到PPT中的一部分. 不难看出其实设置布局是由PhoneWindow来完成的. 然而我们要记住DecorView: 包括:状态栏,标题栏,内容 phoneWindow -> ...

  5. Java使用FileLock实现Java进程互斥锁

    原理:JDK的nio包中FileLock实现类似Linux fcntl的文件锁, 可使文件被进程互斥访问.  借助此功能, 可以实现强大的Java进程互斥锁, 从而在应用层面保证同一时间只有惟一的Ja ...

  6. sql server 查询数据库所有的表名+字段

    SELECT * FROM INFORMATION_SCHEMA.columns WHERE TABLE_NAME='Account' SELECT    (case when a.colorder= ...

  7. XCode6之后预编译文件的创建

    首先,在你的项目创建一个.pch预编译头文件(一直点Next)

  8. Apple Watch应用开发经验谈:我遇到的那些坑

    本文作者张忠良是滴答清单Apple Watch版应用的开发工程师,他用了一周的时间使用纯Objective-C语言完成了Apple Watch版滴答清单应用的开发工作.在这里,他从开发角度阐述了个人对 ...

  9. 关于自定义的NavigationBar

    系统的NavigationBar局限太大,而且现在我要做的navigationBar需要四个按钮,一个Label,一个ImageView,所以不能用系统默认的. 刚刚咨询了一个高手,她的建议是,将系统 ...

  10. matlab求距一个数最近的奇(偶)数

    int_a = floor(a);minEven = int_a+mod(int_a,2); %最近偶数minOdd = int_a+1-mod(int_a,2); %最近奇数