一、UNIX Domain Socket IPC
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIXDomain Socket也是可靠的,消息既不会丢失也不会顺序错乱。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在 文件系统中的路径 ,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
通信的流程跟前面说过的tcp/udp 是类似的,下面直接来看程序:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
/************************************************************************* > File Name: echoser_tcp.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sun 03 Mar 2013 06:13:55 PM CST ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <sys/un.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while ( 0 ); void echo_ser( int conn) { char recvbuf[ 1024 ]; int n; while ( 1 ) { memset(recvbuf, 0 , sizeof (recvbuf)); n = read(conn, recvbuf, sizeof (recvbuf)); if (n == - 1 ) { if (n == EINTR) continue ; ERR_EXIT( "read error" ); } else if (n == 0 ) { printf( "client close\n" ); break ; } fputs(recvbuf, stdout); write(conn, recvbuf, strlen(recvbuf)); } close(conn); } /* unix domain socket与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍。*/ int main( void ) { int listenfd; if ((listenfd = socket(PF_UNIX, SOCK_STREAM, 0 )) < 0 ) ERR_EXIT( "socket error" ); unlink( "/tmp/test socket" ); //地址复用 struct sockaddr_un servaddr; memset(&servaddr, 0 , sizeof (servaddr)); servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, "/tmp/test socket" ); if (bind(listenfd, ( struct sockaddr *)&servaddr, sizeof (servaddr)) < 0 ) ERR_EXIT( "bind error" ); if (listen(listenfd, SOMAXCONN) < 0 ) ERR_EXIT( "listen error" ); int conn; pid_t pid; while ( 1 ) { conn = accept(listenfd, NULL , NULL ); if (conn == - 1 ) { if (conn == EINTR) continue ; ERR_EXIT( "accept error" ); } pid = fork(); if (pid == - 1 ) ERR_EXIT( "fork error" ); if (pid == 0 ) { close(listenfd); echo_ser(conn); exit(EXIT_SUCCESS); } close(conn); } return 0 ; } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
/************************************************************************* > File Name: echocli_tcp.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sun 03 Mar 2013 06:13:55 PM CST ************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <sys/un.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while ( 0 ); void echo_cli( int conn) { char sendbuf[ 1024 ] = { 0 }; char recvbuf[ 1024 ] = { 0 }; while (fgets(sendbuf, sizeof (sendbuf), stdin) != NULL ) { write(conn, sendbuf, strlen(sendbuf)); read(conn, recvbuf, sizeof (recvbuf)); fputs(recvbuf, stdout); memset(recvbuf, 0 , sizeof (recvbuf)); memset(sendbuf, 0 , sizeof (sendbuf)); } close(conn); } int main( void ) { int sock; if ((sock = socket(PF_UNIX, SOCK_STREAM, 0 )) < 0 ) ERR_EXIT( "socket error" ); struct sockaddr_un servaddr; memset(&servaddr, 0 , sizeof (servaddr)); servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, "/tmp/test socket" ); if (connect(sock, ( struct sockaddr *)&servaddr, sizeof (servaddr)) < 0 ) ERR_EXIT( "connect error" ); echo_cli(sock); return 0 ; } |
server 使用fork 的形式来接受多个连接,server调用bind 会创建一个文件,如下所示:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ls -l /tmp/test\ socket
srwxrwxr-x 1 simba simba 0 Jun 12 15:27 /tmp/test socket
即文件类型为s,表示SOCKET文件,与FIFO(命名管道)文件,类型为p,类似,都表示内核的一条通道,读写文件实际是在读写内核通道。程序中调用unlink 是为了在开始执行程序时删除以前创建的文件,以便在重启服务器时不会提示address in use。其他方面与以前说过的回射客户服务器程序没多大区别,不再赘述。
三、UNIX域套接字编程注意点
1、bind成功将会创建一个文件,权限为0777 & ~umask
2、sun_path最好用一个绝对路径
3、UNIX域协议支持流式套接口与报式套接口
4、UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN。
四、socketpair 函数
功能:创建一个全双工的流管道
原型 int socketpair(int domain, int type, int protocol, int sv[2]);
参数
domain: 协议家族
type: 套接字类型
protocol: 协议类型
sv: 返回套接字对
返回值:成功返回0;失败返回-1
实际上socketpair 函数跟pipe 函数是类似的,也只能在同个主机上具有亲缘关系的进程间通信,但pipe 创建的匿名管道是半双工的,而socketpair 可以认为是创建一个全双工的管道。
可以使用socketpair 创建返回的套接字对进行父子进程通信:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
/************************************************************************* > File Name: echoser.c > Author: Simba > Mail: dameng34@163.com > Created Time: Fri 01 Mar 2013 06:15:27 PM CST ************************************************************************/ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while ( 0 ) int main( void ) { int sockfds[ 2 ]; if (socketpair(PF_UNIX, SOCK_STREAM, 0 , sockfds) < 0 ) ERR_EXIT( "sockpair" ); pid_t pid; pid = fork(); if (pid == - 1 ) ERR_EXIT( "fork" ); if (pid > 0 ) { int val = 0 ; close(sockfds[ 1 ]); while ( 1 ) { ++val; printf( " sending data: %d\n" , val); write(sockfds[ 0 ], &val, sizeof (val)); read(sockfds[ 0 ], &val, sizeof (val)); printf( "recv data : %d\n" , val); sleep( 1 ); } } else if (pid == 0 ) { int val; close(sockfds[ 0 ]); while ( 1 ) { read(sockfds[ 1 ], &val, sizeof (val)); ++val; write(sockfds[ 1 ], &val, sizeof (val)); } } return 0 ; } |
输出如下:
simba@ubuntu:~/Documents/code/linux_programming/UNP/socket$ ./socketpair
sending data: 1
recv data : 2
sending data: 3
recv data : 4
sending data: 5
recv data : 6
sending data: 7
recv data : 8
sending data: 9
recv data : 10
sending data: 11
recv data : 12
sending data: 13
recv data : 14
sending data: 15
recv data : 16
...................................
即父进程持有sockfds[0] 套接字进行读写,而子进程 持有sockfds[1] 套接字进行读写。
1.非命名的UNIX域套接字
UNIX套接字用于在用一台机器上运行的进程之间通信。UNIX套接字比因特网域套接字的效率更高。UNIX与套接字提供和
数据报两种接口,UNIX域数据报服务是可靠的,就不会丢失消息也不会传递出错。UNIX域套接字是套接字和管道之间的混合物。
为了创建一对非命名的,相互连接的UNXI域套接字,用户可以使用socketopair函数。
#include<sys/socket.h>
int socketpari(int domain, int type, int protocol, int sockfd[2]);
//若成功则返回0,出错则返回-1.
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
int main(void){
int fd[2];
int pid;
char wbuf[16] = "1234567890";
char rbuf[16];
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
perror("socketpair");
return -1;
}
if((pid = fork())<0){
perror("fork");
return -1;
}else if(pid == 0){
//child
close(fd[0]);
if(write(fd[1],wbuf,strlen(wbuf)) < 0){
perror("write");
exit(-1);
}
}else{
//parent
close(fd[1]);
if(read(fd[0],rbuf,16) < 0){
perror("read");
exit(-1);
}
printf("%s\n",rbuf);
}
return 0;
}
执行结果:
yan@yan-vm:~$ ./a.out
1234567890
2.命名UNIX域套接字:服务端与客户端都有bind
虽然socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。
我们可以命名unix域套接字,并可将其用于告示服务。但是要注意的是,UNXI与套接字使用的地址不同与因特网域套接字。
UNIX域套接字的地址由sockaddr_un结构表示。
在linux2.4.22中,sockaddr_un结构按下列形式定义在有文件<sys/un.h>中。
struct sockaddr_un{
sa_family_t sun_family; //AF_UNIX
char sun_path[108]; //pathname
};
sun_path成员包含一路经名,当我们将一个地址绑定至UNIX域套接字时,系统用该路经名创建一类型为S_IFSOCK文件。
该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。
如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须
确保在应用程序终止前,对该文件执行解除链接操作。
服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器
进程连接;服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。这种风格和因特网套接字
的操作很像。
实践:
server.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
int main(void){
int fd,clientfd;
int client_addr_len = sizeof(struct sockaddr_un);
struct sockaddr_un un,clientun;
char path[32]="serversocketfile";
int addr_len = sizeof(struct sockaddr_un);
char rbuf[32];
char wbuf[32] = "i am server.";
//create a UNIX domain stream socket
if((fd = socket(AF_UNIX,SOCK_STREAM,0)) < 0){
perror("socket");
return -1;
}
//in case it already exists
unlink(path);
//fill in socket address structure
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strncpy(un.sun_path,path,32);
//bind the name to the descriptor
if(bind(fd, (struct sockaddr*)&un, addr_len) < 0){
perror("bind");
return -1;
}
if(listen(fd, 10) < 0){
perror("listen");
return -1;
}
if((clientfd = accept(fd,(struct sockaddr*)&clientun,(socklen_t*)&client_addr_len)) < 0 ){
perror("accept");
return -1;
}
printf("client is:%s\n",clientun.sun_path);
if(read(clientfd,rbuf,32) < 0){
perror("read");
return -1;
}
printf("receive msg:%s\n",rbuf);
if(write(clientfd,wbuf,strlen(wbuf)+1) < 0){
perror("write");
return -1;
}
unlink(path);
return 0;
}
client.c
#include <stdio.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <string.h>
int main(void){
int fd;
struct sockaddr_un un;
char path[32] = "clientsocketfile";
char serverpath[32] = "serversocketfile"; // 与服务端一样
int addr_len = sizeof(struct sockaddr_un);
char wbuf[32] = "i am client.";
char rbuf[32];
if((fd = socket(AF_UNIX,SOCK_STREAM,0))<0){
perror("socket");
return -1;
}
memset(&un,0,sizeof(un));
un.sun_family = AF_UNIX;
strncpy(un.sun_path,path,32);
unlink(path);
if(bind(fd,(struct sockaddr*)&un,addr_len)<0){
perror("bind");
return -1;
}
//fill socket adress structure with server's address
memset(&un,0,sizeof(un));
un.sun_family = AF_UNIX;
strncpy(un.sun_path,serverpath,32);
if(connect(fd,(struct sockaddr*)&un,addr_len) < 0){
perror("connect");
return -1;
}
if(write(fd,wbuf,strlen(wbuf)+1)<0){
perror("write");
return -1;
}
if(read(fd,rbuf,32) < 0){
perror("write");
return -1;
}
printf("receive msg:%s\n",rbuf);
unlink(path);
return -1;
}
先运行server,再运行client。
运行结果:
server.c:
yan@yan-vm:~/apue$ ./unixserver
client is:clientsocketfile
receive msg:i am client.
client.c:
yan@yan-vm:~/apue$ ./unixclient
receive msg:i am server.