拓扑结构:
各个客户端创建读写管道,通过“上下线信息管道”向服务器发送上下线信息和读写管道名称。服务器接受信息,修改链表(存储客户端信息)。客户端、服务器打开读写管道,服务器通过“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
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#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));
}
}
}
linux下多路复用模型之Select模型
Linux关于并发网络分为Apache模型(Process per Connection (进程连接) ) 和TPC , 还有select模型,以及poll模型(一般是Epoll模型) Select模 ...
Windows socket I/O模型 之 select(2)
在Windows socket I/O模型 之 select(1)中.我们仅仅是在console中简单的模拟了select的处理方法. 还有非常多特性不能改动.比方仅仅能写,不能读. 没使用线程.也 ...
SQL Server 中心订阅模型(多发布单订阅)
原文:SQL Server 中心订阅模型(多发布单订阅) 大多数SQL Server 复制拓扑都是基于中心发布模型,它是由一个发布复制到一个或者多个订阅.另一个复制模型是中心订阅模型,它使用事务复制由 ...
[编织消息框架][网络IO模型]NIO(select and poll)
上面测试论证系统内核在read data时会阻塞,如果我们在把第一个阶段解决掉那么性能就会提高 NIO 编程 JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度.实 ...
Socket I/O模型之select模型
socket网络编程中有多种常见的I/O模型: 1.blocking阻塞 2.nonblocking非阻塞 3.I/O multiplexing复用 4.signal driven 5.asynchr ...
1高并发server:多路IO之select
1 select A:select能监听的文件描写叙述符个数受限于FD_SETSIZE,一般为1024.单纯改变进程打开 的文件描写叙述符个数并不能改变select监听文件个数 B:解决1024 ...
IO模型,非阻塞IO模型,select实现多路复用
1. IO阻塞模型 IO问题: 输入输出 我要一个用户名用来执行登陆操作,问题用户名需要用户输入,输入需要耗时, 如果输入没有完成,后续逻辑无法继续,所以默认的处理方式就是 等 将当前进程阻塞住,切换 ...
socket阻塞与非阻塞,同步与异步、I/O模型,select与poll、epoll比较
1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 同步/异步主要针对C端: 同步: 所谓同步,就 ...
Hadoop的Server及其线程模型分析
早期的一篇文章,针对Hadoop 2.6.0. 一.Listener Listener线程,当Server处于运行状态时,其负责监听来自客户端的连接,并使用Select模式处理Accept事件. 同时 ...
随机推荐
UITableView使用
@多组数据和多组cell 1根数组内含数组时,只可以开启一组cell,内含多个数组时,可以开启多组cell 2多个数组开启多个cell时,既可以竖向开启,也可以横向里层开启,就好比猜题的模式 @注意点 ...
CSS - Transform(Translate) abnormal shadow in firefox
问题:当在Firefox中实现动画translate时,会出现虚影的状况: 经查找相关的解决方法,父容器添加样式:outline: 1px solid transparent;//即可解决问题. 但不 ...
【转】MSSQL获取指定表的列名信息,描述,数据类型,长度
/* --作用:根据特定的表名查询出字段,以及描述,数据类型,长度,精度,是否自增,是否为空等信息 --作者:wonder QQ:37036846 QQ群:.NET顶级精英群 ID:124766907 ...
AndroidStudio1.4 manifest 中注册Activity时的错误提示解决办法
问题截图如下: 解决办法截图如下: 1: File->setting->Editor->Language Injections到如下界面 2:双击右侧选中的Item进入编辑界面 3: ...
Struts 上下文
Struts 上下文 ActionContext .ServletActionContext 是继承关系 ActionContext ActionContext context = Action ...
CodeForces 482C Game with Strings
意甲冠军: n一定长度m串 隐藏的字符串相等的概率 然后,你猜怎么着玩家隐藏的字符串 的询问字符串的一个位置 再不断的知道一些位置后 游戏者就能够确定藏起来的串是什么 问 游戏者的期望步 ...
Coffee
Coffee 从接触Spring 到现在已经差不多2年多了,期间用它做过几个项目,从个人使用角度来说,Spring无疑是非常的成熟和方便的,但是知道怎么用,却不知道原理是码农和攻城师的区别,现在准备自 ...
Openjudge-计算概论(A)-鸡兔同笼
描述一个笼子里面关了鸡和兔子(鸡有2只脚,兔子有4只脚,没有例外).已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至多有多少只动物输入第1行是测试数据的组数n,后面跟着n行输入.每组测试数 ...
【转】Java中super和this的几种用法与区别
1. 子类的构造函数如果要引用super的话,必须把super放在函数的首位. class Base { Base() { System.out.println("Base&qu ...
JS第二部分--DOM文档对象模型
一.DOM的概念 二.DOM可以做什么 三.DOM对象的获取 四.事件的介绍 五.DOM节点标签样式属性的操作 六.DOM节点对象对值的操作 七.DOM节点-标签属性的操作(例如id class sr ...