拓扑结构:

各个客户端创建读写管道,通过“上下线信息管道”向服务器发送上下线信息和读写管道名称。服务器接受信息,修改链表(存储客户端信息)。客户端、服务器打开读写管道,服务器通过“W”管道接收从客户端发来的信息,在根据链表同个其他各个“R”管道向其他客户端发送信息。

具体流程:

1、建立上下线信息管道

服务器:

 mkfifo(path_name, );// 创建管道 —— 专用于接收客户端上下线信息

     printf("mkfifo over!\n");

     fd_listen = open(path_name, O_RDONLY);
if(fd_listen == -)
{
printf("open server_fifo fail!\n");
exit();
}

客户端:

 //打开上下线管道
int fd_server ;
char path_name[]="";
char fifo_name[] ;
char msg[] ="" ;
char fifo1[], fifo2[] ;
int fd_recv, fd_send ;
sprintf(path_name, "%s/%s", PIPE_PATH, PIPE_NAME); fd_server = open(path_name, O_WRONLY);
if(fd_server == -)
{
printf("open fail!\n");
exit() ;
}

2、客户端建立读写管道

// 建造读写管道  pid_r.fifo pid_w.fifo
//
memset(fifo_name, , );
sprintf(fifo_name, "%u_r.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); strcpy(fifo1, path_name);
if(- == mkfifo(path_name, ) )
{
printf("mkfif fail: %s\n", path_name);
exit() ;
} printf("%s open\n", path_name); memset(fifo_name, , );
sprintf(fifo_name, "%u_w.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); strcpy(fifo2, path_name);
if(mkfifo(path_name, ) == - )
{ printf("mkfif fail: %s\n", path_name);
exit() ;
}
printf("%s open\n", path_name); printf("mkfifo over!\n");

3、上线处理

客户端发送上线信息:

 //发送上线信息
sprintf(msg, "%u on\n", getpid());
printf("msg: %s\n", msg);
write(fd_server, msg, strlen(msg));

服务器监听到后处理:

 // 读写是针对客户端而言的: pid_r.fifo(c_r - s_w)   pid_w.fifo(c_w - s_r)
printf("client: %d on\n", client_pid);
//pid_r.fifo s_w
//构建管道名字符串
memset(fifo_name, , ) ;
sprintf(fifo_name, "%d_r.fifo", client_pid);
memset(path_name, , ) ;
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); //新增链表节点
pnew = (pCLIENT)calloc(, sizeof(CLIENT));
pnew ->m_id = client_pid ;
printf("pid_r.fifo: %s\n", path_name);
pnew ->m_send = open(path_name, O_WRONLY);
printf("send_fd: %d\n", pnew ->m_send); //打开“W”管道 pid_w.fifo s_r
memset(fifo_name, , ) ;
sprintf(fifo_name, "%d_w.fifo", client_pid);
memset(path_name, , ) ;
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); printf("pid_w.fifo: %s\n", path_name);
pnew ->m_recv = open(path_name, O_RDONLY);
printf("recv_fd: %d\n", pnew ->m_recv);
printf("open client fifo: %d, %d\n", pnew ->m_send, pnew ->m_recv); FD_SET(pnew ->m_recv, &rd_sets);//把pid_w.fifo 句柄 加到读集合中去 主要:更新的是读集合,而不是副本集合 pnew ->m_next = plist ; //插入链表
plist = pnew ;

客户端也打开管道:

     memset(fifo_name, ,  );
