拓扑结构:

各个客户端创建读写管道,通过“上下线信息管道”向服务器发送上下线信息和读写管道名称。服务器接受信息,修改链表(存储客户端信息)。客户端、服务器打开读写管道,服务器通过“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. Android(java)学习笔记73:线程组的概述和使用

    Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制. (1)默认情况下,所有的线程都属于主线程组. public final Thre ...

  2. AspxGridView整理文档【转】

    ASPxGridView属性:概述设置(Settings) <Settings GridLines="Vertical" : 网格样式 Vertical, Both, Non ...

  3. 临时2级页表的初始化过程 head_32.S 相关代码解释

    page_pde_offset = (__PAGE_OFFSET >> 20); /* __PAGE_OFFSET是0xc0000000,page_pde_offset = 3072 = ...

  4. linux下获取ip

    如果打开虚拟机 没有ip置灰显示了 lo 可以使用dhclient自动获取ip 如果想开机就自动获取ip: vim /etc/rc.d/rc.local 在这里插入dhclient命令

  5. CSS3秘笈第三版涵盖HTML5学习笔记1~5章

    第一部分----CSS基础知识 第1章,CSS需要的HTML HTML越简单,对搜索引擎越友好 div是块级元素,span是行内元素 <section>标签包含一组相关的内容,就像一本书中 ...

  6. Commons JXPath - Modifying Object Graphs

    JXPath 除了可以 XPath 语法访问 JavaBeans.DOM/JDOM,也可以对其属性赋值. 以下面的 JavaBeans 为例. package com.huey.jxpath; imp ...

  7. Redis 命令 - Transactions

    DISCARD Discard all commands issued after MULTI 127.0.0.1:6379> MGET bank:A:account bank:B:accoun ...

  8. VSFTPD无法上传的解决方法

    搭建好FTP之后就没有去测试了,今天去试了一下上传的时候发生错误了,无法上传,提示“553 Could not create file”错误, 上网找了一些资料,发现很多都说是权限和防火墙的问题,但是 ...

  9. 需要MARK一下,奇怪的ANDROID SDK自带的APK加密功能的问题

    花了两天时间,各种调试APP,发现问题不在于代码. 在于用了SDK里的加密,导致运行其中一个多线程中的ACTIVITY, 就会黑屏,返回按钮也没用. 发现这个问题的思路是因为,我发现连手机直接调试,一 ...

  10. iOS 在viewController中监听Home键触发以及重新进入界面的方法

    第一步:创建2个NSNotificationCenter监听 [[NSNotificationCenter defaultCenter] addObserver:self selector:@sele ...