在上一篇博文中,我们的程序中我们有3个客户端,因此也事先建立了3个管道,每个客户端分别使用一个管道向服务器发送消息。而在服务器端使用select系统调用,只要监测到某一管道有消息写入,服务器就将其read,并显示在标准输出上。

本篇文章,我们会让服务器拥有一个管道,专门用于从客户端接收消息(上线通知,发送需要服务器转发的消息以及下线通知)。服务器需要维护一个列表(使用结构体),记录哪些用户已经连上服务器用于接收消息的管道。当客户端启动,会向服务器发送上线消息,同时将自己的pid发送给server,server会将其添加到列表,以后会转发消息给在列表上的客户端。与此同时,客户端需要创建一个管道,用于接收服务器转发的消息;注意,要将其创建的管道名称告知服务器,以便server打开管道写端,告知管道名称可以在客户端向server发送上线消息时一并发送。 当客户端下线时也要告诉server,以便服务器将其从列表删除,这样以后不会再转发消息给它。如果不删,服务器向一个关闭读端的管道发送消息,会使服务器挂掉!(PIPE信号)

注意

我们假设现在有3个客户端,服务器用于接收消息的管道称为A。由于服务器只拥有一个管道A用于接收从客户端发送的消息,那么所有的客户端都会在管道A的另一端开启写端。也就是说所有客户端的3个写端对服务器的1个读端。通过上一篇博文,我们已经知道,select是通过阻塞与否来监听管道的。只有当管道非阻塞时,select才能获得消息,将fd_set中的相应文件描述符置1。当3个写端对1个读端时,非阻塞的情况如下:

1.当任意一个客户端的写端向管道发送消息时,该管道非阻塞,select可以监听到,read返回读到的字数。

2.所有3个写端都关闭,该管道非阻塞,select可以监听到,read返回0。注意,仅仅关闭某个客户端的写端,select是检测不到的,原因就是因为select只能检测到非阻塞的状况。

到底非阻塞是属于1或者2那种情况,select并不会知道,需要我们自己判断,通常通过read的返回值判断。当read返回值为0时,我们会在if语句中使用continue,因为服务器不能挂,客户端关闭的读端,可以在之后开启。

server.c

/*************************************************************************
> File Name: server.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Sat 23 Aug 2014 05:08:48 PM CST
************************************************************************/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h> typedef struct tag
{
int s_id; /* 进程ID */
int s_fd; /* 进程描述符 */
int s_flag; /* server列表里用户是否有效 */
}USR,*pUSR;
int main(int argc, char *argv[])
{
/* 打开管道 */
int fd_server;
fd_server = open(argv[1], O_RDONLY);
if(fd_server == -1)
{
perror("error");
exit(1);
}
/* 初始化server用户列表 */
USR ulist[1024];
memset(ulist,0,sizeof(ulist)); /* 定义select参数各项参数 */
fd_set read_set,ready_set; /* ready_set是read_set的备份 */
FD_ZERO(&read_set); /* 清空fd_set */
FD_SET(fd_server, &read_set); /* 将服务器用于接收消息的管道添加到监听集合中 */
struct timeval tm; /* select轮巡时间*/ int nret; /* 记录select返回值 */
char buf[1024]; /* 存放从管道中读取的消息 */
while(1)
{
/* 重设select各项参数 */
tm.tv_sec = 0;
tm.tv_usec = 1000;
ready_set = read_set;
nret = select(fd_server + 1, &ready_set, NULL, NULL, &tm);
/* 在select的轮巡时间内,管道阻塞,则nret返回0 */
if(nret == 0)
{
continue;
}
if(FD_ISSET(fd_server, &ready_set)) //实际上此处if可以省略,因为只监听了一个管道
{ //nret不为0,一定是该管道非阻塞
memset(buf, 0, 1024);
if(0 == read(fd_server, buf,1024))
{
continue;
}else
{
if(strncmp(buf,"on",2) == 0) //on pid
{
int pid;
char pipename[32] = ""; //存放管道名
sscanf(buf+3, "%d", &pid); //管道名义pid.pipe命名
printf("%d on \n", pid); //将客户上线消息输出在屏幕上
sprintf(pipename,"%d.fifo", pid);
/* 从用户列表中,找一个无效的结构体将其存入 */
int index;
for(index = 0; index < 1024; index++)
{
if(ulist[index].s_flag == 0)
{
break;
}
}
if(index == 1024)
{
printf("full !\n");
}else
{
ulist[index].s_id = pid;
ulist[index].s_fd = open(pipename,O_WRONLY); /* 打开服务器端的写端,用于转发消息给客户端 */
ulist[index].s_flag = 1;
}
}else if(strncmp(buf,"off",3) == 0) //off pid
{
int pid;
sscanf(buf+4,"%d",&pid);
printf("%d off!\n", pid);
int index;
for(index = 0;index < 1024; index++)
{
if(ulist[index].s_id == pid)
{
ulist[index].s_flag = 0;
close(ulist[index].s_fd);
break;
}
}
}else
{
int index;
for(index = 0; index < 1024; index++)
{
if(ulist[index].s_flag == 1)
{
write(ulist[index].s_fd, buf, strlen(buf));
}
}
}
}
}
} }

