进程之间的通讯使用很频繁,在本地主机上两个不同的进程之间需要通讯时,我们可以采用unix域套接字来通讯,虽然因特网域套接字也可以达到同一目的,但是unix域套接字效率更高,主要体现如下:
(1)UNIX域套接字仅仅复制数据
(2)不像因特网域的套接字,它不需要处理协议,不需要产生顺序号,不需要发送确认报文,不需要添加或者删除网络头部,无需计算校验和。
UNIX域套接字用于同一台主机上的进程间通讯时,提供了STREAM和DIAGRAM两种,UNIX域的数据服务是可靠的,既不会丢失也不会传错。
非命令的UNIX域套接字只能用于亲缘关系的父子进程之间通讯:
为了创建一对非命名的,相互连接的UNXI域套接字,用户可以使用socketpair函数
参数介绍:
第1个参数d(domain),表示协议族,只能为AF_LOCAL或者AF_UNIX;
第2个参数type,表示类型,只能为0。
第3个参数protocol,表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。用SOCK_STREAM建立的套接字对是管道流,与一般的管道相区别的是,套接字对建立的通道是双向的,即每一端都可以进行读写。不管是数据流还是数据报协议,unix域套接字都是可靠的,不丢包的。
第4个参数sv,用于保存建立的套接字对。
示例代码如下
#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <sys/types.h> #include <unistd.h> int main(void) { int i_fd_arr[2]; int i_pid; char psz_wbuf[16] = "0123456789"; char psz_rbuf[16] = {0}; if ( socketpair( AF_UNIX, SOCK_STREAM, 0, i_fd_arr ) < 0 ) { perror( "socketpair" ); return -1; } if ( ( i_pid = fork() ) < 0 ) { perror( "fork" ); return -1; } else if ( 0 == i_pid ) { //child close( i_fd_arr[0] ); if ( write( i_fd_arr[1], psz_wbuf, strlen( psz_wbuf ) ) < 0 ) { perror( "write" ); exit( -1 ); } memset( psz_rbuf, 0, sizeof( psz_rbuf ) ); if ( read( i_fd_arr[1], psz_rbuf, 16 ) < 0 ) { perror( "read" ); exit( -1 ); } printf( "child read: %s\n", psz_rbuf ); } else { //parent close( i_fd_arr[1] ); if ( read( i_fd_arr[0], psz_rbuf, 16 ) < 0 ) { perror( "read" ); exit( -1 ); } printf( "parent read: %s\n", psz_rbuf ); memset( psz_wbuf, 0, sizeof( psz_wbuf ) ); strncpy( psz_wbuf, "9876543210", sizeof( psz_wbuf ) - 1 ); if ( write( i_fd_arr[0], psz_wbuf, strlen( psz_wbuf ) ) < 0 ) { perror( "write" ); exit( -1 ); } } return 0; }
其实unix套接字是unix和管道结合的产物。本示例中子进程关闭读端,然后向父进程发送“0123456789”字符串,发完之后读取父进程发来的数据,而父进程中是先关闭写端,先读取子进程发来的字符串,然后向子进程发送“9876543210”字符串,本例是一个全双工的通讯。
命名UNIX域套接字适用于非亲缘关系的进程之间通讯
unix域套接字结构体如下:
struct sockaddr_un{
sa_family_t sun_family;
char sun_path[108];
};
sun_path成员包含一路经名,当我们将一个地址绑定至UNIX域套接字时,系统用该路经名创建一类型为S_IFSOCK文件。
该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。
如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。
服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器进程连接;服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。这种风格和因特网套接字的操作很像。
tcp示例
server.c
#include <stdio.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> #include <unistd.h> int main(void) { int i_listenfd = 0, i_clientfd = 0; struct sockaddr_un addr_server, addr_client; char psz_path[32] = "./server_unixsocket_file"; int i_caddr_len = sizeof(struct sockaddr_un); int i_saddr_len = sizeof(struct sockaddr_un); char psz_rbuf[32] = {0}; char psz_wbuf[32] = "i am server."; int i_readlen = 0; //create a UNIX domain stream socket if ( ( i_listenfd = socket( AF_UNIX, SOCK_STREAM, 0 ) ) < 0 ) { perror( "socket" ); return -1; } //in case it already exists unlink( psz_path ); //fill in socket address structure memset( &addr_server, 0, sizeof( addr_server ) ); addr_server.sun_family = AF_UNIX; strncpy( addr_server.sun_path, psz_path, sizeof( addr_server.sun_path ) - 1 ); //bind the name to the descriptor if ( bind( i_listenfd, ( struct sockaddr * )&addr_server, i_saddr_len ) < 0 ) { perror( "bind" ); return -1; } if ( listen( i_listenfd, 10 ) < 0 ) { perror( "listen" ); return -1; } while(1) { if ( ( i_clientfd = accept( i_listenfd, ( struct sockaddr * )&addr_client, ( socklen_t * )&i_caddr_len ) ) < 0 ) { perror("accept"); return -1; } printf( "client is: %s\n", addr_client.sun_path ); if ( ( i_readlen = read( i_clientfd, psz_rbuf, sizeof( psz_rbuf ) - 1 ) ) < 0 ) { perror( "read" ); return -1; } psz_rbuf[i_readlen] = '\0'; printf( "receive msg:%s\n", psz_rbuf ); if ( write( i_clientfd, psz_wbuf, strlen( psz_wbuf ) + 1 ) < 0 ) { perror("write"); return -1; } } unlink( psz_path ); return 0; }
client.c
#include <stdio.h> #include <sys/un.h> #include <sys/socket.h> #include <string.h> #include <unistd.h> int main(void) { int i_fd = 0; struct sockaddr_un addr; char psz_path[32] = "./client_unixsocket_file"; char serverpath[32] = "./server_unixsocket_file"; int i_addr_len = sizeof( struct sockaddr_un ); char psz_wbuf[32] = "i am client."; char psz_rbuf[32] = {0}; int i_readlen = 0; if ( ( i_fd = socket( AF_UNIX, SOCK_STREAM, 0 ) ) < 0 ) { perror("socket"); return -1; } memset( &addr, 0, sizeof( addr ) ); addr.sun_family = AF_UNIX; strncpy( addr.sun_path, psz_path, sizeof( addr.sun_path ) - 1 ); unlink( psz_path ); if ( bind( i_fd, ( struct sockaddr * )&addr, i_addr_len ) < 0 ) { perror("bind"); return -1; } //fill socket adress structure with server's address memset( &addr, 0, sizeof( addr ) ); addr.sun_family = AF_UNIX; strncpy( addr.sun_path, serverpath, sizeof( addr.sun_path ) - 1 ); if ( connect( i_fd, ( struct sockaddr * )&addr, i_addr_len ) < 0 ) { perror("connect"); return -1; } if ( write( i_fd, psz_wbuf, strlen( psz_wbuf ) + 1 ) < 0 ) { perror( "write" ); return -1; } if ( ( i_readlen = read( i_fd, psz_rbuf, sizeof( psz_rbuf ) - 1 ) ) < 0 ) { perror("write"); return -1; } psz_rbuf[i_readlen] = '\0'; printf( "receive msg:%s\n", psz_rbuf ); unlink( psz_path ); return -1; }
其实和通用的因特网套接字编程差不多,只是在客户端使用时,要为客户端设置中文路径,即采用bind绑定客户端路径,不设路径,那么例子中服务器输出客户端为空。
UDP示例
server.c
#include <stdio.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> #include <unistd.h> int main(void) { int i_listenfd = 0/*, i_clientfd = 0*/; struct sockaddr_un addr_server, addr_client; char psz_path[32] = "./server_unixsocket_file"; int i_caddr_len = sizeof(struct sockaddr_un); int i_saddr_len = 0; char psz_rbuf[32] = {0}; char psz_wbuf[32] = "i am server."; int i_readlen = 0; //create a UNIX domain stream socket if ( ( i_listenfd = socket( AF_UNIX, SOCK_DGRAM, 0 ) ) < 0 ) { perror( "socket" ); return -1; } //in case it already exists unlink( psz_path ); //fill in socket address structure memset( &addr_server, 0, sizeof( addr_server ) ); addr_server.sun_family = AF_UNIX; strncpy( addr_server.sun_path, psz_path, sizeof( addr_server.sun_path ) - 1 ); //bind the name to the descriptor i_saddr_len = strlen( addr_server.sun_path ) + sizeof( addr_server.sun_family ); if ( bind( i_listenfd, ( struct sockaddr * )&addr_server, i_saddr_len ) < 0 ) { perror( "bind" ); return -1; } while(1) { i_readlen = recvfrom( i_listenfd, psz_rbuf, sizeof( psz_rbuf ) - 1, 0, ( struct sockaddr * )&addr_client, ( socklen_t *)&i_caddr_len ); if ( i_readlen < 0 ) { perror( "read" ); return -1; } printf( "client is: %s\n", addr_client.sun_path ); psz_rbuf[i_readlen] = '\0'; printf( "receive msg:%s\n", psz_rbuf ); if ( sendto( i_listenfd, psz_wbuf, strlen( psz_wbuf ) + 1, 0, ( struct sockaddr * )&addr_client, i_caddr_len ) < 0 ) { perror( "write" ); return -1; } } unlink( psz_path ); return 0; }
client.c
#include <stdio.h> #include <sys/un.h> #include <sys/socket.h> #include <string.h> #include <unistd.h> int main(void) { int i_fd = 0; struct sockaddr_un addr; char psz_clientpath[32] = "./client_unixsocket_file"; char psz_serverpath[32] = "./server_unixsocket_file"; int i_addr_len = 0; char psz_wbuf[32] = "i am client."; char psz_rbuf[32] = {0}; int i_readlen = 0; if ( ( i_fd = socket( AF_UNIX, SOCK_DGRAM, 0 ) ) < 0 ) { perror("socket"); return -1; } memset( &addr, 0, sizeof( addr ) ); addr.sun_family = AF_UNIX; strncpy( addr.sun_path, psz_clientpath, sizeof( addr.sun_path ) - 1 ); unlink( psz_clientpath ); i_addr_len = strlen( addr.sun_path ) + sizeof( addr.sun_family ); if ( bind( i_fd, ( struct sockaddr * )&addr, i_addr_len ) < 0 ) { perror("bind"); return -1; } //fill socket adress structure with server's address memset( &addr, 0, sizeof( addr ) ); addr.sun_family = AF_UNIX; strncpy( addr.sun_path, psz_serverpath, sizeof( addr.sun_path ) - 1 ); i_addr_len = strlen( addr.sun_path ) + sizeof( addr.sun_family ); if ( sendto( i_fd, psz_wbuf, strlen( psz_wbuf ) + 1, 0, ( struct sockaddr * )&addr, i_addr_len ) < 0 ) { perror( "write" ); return -1; } if ( ( i_readlen = recvfrom( i_fd, psz_rbuf, sizeof( psz_rbuf ) - 1, 0, ( struct sockaddr * )&addr, ( socklen_t * )&i_addr_len ) ) < 0 ) { perror("write"); return -1; } psz_rbuf[i_readlen] = '\0'; printf( "receive msg:%s\n", psz_rbuf ); unlink( psz_clientpath ); return -1; }此示例中,如果客户端不绑定地址,服务器端不能向客户端发送信息,只能从客户端发送信息给服务器,如果要实现双端通信,一定要给客户端绑定地址。