网络消息通信设计

网络消息通信设计

如何实现系统间通信以及系统内部各个进程间的通信?

1. 系统间或者进程间的通信方式

1 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

2 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

4 消息队列( message queue ): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

6 共享内存( shared memory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

内核会为每一个共享内存段设置了一个shhmid_ds结构体(定义在linux/shm.h中):

struct shmid_ds {    
           struct ipc_perm shm_perm;        /* operation perms */    
           int     shm_segsz;               /* size of segment (bytes) 该共享内存段所占的字节数 */    
           time_t  shm_atime;               /* last attach time */    
           time_t  shm_dtime;               /* last detach time */    
           time_t  shm_ctime;               /* last change time */    
           unsigned short  shm_cpid;        /* pid of creator 创建该共享内存段的进程id*/    
           unsigned short  shm_lpid;        /* pid of last operator  最后访问该共享内存段的进程id*/    
           short   shm_nattch;              /* no. of current attaches  连接到该共享内存段的进程个数*/    
                                            /* the following are private */    
           unsigned short   shm_npages;     /* size of segment (pages) */    
           unsigned long   *shm_pages;      /* array of ptrs to frames -> SHMMAX */    
           struct vm_area_struct *attaches; /* descriptors for attaches */    
   }; 

内核为共享内存机制提供了下面几个系统调用:


创建(或获取)共享内存段id
int shmget ( key_t key, int size, int shmflg ); 
设置某个共享内存段
int shmctl ( int shmqid, int cmd, struct shmid_ds *buf );
连接到某个共享内存段
int shmat ( int shmid, char *shmaddr, int shmflg);
对应共享内存段结构体的shm_nattch自增。
返回值就是获取到的对应共享内存段的地址。
解开到某个共享内存段的连接(记得做这件事)
int shmdt ( char *shmaddr );
对应共享内存段结构体的shm_nattch自减,如果减到0,则内核会清除这个共享内存段。


共享内存段的地址空间:
     使用以上系统调用获取到的共享内存的地址是映射到当前内存地址空间的,其地址处在堆的上方,栈的下方,并且紧靠着在栈下方。


7 套接字( socket ):
套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同系统间的进程通信。



主系统与子系统之间可以通过socket通信,单系统内的进程间通信选用message queue. 而同一进程同步用share Memory 来实现同步。


关于share memory: 为主系统建立一个main share memory key ,如果子系统,就将其share memory key 定义为 (main share memory key) +1 以示区分,实际上由于子系统和主系统是分开的cpu跑的,所以share memory key即使相同也没关系,这里只是为了区分不同系统。
share memory 里存放的是消息队列,设计总共存放的消息是256种类型。每一个消息类型包含了绑定的TCP和UDP信息(包括IP 和port 信息)、消息来源于哪个进程,消息地址addr(node+port),以及已接收的消息列表,待发送的消息列表。有了这个msg信息,发送到网路上,就可以组成一个网络包直接发到对应的socket -> port->process 上了。


为了在网络内识别每个子系统和每个进程,在通信用中, 用Node + port来唯一界定。
Node 设定为8bits: 
BIT7:BIT4 用于识别子系统
BIT3:BIT0 用于识别进程类型,本地的为0x01,子系统的为0x02.
比如: 收到msg Node为0x0601表示的此消息来自于第六个系统,且此系统是主系统;而0x0402表示消息来自第4个系统,是子系统。
Node 的另一个用处在于唯一解析某一系统的IP地址,这样就为全系统内每一子系统分配了唯一的IP地址, 例如 0x0402表示 第4个系统,是子系统。该系统对外的IP确定为:127.0.2.4; 0x0601表示第六个系统,其为主系统, 其IP分配为:127.0.1.6
网络服务地址端口(SAP or port):
这个端口号分为动态分配的端口号和固定的端口, 0x2c00-0x3000为动态可分配端口, 固定的端口分配在0x0c00 - 0x0c10, 最多也就16个端口。
在系统进程启动的过程中通过message initiate,首先为每个进程分配了一个port,然后根据port + node ,从share memory获取一个消息句柄handler,也就是在消息表中的位置,这里叫做queue,这样为每一个进程建立了一个网络消息队列queue。然后通过select函数对此进程的消息进行非阻塞处理。


这样SAP+NODE 组成一个32位的网络唯一地址,用于在网络通信。



信息发送与接收流程:
发送:
以后任何进程需要发送消息给其他进程或者是网络时,必须先发起一个网络请求,在这个请求中带上此进程的消息队列handler queue,也就是该进程初始化时得到的消息表位置信息;在发送信息之前,首先会对该queue做validation,以判断其是否为 已经在share memory初始化的进程的queue,如果是,就将此queue的信息作为网络发送端(source address , source queue)信息,填入即将要发送的packet header. 然后通过sendto发出去,如果发送成功,就将此queue的已发送信息counter +1,同时将此条信息加入此queue的待发送信息列表。同时,系统对每条信息都有生成一个唯一token,并返回给发送方,这样发送方在需要接收网络返回信息的时候,可以利用此token识别收到的信息是他之前所发送的哪条信息的response. 发送成功后,callback function 将此待发送消息从待发送列表中删除;


接收:
发起接收函数时候,同样要带queue,对此queue进行validate,接收消息相对简单,从此queue的接收消息队列中取出对应的内容即可,如果有token就根据token去取对应的消息返回给callback function ,如果没有token,就取队列中第一条信息送回给callback function; 否则就继续监听网络上的消息,直到timeout。 
信息成功接收到后 此接收到的信息从queue的接收信息列表中删除。


本设计中主系统端口分配如下: 
每一个主系统进程分别对应一个port;
所有子系统消息处理进程为一个共享port (0x0C03);


本设计中子系统端口分配如下:
每一个子系统进程分别对应一个port;
而如果子系统想要发消息给主系统,都统一通过一个程序处理,这个程序叫做Engine,使用的port就是0x0C03.


socket 设计时,可以采用的是FD_CLOEXEC不可继承, TCP(SOCK_STREAM)协议且是非阻塞(O_NONBLOCK)方式。
本设计中,对于本地(local)信息,采用UDP方式,对于系统间的通信,采用了TCP的方式,但是后来发现TCP延时太久,所有网络都采用了UDP方式通信。








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值