這里主要介紹命名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。