sprintf(fifo_name, "%u_r.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); fd_recv = open(path_name, O_RDONLY); memset(fifo_name, , );
sprintf(fifo_name, "%u_w.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); fd_send = open(path_name, O_WRONLY); printf("fifo open %d %d\n", fd_send, fd_recv);

4、客户端、服务器对通信信息处理

客户端监听到键盘的输入信息,则转发给服务器:

 if(FD_ISSET(, &rd_sets))
{
memset(msg, , sizeof(msg)) ;
sprintf(msg, "from %u: ", getpid());
write(fd_send, msg, strlen(msg)); }

客户端监听服务器发来的信息,并打印:

      if(FD_ISSET(fd_recv, &rd_sets))
{
memset(msg, , sizeof(msg)) ;
read(fd_recv, msg, );
write(, msg, strlen(msg));
}

服务器监听到客户端发来的信息,根据链表内客户端的信息,进行转发:

     //2、遍历链表,监听其他的管道文件(用于服务器和客户端通信)句柄
pcur = plist ;
while(pcur)
{
if(FD_ISSET(pcur ->m_recv, &bak_sets))// translate
{
memset(msg, , );
read(pcur -> m_recv, msg, ); dispatch_msg(plist,pcur, msg);
}
pcur = pcur ->m_next ;
}
 void dispatch_msg(pCLIENT phead,pCLIENT pcur, char* msg)
{
while(phead)
{
if(phead!=pcur)
write(phead ->m_send, msg, strlen(msg));
phead = phead ->m_next ;
}
}

完整代码

server:

 #include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/select.h>
#include<sys/time.h>
#define PIPE_PATH "/home/soso/Desktop/2-17/FIFO"
#define PIPE_NAME "server.fifo"
typedef struct tag
{
int m_id ;
int m_send;
int m_recv;
struct tag* m_next ;
}CLIENT, *pCLIENT; void dispatch_msg(pCLIENT phead,pCLIENT pcur, char* msg)
{
while(phead)
{
if(phead!=pcur)
write(phead ->m_send, msg, strlen(msg));
phead = phead ->m_next ;
}
} int main(int argc, char* argv[])
{
int fd_listen ; //文件句柄
char path_name[] = "" ;
char fifo_name[] ;
char msg[]; char client_stat[] = "";//客户端状态
int client_pid ; // 客户端进程ID
sprintf(path_name, "%s/%s", PIPE_PATH, PIPE_NAME);//路径名 = 路径/管道名 mkfifo(path_name, );// 创建管道 —— 专用于接收客户端上下线信息 printf("mkfifo over!\n"); fd_listen = open(path_name, O_RDONLY);
if(fd_listen == -)
{
printf("open server_fifo fail!\n");
exit();
} pCLIENT plist = NULL, pcur, pnew, ppre ; fd_set rd_sets, bak_sets; //读集合 和 备份读集合
FD_ZERO(&rd_sets);//初始化清空
FD_ZERO(&bak_sets);
FD_SET(fd_listen, &rd_sets); //将fd_listen句柄 加入到 读集合
while()
{
bak_sets = rd_sets ;//每次循环更新副本集合
printf("selecting...\n");
select(, &bak_sets, NULL, NULL, NULL);//监听集合 //1、监听fd_listen 管道文件句柄(专用于服务器接收客户端上下线信息)
if(FD_ISSET(fd_listen, &bak_sets)) //若监听到 fd_listen
{
memset(msg,, );
if( read(fd_listen, msg, ) == ) //读取管道信息;但没有客户端 write的时候,read的返回值是0
{
printf("no clients!\n");
continue ;
} memset(client_stat, , sizeof(client_stat));
sscanf(msg, "%d%s", &client_pid, client_stat);//信息格式:“client_pid client_stat\n”
if(strncmp("on", client_stat, ) == )//client on"pid on\n"
{// 读写是针对客户端而言的: pid_r.fifo(c_r - s_w) pid_w.fifo(c_w - s_r)
printf("client: %d on\n", client_pid);
//pid_r.fifo s_w
//构建管道名字符串
memset(fifo_name, , ) ;
sprintf(fifo_name, "%d_r.fifo", client_pid);
memset(path_name, , ) ;
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); //新增链表节点
pnew = (pCLIENT)calloc(, sizeof(CLIENT));
pnew ->m_id = client_pid ;
printf("pid_r.fifo: %s\n", path_name);
pnew ->m_send = open(path_name, O_WRONLY);
printf("send_fd: %d\n", pnew ->m_send); //pid_w.fifo s_r
memset(fifo_name, , ) ;
sprintf(fifo_name, "%d_w.fifo", client_pid);
memset(path_name, , ) ;
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); printf("pid_w.fifo: %s\n", path_name);
pnew ->m_recv = open(path_name, O_RDONLY);
printf("recv_fd: %d\n", pnew ->m_recv);
printf("open client fifo: %d, %d\n", pnew ->m_send, pnew ->m_recv); FD_SET(pnew ->m_recv, &rd_sets);//把pid_w.fifo 句柄 加到读集合中去 主要:更新的是读集合,而不是副本集合 pnew ->m_next = plist ; //插入链表
plist = pnew ; }else//client off "pid off\n"
{
printf("client: %d off\n", client_pid);
ppre = NULL ;//前驱指针
pcur = plist ;
while(pcur && pcur ->m_id != client_pid) //遍历到该客户端PID
{
ppre = pcur ;
pcur = pcur ->m_next ;
} if(pcur == NULL)
{
printf("not exist!\n");
continue ;
}else
{
//删除节点
if(ppre == NULL)
{
plist = pcur ->m_next ;
}else
{
ppre ->m_next = pcur ->m_next ;
} //关闭文件
close(pcur ->m_send) ;
close(pcur ->m_recv) ; //把 pcur ->m_recv 从读集合中删除
FD_CLR(pcur ->m_recv, &rd_sets); free(pcur); //释放内存
printf("clear ok !\n"); }
}
} //2、遍历链表,监听其他的管道文件(用于服务器和客户端通信)句柄
pcur = plist ;
while(pcur)
{
if(FD_ISSET(pcur ->m_recv, &bak_sets))// translate
{
memset(msg, , );
read(pcur -> m_recv, msg, ); dispatch_msg(plist,pcur, msg);
}
pcur = pcur ->m_next ;
}
}
return ;
}

