这篇文章将要学习UNIX域相关知识,包括UNIX域协议特点、UNIX域地址结构、UNIX字节流回射客户/服务、UNIX域套接字编程注意点等。
1.UNIX域协议特点
(1)UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者约是后者的两倍。
(2)UNIX域套接字可以在同一台主机上各进程之间传递描述符。
(3)UNIX域套接字与传统套接字的区别是用路径名来表示协议族的描述。
2.UNIX域地址结构
1 2 3 4 5 6 |
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family sun_family; /*AF_UNIX*/ char sun_path[UNIX_PATH_MAX]; /*pathname*/ }; |
3.UNIX域字节流回射客户/服务
其基本流程和TCP回射客户/服务是类似的。
- 服务端设计:
(1)首先,创建一个套接字linstenfd,与TCP/UDP创建套接字不同的是,此时socket函数第一个参数协议家族是AF_UNIX,并且使用流式套接字:
int listenfd;
listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
(2)然后初始化地址结构,其地址结构内需要关注两个成员——sun_family、sun_path:
struct sockaddr_un servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaaddr.sun_path, “test_socket”);
(3)接下来进行bind绑定:
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
(4)绑定完之后进行listen监听:
listen(listenfd, SOMAXCONN);
(5)之后就可以在while循环中接收客户端的连接了,accept之后会返回一个已连接套接字conn,其中accept可以不关心对等方的地址,即,后两个参数置为0:
accept(listenfd, NULL, NULL);
(6)连接建立之后,这里就不使用select或epoll的方式来处理了,直接使用fork子进程的方式来进行处理。其中子进程(pid == 0)来处理通信模块echo_srv(子进程不需要处理监听,所以要close(listenfd));父进程负责监听任务(父进程不需要处理连接,所以close(conn)即可)。
服务端源码:
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 |
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<sys/un.h> #include<netinet/in.h> #include<errno.h> #include<stdlib.h> #include<stdio.h> #include<string.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) void echo_srv(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"); } else if (n == 0){ printf("client close\n"); break; } fputs(recvbuf, stdout); write(conn, recvbuf, strlen(recvbuf)); } } int main(void) { int listenfd; if((listenfd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) ERR_EXIT("socket"); unlink("test_socket"); //再次启动服务端时先将其删除 struct sockaddr_un servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, "test_socket"); if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if(listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen"); int conn; pid_t pid; while(1){ conn = accept(listenfd, NULL, NULL); if (conn == -1){ if(conn == EINTR) continue; ERR_EXIT("accept"); } pid = fork(); if(pid == -1) ERR_EXIT("fork"); if(pid == 0){ close(listenfd); echo_srv(conn); exit(EXIT_SUCCESS); } close(conn); } return 0; } |
- 客户端设计:
(1)类似地,客户端也需要创建一个套接字,协议家族是AF_UNIX。
(2)然后连接对方的地址,所以在这之前需要初始化服务器端的地址。初始化地址之后就可以connect对等方了。
connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr));
(3)连接成功之后,就可以进行通信了,封装一个通信模块echo_cli。
客户端源码:
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 |
#include<unistd.h> #include<sys/socket.h> #include<sys/types.h> #include<sys/un.h> #include<stdlib.h> #include<stdio.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m)\ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) void echo_cli(int sock) { char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){ write(sock, sendbuf, strlen(sendbuf)); read(sock, recvbuf, sizeof(recvbuf)); fputs(recvbuf, stdout); memset(sendbuf, 0, sizeof(sendbuf)); memset(recvbuf, 0, sizeof(recvbuf)); } close(sock); } int main(void) { int sock; if((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) ERR_EXIT("socket"); struct sockaddr_un servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, "test_socket"); if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); echo_cli(sock); return 0; } |
4.UNIX域套接字编程注意事项
(1)bind成功将会创建一个文件,权限为0777 & ~umask
(2)sun_path最好用一个绝对路径
例如,将程序中的“test_socket”改为“/tmp/test_socket”
(3)UNIX域协议支持流式套接口与报式套接口
流式套接口可能会产生粘包问题,接收的时候最好按照协议的方式进行接收,比如封装一个readline方法;报式套接口则不存在粘包问题。
(4)UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同——如果监听队列满,会忽略到来的SYN,这导致对方重传SYN。