在 Linux 上实现基于 Socket 的多进程实时通信

套接口(Socket)为目前Linux上最为广泛使用的一种的进程间通信机制,与其他的Linux通信机制不同之处在于除了它可用于单机内的进程 间通信以外,还可用于不同机器之间的进程间通信。但是由于Socket本身不支持同时等待和超时处理,所以它不能直接用来多进程之间的相互实时通信。

本文提出一个基于Socket的多进程之间通信的实现方法。原理是建立一个进程专门用来做为通信服务器(server)来中转各个进程之间的 通信。它首先启动一个用来监视连接要求的listening Socket,并把它的描述(Descriptor)号加入到一个事先定义好的fd_set的集合中,这个fd_set的集合用来存放listening Socket和后来生成的通信Socket的描述号。Server运用system call select来实时检查是否有数据到达这个集合中的任何一个socket,如果有数据到达listening Socket,则这一定是客户端发起的连接请求,于是生成一个新的通信Socket与该客户端连接,将生成的Socket描述号加入到fd_set的集合 中,将客户端的ID号和与之对应的Socket的描述号记录在ID登记表中。如果有数据到达某个通信Socket,则这一定是某个客户端发起的通信请求, 读出数据并取出收信客户端ID号,在ID登记表中找到与之对应的Socket描述号,将数据通过对应Socket传送到收信客户端。

其他各进程作为客户端(client)。客户端的动作是首先建立通信Socket连接服务器端,然后通过通信Socket进行送信和收信。

下面给出具体的程序实现和说明,

首先给出Server端的程序,在这里假设有两个客户端要进行实时通信,ClientA向ClientB发送字符1,ClientB向ClientA发送字符2。

  #include  <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
int main()
{
  int        rcd ;
  struct sockaddr_un  server_sockaddr ;
  int        backlog ;
  ushort        ci ;
  int        watch_fd_list[3] ;
  fd_set        catch_fd_set ;
  fd_set        watchset ;
  int    new_cli_fd ;
  int   maxfd;
  int    socklen ,server_len;
  struct sockaddr_un  cli_sockaddr ;
  struct {
    char  module_id ;  /* Module ID    */
    int  cli_sock_fd ;  /* Socket ID    */
  } cli_info_t[2] ;
  
  for (ci=0;ci<=1;ci++)
    cli_info_t[ci].cli_sock_fd=-1;
    
  for (ci=0;ci<=2;ci++)
    watch_fd_list[ci]=-1;  
    
  int server_sockfd,client_sockfd;
  
  server_sockfd = socket( AF_UNIX, SOCK_STREAM, 0 ) ;
  server_sockaddr.sun_family = AF_UNIX ;
  strcpy( server_sockaddr.sun_path, "server_socket" ) ;
  server_len=sizeof(server_sockaddr);
  rcd = bind( server_sockfd, ( struct sockaddr * )&server_sockaddr, server_len ) ;
  
  backlog = 5 ;
  rcd = listen( server_sockfd, backlog ) ;
  printf("SERVER::Server is  waitting on socket=%d /n",server_sockfd);
  watch_fd_list[0]=server_sockfd;
  FD_ZERO( &watchset ) ;
  FD_SET( server_sockfd, &watchset ) ;
  maxfd=watch_fd_list[0];

	

 

在上面的程序中,Server生成listening Socket(server_sockfd),初始化Socket监视集合(watchset),并将listening Socket放入Socket监视集合中。

			while (1){
		char ch;
		int fd;
		int nread;
		
		catch_fd_set=watchset;
		rcd = select( maxfd+1, &catch_fd_set, NULL, NULL, (struct timeval *)0 ) ;
		

 

在上面的程序中,Server运用系统调用函数 select来实时检查是否有数据到达Socket监视集合中的任何一个socket。

        if ( rcd < 0 ) {
      printf("SERVER::Server 5 /n");
      exit(1);
    }
    if ( FD_ISSET( server_sockfd, &catch_fd_set ) ) {
      socklen = sizeof( cli_sockaddr ) ;
      new_cli_fd = accept( server_sockfd, ( struct sockaddr * )
        &( cli_sockaddr ), &socklen ) ;
      printf(" SERVER::open communication with  Client %s on socket %d/n", 
           cli_sockaddr.sun_path,new_cli_fd);  
  
      for (ci=1;ci<=2;ci++){
        if(watch_fd_list[ci] != -1) continue;
        else{  
          watch_fd_list[ci] = new_cli_fd;
          break;
        }  
      }  
      FD_SET(new_cli_fd , &watchset ) ;
      if ( maxfd < new_cli_fd ) {
        maxfd = new_cli_fd ;
      }
      
      for ( ci=0;ci<=1;ci++){
        if(cli_info_t[ci].cli_sock_fd == -1) {
          cli_info_t[ci].module_id=cli_sockaddr.sun_path[0];
          cli_info_t[ci].cli_sock_fd=new_cli_fd;
          break;
        }    
      }
        
      continue;  
    }
		

 

在上面的程序中,Server运用系统调用函数FD_ISSET来检查是否有客户端的连接请求到达Listening Socket, 如果返回值大于0,Server生成一个新的通信Socket (new_cli_fd)与客户端连接。将新生成的通信Socket放入Socket监视集合中(FD_SET)。将客户端的信息(ID号和Socket 描述号)保存在注册表cli_info_t中

    for ( ci = 1; ci<=2 ; ci++ ) {
      int      dst_fd = -1 ;
      char      dst_module_id;
      char       src_module_id;
      int    i;
      if (watch_fd_list[ ci ]==-1) continue;
      if ( !FD_ISSET( watch_fd_list[ ci ], &catch_fd_set ) ) {
        continue ;
      }
      ioctl(watch_fd_list[ ci ],FIONREAD,&nread);
      if (nread==0){
        continue;
      }  
      read( watch_fd_list[ ci ], &dst_module_id, 1 ) ;
      for (i=0;i<=1;i++){
        if(cli_info_t[i].module_id == dst_module_id) 
          dst_fd=  cli_info_t[i].cli_sock_fd;  
         if(cli_info_t[i].cli_sock_fd==watch_fd_list[ ci ]) 
        src_module_id=  cli_info_t[i].module_id;    
      }
      read( watch_fd_list[ ci ], &ch, 1 ) ;
      printf("SERVER::char=%c to  Client %c on socket%d/n",ch, dst_module_id,dst_fd);  
      write(dst_fd,&src_module_id, 1 ) ;
      write(dst_fd,&ch, 1 ) ;
    }
  }  
}

 

