UNIX域协议(命名套接字)
这里主要介绍命名UNIX域套接字
1.什么是UNIX域套接字
Unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务通信的一种方式。是进程间通信(IPC)的一种方式。
它提供了两类套接字:字节流套接字(有点像TCP)和数据报套接字(有点像UDP)
UNIX域数据报服务是可靠的,不会丢失消息,也不会传递出错。
IP协议标识客户服务器是通过IP地址和端口号实现的,UNIX域协议中用于标识客户机和服务器的协议地址的是普通文件系统中的路径名。
2.UNIX域协议特点
1)UNIX域套接字域TCP套接字相比,在同一台主机的传输速度前者是后者的两倍。UNIX域套接字仅仅复制数据,并不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不产生顺序号,也不需要发送确认报文
2)UNIX域套接字可以在同一台主机上各进程之间传递文件描述符
3)UNIX域套接字域传统套接字的区别是用路径名表示协议族的描述
3.UNIX域地址结构
#define UNIX_PATH_MAX 128
struct sockaddr_un{
sa_family_t sun_family; /* AF_UNIX 或者 AF_LOCAL */
char sun_path[UNIX_PATH_MAX]; /* path name */
};
4.使用实例,编程套路跟TCP很像。
Server:先创建套接字 -> 绑定地址 -> 监听 -> accept 客户端连接 -> 连接成功开始通信 -> 关闭套接字
Client:先创建套接字 -> 连接server -> 开始通信 -> 关闭套接字。
这里实现一个简单的回射服务器。
启动服务器,等待客户端连接,连接上之后,客户端通过标准输入接收数据发送给服务器。服务器接收数据以后,再把数据发送回客户端。
下面上代码:
server:
#include<stdio.h> #include<unistd.h> #include<string.h> #include<stdlib.h> #include<errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <sys/un.h> //#include<netinet/in.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) #define UNIXSOCKETNAME "test_socket" void echo_cli(int sock) { char buf[1024] = {0}; int ret = 0; while(1) { ret = read(sock, buf, sizeof(buf)); if(ret == 0) { printf("client %d close\n", sock); break; } write(sock, buf, strlen(buf)); } close(sock); } int main() { int listenfd = socket(PF_UNIX, SOCK_STREAM, 0); if(listenfd < 0) ERR_EXIT("socket"); unlink(UNIXSOCKETNAME); struct sockaddr_un servaddr; // 头文件是这个 #include <sys/un.h> memset(&servaddr, 0, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, UNIXSOCKETNAME); if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); if(listen(listenfd, SOMAXCONN) < 0) ERR_EXIT("listen"); int conn = 0; pid_t pid; while(1) { conn = accept(listenfd, NULL, NULL); if(conn == -1) { if(errno == EINTR) continue; ERR_EXIT("accept"); } printf("Has new client connected, conn = %d\n", conn); pid = fork(); if(pid < 0) ERR_EXIT("fork"); else if(pid == 0) { echo_cli(conn); exit(1); } else close(conn); } return 0; }
client:
#include<stdio.h> #include<unistd.h> #include<string.h> #include<stdlib.h> #include<errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <sys/un.h> //#include<netinet/in.h> #define UNIXSOCKETNAME "test_socket" #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) void echo_cli(int sock) { char buf1[1024] = {0}; char buf2[1024] = {0}; int ret = 0; while(fgets(buf1, sizeof(buf1), stdin) != NULL) { write(sock, buf1, strlen(buf1)); ret = read(sock, buf2, sizeof(buf2)); if(ret == 0) { printf("server %d close\n", sock); break; } printf("%s", buf2); memset(buf1, 0, sizeof(buf1)); memset(buf2, 0, sizeof(buf2)); } close(sock); } int main() { int sock = socket(PF_UNIX, SOCK_STREAM, 0); if(sock < 0) ERR_EXIT("socket"); struct sockaddr_un servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strcpy(servaddr.sun_path, UNIXSOCKETNAME); if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("connect"); echo_cli(sock); return 0; }
运行:
直接gcc编译,就可以运行了。先启动server,再启动client。
注意:
1)启动server后,bind后会在对应目录创建一个文件(权限是0777&~umask)。这文件的类型是s。表示是套接口文件。可以通过ls -al查看。
srwxrwxr-x 1 xcy xcy 0 1月 3 10:29 test_socket
2)若套接口文件存在,则bind会出错。为此可以先把该文件删掉。(server中的unlink就干这个的)
3)创建的套接口文件最好为绝对路径。建议指定在/tmp目录下。比如把上面的目录改成/tmp/test_socket
3)UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满了,会忽略到来的SYN,这会导致对方重传SYN。