client.c

/*************************************************************************
> File Name: client.c
> Author: KrisChou
> Mail:zhoujx0219@163.com
> Created Time: Sat 23 Aug 2014 09:21:02 AM PDT
************************************************************************/ #include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
/* 打开上传消息给服务器的管道 */
int fd_send;
fd_send=open(argv[1],O_WRONLY);
if(fd_send==-1)
{
perror("open");
exit(1);
} /* 注意一定要在向服务器发送上线消息之前创建好客户端自己的管道,不然服务端找不到该管道*/
/* pipename存放客户端自己所创建的管道,命名统一为pid.fifo */
char pipename[32]="";
sprintf(pipename,"%d.fifo",getpid());
/* 客户端创建接受消息的管道 */
if(-1==mkfifo(pipename,0666))
{
perror("mkfifo");
exit(1);
} /* 将上线消息写入管道 */
char msg[1024]="";
sprintf(msg,"on %d !\n",getpid());
write(fd_send,msg,strlen(msg)); /* 打开客户端自己的管道 */
int fd_rcv;
fd_rcv=open(pipename,O_RDONLY);
if(fd_rcv==-1)
{
perror("open client");
exit(1);
} /* 子进程用于接收服务器转发的消息 */
if(fork()==0)
{
close(fd_send);
while(memset(msg,0,1024),read(fd_rcv,msg,1024)>0)
{
printf("msg>>:");
fflush(stdout);
write(1,msg,strlen(msg));
}
/* 当客户端下线,服务器将其从列表中删除,并关闭客户端管道的写端。
当服务器关闭该管道的写端时,即退出while循环 */
close(fd_rcv);
exit(1);
}
/* 父进程用于发送消息 */
close(fd_rcv);
while(memset(msg,0,1024),fgets(msg,1024,stdin)!=NULL)
{
write(fd_send,msg,strlen(msg));
}
/* 按ctrl+D退出循环,之后客户端下线 */
memset(msg,0,1024);
sprintf(msg,"off %d\n",getpid());
write(fd_send,msg,strlen(msg));
close(fd_send);
wait(NULL);
}
运行程序,输入:
make 1.fifo

./server.exe 1.fifo

./client.exe 1.fifo
./client.exe 1.fifo
...

Linux之select系统调用_2的更多相关文章

  1. Linux之select系统调用_1

    SYNOPSIS /* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier stand ...

  2. Linux下select, poll和epoll IO模型的详解

    http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll 介绍 Epoll 可是当前在 Linux 下开发大规模并发网络程序的热 ...

  3. Linux 多路复用 select / poll

    多路复用都是在阻塞模式下有效! linux中的系统调用函数默认都是阻塞模式,例如应用层读不到驱动层的数据时,就会阻塞等待,直到有数据可读为止. 问题:在一个进程中,同时打开了两个或者两个以上的文件,读 ...

  4. Linux : select()详解 和 实现原理【转】

    转自:http://blog.csdn.net/huntinux/article/details/39289317 原文:http://blog.csdn.net/boboiask/article/d ...

  5. (转)Linux下select, poll和epoll IO模型的详解

    Linux下select, poll和epoll IO模型的详解 原文:http://blog.csdn.net/tianmohust/article/details/6677985 一).Epoll ...

  6. linux下select,poll,epoll的使用与重点分析

    好久没用I/O复用了,感觉差点儿相同都快忘完了.记得当初刚学I/O复用的时候花了好多时间.可是因为那会不太爱写博客,导致花非常多时间搞明确的东西,依旧非常easy忘记.俗话说眼过千遍不如手过一遍,的确 ...

  7. 基于int的Linux的经典系统调用实现

     先说明两个概念:中断和系统调用 一 系统调用: 是应用程序(运行库也是应用程序的一部分)与操作系统内核之间的接口,它决定了应用程序是如何和内核打交道的. 1,  Linux系统调用:2.6.19版内 ...

  8. linux select函数:Linux下select函数的使用详解【转】

    本文转载自;http://www.bkjia.com/article/28216.html Linux下select函数的使用 Linux下select函数的使用 一.Select 函数详细介绍 Se ...

  9. 高性能网络编程 - select系统调用

         IO复用使得程序可以同一时候监听多个文件描写叙述符,比方client须要同一时候处理用户输入和网络连接,server端须要同一时候处理监听套接字和连接套接字,select系统调用可以使得我们 ...

随机推荐

  1. Android--获取使用的总流量和每个App的上传、下载的流量

    获得每个App的上传.下载的流量. 思路就是获取到我们手机上的所有app,再获得app里面使用的权限,如果app有网络权限,就显示出来. 代码很简单,代码里面也有比较详细的注释,下面直接上代码 布局文 ...

  2. 开发移动app与服务器端session的状态管理与交互

    我们进行web开发的时候,一般使用cookie或session来保存用户的登录状态,通过检查cookie或session的数据来验证用户是否具有对某些需要登录的页面的访问权限,这一切都是通过浏览器来完 ...

  3. 什么是O/R Mapping(ORM)

    ORM,即Object-Relationl Mapping,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一 ...

  4. Java动态替换InetAddress中DNS的做法简单分析1

    在java.net包描述中, 简要说明了一些关键的接口. 其中负责networking identifiers的是Addresses. 这个类的具体实现类是InetAddress, 底层封装了Inet ...

  5. [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)

     [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching) http://www.360doc.com/content/12/0428/17/6187784 ...

  6. MVC Razor模板引擎输出HTML或者生产HTML文件

    以前做CMS的时候都会根据模板来生成输出HTML或者生成HTML文件. 常用的引擎有VTemplate.NVelocity等等,这个我就布做介绍了. 这里我想说的是.当mvc出现Razor模板引擎的时 ...

  7. ios coreData使用

    ios中的coredata的使用(转) 分类: ios2013-07-15 18:12 27288人阅读 评论(1) 收藏 举报 Core Data数据持久化是对SQLite的一个升级,它是ios集成 ...

  8. js event bubble and capturing

    Bubble: pppppp Capturing pppppp Mix pppppp To Stop Bubble pppppp // JS Bin

  9. MyEclipse导入jquery等文件报错的解决方案

    1.选中报错的jquery文件例如“jquery-1.8.0.min.js”. 2.右键选择 MyEclipse-->Exclude From Validation . 3.再右键选择 MyEc ...

  10. 四则运算之C++版

    一.设计思想 之前的版本是用Java语言实现的,在这次的练习中,我用C++语言将其功能逐一实现,其实C++与Java有很多相似之处,只是一些书写格式不同,思路还是一样的. 二.源代码 #include ...