在上面的程序中,如果有数据到达某个通信Socket,Server则读出数据并取出收信客户端ID号。在ID登记表中找到收信客户端对应的Socket描述号。并将数据通过对应Socket传送到收信客户端

给出客户端 ClientA的程序

ClientB的程序只需将 char dst_module_id='B'; 改为char dst_module_id='A'; char ch='1'; 改为char char ch='2';既可。

#include  <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(){
	
	int client_sockfd;
	int len;
	struct sockaddr_un server_sockaddr,cli_sockaddr;
	int result;
	char dst_module_id='B';
	char ch='1';
	char src_module_id;
	
	
	client_sockfd= socket(AF_UNIX,SOCK_STREAM,0);
	
	cli_sockaddr.sun_family = AF_UNIX ;
	strcpy( cli_sockaddr.sun_path, "A" ) ;
	bind(client_sockfd,(struct sockaddr * )&cli_sockaddr, sizeof( cli_sockaddr ) ) ;
	server_sockaddr.sun_family=AF_UNIX;
	strcpy( server_sockaddr.sun_path, "server_socket" ) ;
	len=sizeof(server_sockaddr);
	
	result = connect(client_sockfd,( struct sockaddr * )&server_sockaddr,len);
	if (result <0){
		printf("ClientA::error on connecting /n");	
		exit(1);
	}
	
	printf("ClientA::succeed in connecting with server/n");
	sleep(10);
	write(client_sockfd,&dst_module_id,1);
	write(client_sockfd,&ch,1); 
	read (client_sockfd,&src_module_id,1); 
	read (client_sockfd,&ch,1); 
	printf("ClientA::char from  Client %c =%c/n", src_module_id,ch);	
	close (client_sockfd);
	
}	

 

下面是样本程序的执行结果

[root@zhou test]# ./server &
[3] 4301
[root@zhou test]# SERVER::Server is  waitting on socket=3
./clientA & ./clientB &
[4] 4302
[5] 4303
ClientA::succeed in connecting with server
 SERVER::open communication with  Client A on socket 4
[root@zhou test]#  SERVER::open communication with  Client B on socket 5
ClientB::succeed in connecting with server
SERVER::char=1 to  Client B on socket5
ClientB::char from  Client A =1
SERVER::char=2 to  Client A on socket4
ClientA::char from  Client B =2

 

程序清单下载:server.c , clientA.c

http://www.ibm.com/developerworks/cn/linux/l-socket-ipc/index.html

 

server端建立sockfd后加入fd_set进行select,发现新的连接进来时我就accept然后将新的fd加入到fd_set中,并且修改 select的第一个参数max_fd,扩大select的范围,我的想法是如果有新连接进来fd_isset(sockfd)应该有反应,如果是已连接的客户端有数据进来的话,某个fd_isset(new_fd)应该有反应,相关的程序见下面.但是现在发现的问题是只能接受一个连接(我的listen 的BACKLOG==10),而且这个连接的数据我能收到,但是如果再有一个连接进来,我的select好象没反应,这是为什么?max_fd应该是第一个new_fd+1应该大于sockfd+1,按我的想法,这时select应该有反应,fd_isset(sockfd)为真. 另外,如果s = max(sockfd, new_fd[n]);这句话注释掉,也就是说max_fd一直等于sockfd+1,新的连接可以进来,但是无论哪个连接都无法收到信息(这点我可以理解,虽然new_fd加入到了fd_set中,但是max_fd还是那么小)....... 应该如何实现呢?另外因为是第一次写socket程序,不知道正常的处理是不是像我这样做的?请大虾们指教

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这是一门linux下c++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++在linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时间,所以老师会在课前先写好代码,主要的时间花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux;(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之间的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等;
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值