在 Linux 上實現基於 Socket 的多進程即時通訊
周欣 (jones_zhou@yahoo.com.cn),
軟體工程師
簡介: 套介面(Socket)為目前 Linux 上最為廣泛使用的一種的處理序間通訊機制。但是它不能直接用來多進程之間的相互即時通訊。本文提出一個基於 Socket 的多進程之間通訊的實現方法,並給出樣本程式的實現和說明。
本文的標籤: linux, socket, 上實現基於, 在,
的多進程即時通訊
標記本文!
發布日期: 2005 年 3 月 01 日
層級: 初級
訪問情況 : 8722 次瀏覽
評論: 1 (查看 | 添加評論 - 登入)
平均分 (21個評分)
為本文評分
套介面(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] 4303ClientA::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 5ClientB::succeed in connecting with serverSERVER::char=1 to Client B on socket5ClientB::char from Client A =1SERVER::char=2 to Client A on socket4ClientA::char from Client B =2
程式清單下載:server.c,clientA.c
關於作者
周欣,北京郵電大學電腦系碩士畢業, 現在日本從事軟體開發,通過
jones_zhou@yahoo.com.cn 可以和他聯絡。
轉載自http://www.ibm.com/developerworks/cn/linux/l-socket-ipc/index.html