client:

 #include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/select.h>
#include<sys/time.h>
#define PIPE_PATH "/home/soso/Desktop/2-17/FIFO"
#define PIPE_NAME "server.fifo"
int main(int argc, char* argv[])
{
//1、向服务器通知上线下线信息 //打开上下线管道
int fd_server ;
char path_name[]="";
char fifo_name[] ;
char msg[] ="" ;
char fifo1[], fifo2[] ;
int fd_recv, fd_send ;
sprintf(path_name, "%s/%s", PIPE_PATH, PIPE_NAME); fd_server = open(path_name, O_WRONLY);
if(fd_server == -)
{
printf("open fail!\n");
exit() ;
} // 建造读写管道 pid_r.fifo pid_w.fifo
//
memset(fifo_name, , );
sprintf(fifo_name, "%u_r.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); strcpy(fifo1, path_name);
if(- == mkfifo(path_name, ) )
{
printf("mkfif fail: %s\n", path_name);
exit() ;
} printf("%s open\n", path_name); memset(fifo_name, , );
sprintf(fifo_name, "%u_w.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); strcpy(fifo2, path_name);
if(mkfifo(path_name, ) == - )
{ printf("mkfif fail: %s\n", path_name);
exit() ;
}
printf("%s open\n", path_name); printf("mkfifo over!\n"); //发送上线信息
sprintf(msg, "%u on\n", getpid());
printf("msg: %s\n", msg); write(fd_server, msg, strlen(msg)); //
memset(fifo_name, , );
sprintf(fifo_name, "%u_r.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); fd_recv = open(path_name, O_RDONLY); memset(fifo_name, , );
sprintf(fifo_name, "%u_w.fifo", getpid());
memset(path_name, , sizeof(path_name));
sprintf(path_name, "%s/%s", PIPE_PATH, fifo_name); fd_send = open(path_name, O_WRONLY); printf("fifo open %d %d\n", fd_send, fd_recv); fd_set rd_sets ;
FD_ZERO(&rd_sets);
while()
{
FD_SET(, &rd_sets);
FD_SET(fd_recv, &rd_sets); select(, &rd_sets, NULL, NULL, NULL); if(FD_ISSET(, &rd_sets))
{
memset(msg, , sizeof(msg)) ;
sprintf(msg, "from %u: ", getpid());
if(read(, msg + strlen(msg), - strlen(msg) ) == )
{
printf("off!\n");
memset(msg, , sizeof(msg));
sprintf(msg, "%d off\n", getpid());
write(fd_server, msg, strlen(msg)); close(fd_send);
close(fd_recv); unlink(fifo1);
unlink(fifo2);
break ;
}
write(fd_send, msg, strlen(msg)); }
if(FD_ISSET(fd_recv, &rd_sets))
{
memset(msg, , sizeof(msg)) ;
read(fd_recv, msg, );
write(, msg, strlen(msg));
}
}
}

1 server - n clients 模型实现(select)的更多相关文章

  1. linux下多路复用模型之Select模型

    Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...

  2. Windows socket I/O模型 之 select(2)

    在Windows socket I/O模型 之  select(1)中.我们仅仅是在console中简单的模拟了select的处理方法. 还有非常多特性不能改动.比方仅仅能写,不能读. 没使用线程.也 ...

  3. SQL Server 中心订阅模型(多发布单订阅)

    原文:SQL Server 中心订阅模型(多发布单订阅) 大多数SQL Server 复制拓扑都是基于中心发布模型,它是由一个发布复制到一个或者多个订阅.另一个复制模型是中心订阅模型,它使用事务复制由 ...

  4. [编织消息框架][网络IO模型]NIO(select and poll)

    上面测试论证系统内核在read data时会阻塞,如果我们在把第一个阶段解决掉那么性能就会提高 NIO 编程 JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度.实 ...

  5. Socket I/O模型之select模型

    socket网络编程中有多种常见的I/O模型: 1.blocking阻塞 2.nonblocking非阻塞 3.I/O multiplexing复用 4.signal driven 5.asynchr ...

  6. 1高并发server:多路IO之select

     1 select A:select能监听的文件描写叙述符个数受限于FD_SETSIZE,一般为1024.单纯改变进程打开 的文件描写叙述符个数并不能改变select监听文件个数 B:解决1024 ...

  7. IO模型,非阻塞IO模型,select实现多路复用

    1. IO阻塞模型 IO问题: 输入输出 我要一个用户名用来执行登陆操作,问题用户名需要用户输入,输入需要耗时, 如果输入没有完成,后续逻辑无法继续,所以默认的处理方式就是 等 将当前进程阻塞住,切换 ...

  8. socket阻塞与非阻塞,同步与异步、I/O模型,select与poll、epoll比较

    1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步/异步主要针对C端: 同步:      所谓同步,就 ...

  9. Hadoop的Server及其线程模型分析

    早期的一篇文章,针对Hadoop 2.6.0. 一.Listener Listener线程,当Server处于运行状态时,其负责监听来自客户端的连接,并使用Select模式处理Accept事件. 同时 ...

随机推荐

  1. uva 699 The Falling Leaves dfs实现

    额,刘汝佳小白里面的配套题目. 题目求二叉树同垂直线上结点值的和. 可以用二叉树做,挺水的其实. 尝试使用dfs实现了:开一个大点的数组,根节点为最中间那点,然后读取时就可以进行和的计算了. 代码: ...

  2. linux 文件夹权限及umask

    先创建一个目录,看看权限: $ ll 总用量 drwxrwxr-x huangxm huangxm 2月 : ./ drwxr-xr-x huangxm huangxm 2月 : ../ drwxrw ...

  3. Apache配置文件中的deny与allow小结

    今天在公司配置Zend本地Apache环境的时候,发现在zend.conf中的权限控制中的几段句子,如下所示: 复制代码代码如下: <Location /server-status>    ...

  4. JavaScript 三种绑定事件方式之间的区别

    JavaScript三种绑定事件的方式: 1. <div id="btn" onclick="clickone()"></div> // ...

  5. Ehcache(2.9.x) - API Developer Guide, Basic Caching

    Creating a CacheManager All usages of the Ehcache API start with the creation of a CacheManager. The ...

  6. 晒下自己App广告平台积分墙收入,顺便点评几个广告平台

    这是我之前发在爱开发App源码论坛的文章.分享了我从2011年到现在移动广告方面的收入和一些心得. 产品类型:FC.街机模拟器类App游戏 广告平台:万普世纪 广告形式:积分墙,用户先试玩几次,再玩需 ...

  7. jquery之音乐均衡器

    制作这个音乐均衡器需要一个equalizer插件(插件我已经上传),下面介绍一下网页的BGM的相关属性: hidden="true"表示隐藏播放,即不显示播放器的外观,若要想显示, ...

  8. windows server 2008下装SQL 2008R2x64

    1. 在windows server 2008下装SQL 2008出现 This SQL Server Setup media is not supported on a X64 system 使用虚 ...

  9. Memcached 安装配置

    安装: memcached -d install memcached -d start net start "Memcached Server" 卸载: memcached -d ...

  10. JAVA 实现通过URL下载文件到本地库

    /** * TODO 下载文件到本地 * @author nadim * @date Sep 11, 2015 11:45:31 AM * @param fileUrl 远程地址 * @param f ...