UNIX Domain Socket 是基于socket发展而来的,是linux/unix下一种IPC(Inter-Process Communication 进程间通讯)机制,它无需向内核网络协议栈一样拆包打包,只是将数据从一个进程拷贝到另外一个进程。在这种模式下,无论使用 SOCKET_STREAM 还是 SOCKET_DGRAM 都是可以的,因为同一台电脑上基本上不存在数据丢失的情况,下面的案例实现了一个最小化的 domain socket 模型。
公共头文件
/* wrap.h */ #ifndef __WRAP_H__ #define __WRAP_H__ void perr_exit(const char* s); int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr); void Bind(int fd, const struct sockaddr* sa, socklen_t salen); void Connect(int fd, const struct sockaddr* sa, socklen_t salen); void Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void* ptr, size_t nbytes); ssize_t Write(int fd, const void* ptr, size_t nbytes); void Close(int fd); ssize_t Readn(int fd, void* vptr, size_t n); ssize_t Writen(int fd, const void* vptr, size_t n); static ssize_t my_read(int fd, char* ptr); ssize_t Readline(int fd, void* vptr, size_t maxlen); #endif
#include <stdlib.h> #include <errno.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> #include "wrap.h" void perr_exit(const char* s) { perror(s); exit(1); } int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr) { int n; again: if ( (n = accept(fd, sa, salenptr)) < 0 ) { if ((errno == ECONNABORTED) || (errno == EINTR)) { goto again; } else { perr_exit("accept error"); } } return n; } void Bind(int fd, const struct sockaddr* sa, socklen_t salen) { if ( bind(fd, sa, salen) < 0 ) { perr_exit("bind error"); } } void Connect(int fd, const struct sockaddr* sa, socklen_t salen) { if ( connect(fd, sa, salen) < 0 ) { perr_exit("connect error"); } } void Listen(int fd, int backlog) { if ( listen(fd, backlog) < 0 ) { perr_exit("listen error"); } } int Socket(int family, int type, int protocol) { int n = socket(family, type, protocol); if ( n < 0 ) { perr_exit("socket error"); } return n; } ssize_t Read(int fd, void* ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) { goto again; } else { return -1; } } return n; } ssize_t Write(int fd, const void* ptr, size_t nbytes) { ssize_t n; again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) { goto again; } else { return -1; } } return n; } void Close(int fd) { if (close(fd) == -1) { perr_exit("close error"); } } ssize_t Readn(int fd, void* vptr, size_t n) { size_t nleft; ssize_t nread; char* ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0 ) { if (errno == EINTR) { nread = 0; } else { return -1; } } else if (nread == 0) { break; } nleft -= nread; ptr += nread; } } ssize_t Writen(int fd, const void* vptr, size_t n) { size_t nleft; ssize_t nwritten; const char* ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) { nwritten = 0; } else { return -1; } } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read(int fd, char* ptr) { static int read_cnt; static char* read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0 ) { if (errno == EINTR) { goto again; } return -1; } else if (read_cnt == 0) { return 0; } read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } ssize_t Readline(int fd, void* vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') { break; } } else if (rc == 0) { *ptr = 0; return n - 1; } else { return -1; } *ptr = 0; return n; } }
服务端代码
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <strings.h> #include <ctype.h> #include <arpa/inet.h> #include <sys/un.h> #include <stddef.h> #include "wrap.h" // 服务端要绑定的socket套接字文件 #define SERV_ADDR "foo.socket" int main(int argc, char* argv[]) { // 创建socket以 AF_UNIX 方式 int sock = Socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un srvaddr; memset(&srvaddr, 0, sizeof(srvaddr)); srvaddr.sun_family = AF_UNIX; strcpy(srvaddr.sun_path, SERV_ADDR); /* offsetof 是一个宏,可以计算出第一个参数中的结构体中第二个参数的偏移位置 */ int len = offsetof(struct sockaddr_un, sun_path) + strlen(srvaddr.sun_path); // 删除当前目录下的socket文件,防止bind失败 unlink(SERV_ADDR); Bind(sock, (struct sockaddr*)&srvaddr, len); Listen(sock, 20); printf("Accept.....\n"); struct sockaddr_un cntaddr; int conn; int size; char buf[4096]; while (1) { len = sizeof(cntaddr); conn = Accept(sock, (struct sockaddr*)&cntaddr, &len); len -= offsetof(struct sockaddr_un, sun_path); cntaddr.sun_path[len] = '\0'; printf("client bind filename %s\n", cntaddr.sun_path); // 接收 while ((size = read(conn, buf, sizeof(buf))) > 0) { for (int i = 0; i < size; i++) { buf[i] = toupper(buf[i]); } // 发送 Write(conn, buf, size); } } return 0; }
客户端代码
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <strings.h> #include <ctype.h> #include <arpa/inet.h> #include <sys/un.h> #include <stddef.h> #include "wrap.h" // 要连接的服务器socket套接字文件 #define SERV_ADDR "foo.socket" // 客户端要绑定的socket套接字文件 #define CLIENT_ADDR "client.socket" int main(int argc, char* argv[]) { int len; // 同样以 AF_UNIX 方式创建 socket int sock = Socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un srvaddr, cntaddr; memset(&srvaddr, 0, sizeof(srvaddr)); memset(&cntaddr, 0, sizeof(cntaddr)); // 构建客户端的结构体 cntaddr.sun_family = AF_UNIX; strcpy(cntaddr.sun_path, CLIENT_ADDR); len = offsetof(struct sockaddr_un, sun_path) + strlen(cntaddr.sun_path); // 绑定客户端socket套接字文件 unlink(CLIENT_ADDR); Bind(sock, (struct sockaddr*)&cntaddr, len); // 构建要连接的服务端结构体 srvaddr.sun_family = AF_UNIX; strcpy(srvaddr.sun_path, SERV_ADDR); len = offsetof(struct sockaddr_un, sun_path) + strlen(srvaddr.sun_path); // 连接服务端 Connect(sock, (struct sockaddr*)&srvaddr, len); char buf[4096]; while (fgets(buf, sizeof(buf), stdin) != NULL) { // 写 Write(sock, buf, strlen(buf)); // 读 len = read(sock, buf, sizeof(buf)); // 输出到屏幕 Write(STDOUT_FILENO, buf, len); } Close(sock); return 0; }
编译测试
编译客户端:gcc domian_client.c wrap.c -o domain_client
编译服务端:gcc domain_server.c wrap.c -o domain_server -std=c99
测试效果: