一.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. [原创]PostgreSQL Plus Advanced Server监控工具PEM(二)

    2.安装PEM Client 简单两条命令,开始PEM Client的安装. 我们在SUSE 11sp2上安装PEM Client 安装结束,运行PEM Client后可以看到如下的界面: 目前我们并 ...

  2. flask环境配置

    1.首先要看装python环境,最好是python 2.7版本的: 2.安装easy_install,至今也不确定这个东西是怎么装的,我先下载了一个ez_setup,用命令“python ez_set ...

  3. PBOC2.0与PBOC3.0的区别

    2013年2月,中国人民银行发布了<中国金融集成电路(IC)卡规范(V3.0)>(以下简称PBOC3.0),PBOC3.0是在中国人民银行2005年颁布的<中国金融集成电路(IC)卡 ...

  4. C#中如何利用操作符重载和转换操作符

    操作符重载 有的编程语言允许一个类型定义操作符应该如何操作类型的实例,比如string类型和int类型都重载了(==)和(+)等操作符,当编译器发现两个int类型的实例使用+操作符的时候,编译器会生成 ...

  5. <<梦断代码>>读书笔记

    从任何角度,Chandler项目开始时都是值得羡慕的.虽然是讲一个软件项目是如何失败的,不过里面有让我觉得很有意思. 失败了就进行反思:定位不能逆时代的潮流, 互联网的趋势不可逆转,人员沟通与合作是永 ...

  6. liferay7中如何Hiding the default Success Message

    下面介绍如何把在Liferay 7中如何把action执行成功之后的信息不显示,因为宝宝有需要,就去查了相关源码和资料. 如果想要某个portlet不显示执行成功的信息,在doProcessActio ...

  7. messager(消息窗口)

    一.$.messager.alert()类似js中的alert('String') 方法参数:title, msg, icon, function(回调函数) 描述:title头部面板标题.msg主要 ...

  8. 在云服务器搭建WordPress博客(六)发布和管理文章

    <( ̄︶ ̄)↗[GO!] 发布文章是一个网站后台最重要的功能之一,WordPress的文章发布功能是比较强大的,系统简单地介绍一下. 访问后台 – 文章 – 写文章 ,就可以看到如下图所示的界面 ...

  9. UVALive - 6955 Finding Lines 随机算法

    题目链接: http://acm.hust.edu.cn/vjudge/contest/126968#problem/F 题意 给你n个点,问是否有>=p/100*n个点共线(p>=20& ...

  10. 图解Git/图形化的Git参考手册

    此页图解git中的最常用命令.如果你稍微理解git的工作原理,这篇文章能够让你理解的更透彻. 基本用法 上面的四条命令在工作目录.暂存目录(也叫做索引)和仓库之间复制文件. ● git add fil ...