1、基礎
雖然網絡socket也可用于同一台主機的程序間通訊(通過lo位址127.0.0.1),但是unix domain socket用于IPC更有效率:不需要經過網絡協定棧,不需要打包拆包/計算校驗和/維護信号和應答等。隻是将應用層資料從一個程序拷貝到另一個程序。這是因為IPC機制本質上是可靠的通訊,而網絡協定是不可靠的通訊。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SMycDOzADO1QTMtgDMwAzMwUzMygjMyATMyAjMtUzN0ETO38CXyATMyAjMvwVN3QTM5czLcd2bsJ2Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
unix domain socket也提供面向流和面向資料的兩種API接口,類似TCP和UDP,但是面向消息的unix domain socket也是可靠的,消息既不會丢失也不會順序錯亂。
2、socket位址
socket:address family指定為AF_UNIX,type可以選擇SOCK_DGRAM或SOCK_STREAM,protocol參數仍指定為0即可。
unix domain socket的位址格式用sockaddr_un表示,指定一個socket類型檔案在檔案系統中的路徑。這個socket檔案有bind()調用建立,如果調用bind()時該檔案已存在,則bind()錯誤傳回。
unix domain socket用戶端一般要顯示調用bind(),而不是依賴系統自動配置設定的位址。用戶端bind一個自己指定的socket檔案名的好處是,該檔案可以包含用戶端的pid以便伺服器區分不同的用戶端。
time + pid
sprintf(cli_addr.sun_path, "%u.%u.sock", time(NULL), getpid()).
注:用戶端與伺服器端各自綁定自己的檔案,檔案名必須不同(兩端的檔案名沒有聯系)。
位址格式,摘自man unix:
A UNIX domain socket address is represented in the following structure:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
sun_family always contains AF_UNIX.
Three types of address are distinguished in this structure:
* pathname: a UNIX domain socket can be bound to a null-terminated file system pathname using
bind(2). When the address of the socket is returned by getsockname(2), getpeername(2), and
accept(2), its length is offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1, and
sun_path contains the null-terminated pathname.
* unnamed: A stream socket that has not been bound to a pathname using bind(2) has no name. Like?
wise, the two sockets created by socketpair(2) are unnamed. When the address of an unnamed socket
is returned by getsockname(2), getpeername(2), and accept(2), its length is sizeof(sa_family_t),
and sun_path should not be inspected.
* abstract: an abstract socket address is distinguished by the fact that sun_path[0] is a null byte
('\0'). The socket's address in this namespace is given by the additional bytes in sun_path that
are covered by the specified length of the address structure. (Null bytes in the name have no spe‐
cial significance.) The name has no connection with file system pathnames. When the address of an
abstract socket is returned by getsockname(2), getpeername(2), and accept(2), the returned addrlen
is greater than sizeof(sa_family_t) (i.e., greater than 2), and the name of the socket is contained
in the first (addrlen - sizeof(sa_family_t)) bytes of sun_path. The abstract socket namespace is a
nonportable Linux extension.
3、應用
1)UDP應用
// UDP server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>
#define BUF_SIZE 10
#define DES_PATH "/tmp/main.socket"
int main(int argc, char *argv[])
{
int sd;
struct sockaddr_un un, peer_un;
socklen_t len;
int i;
int ret;
char buf[BUF_SIZE];
sd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sd < 0)
{
perror("socket");
return -1;
}
unlink(DES_PATH);
memset(&un, 0, sizeof(struct sockaddr_un));
un.sun_family = AF_UNIX;
strncpy(un.sun_path, DES_PATH, sizeof(un.sun_path) - 1);
ret = bind(sd, (struct sockaddr *)&un, sizeof(struct sockaddr_un));
if(ret < 0)
{
perror("bind");
return -1;
}
while(1)
{
memset(buf, 0, BUF_SIZE);
// len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path) + 1;
len = sizeof(struct sockaddr_un);
ret = recvfrom(sd, buf, BUF_SIZE, 0, (struct sockaddr *)&peer_un, &len);
if(ret > 0)
{
printf("Recvfrom [%d] bytes from >>%s:\n", ret, peer_un.sun_path);
for(i = 0; i < BUF_SIZE; i++)
{
printf("0x%.2x\t", 0xFF&buf[i]);
if(0 == (i + 1) % 5)
{
printf("\n");
}
}
} else {
printf("Recvfrom [%d]\n", ret);
}
}
close(sd);
return 0;
}
// UDP client
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stddef.h>
#include <fcntl.h>
#include <time.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
int sd;
struct sockaddr_un un;
socklen_t len;
int tnode;
int ret ;
if(argc < 2)
{
return -1;
}
tnode = atoi(argv[1]);
sd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sd < 0)
{
perror("socket");
return -1;
}
memset(&un, 0, sizeof(struct sockaddr_un));
un.sun_family = AF_UNIX;
// strcpy(un.sun_path, "/tmp/main.socket");
snprintf(un.sun_path, sizeof(struct sockaddr_un), "%u.%u.sock", time(NULL), getpid());
printf("sockaddr is %s\n", un.sun_path);
ret = bind(sd, (struct sockaddr *)&un, sizeof(struct sockaddr_un));
if(ret < 0)
{
perror("bind");
return -1;
}
memset(&un, 0, sizeof(struct sockaddr_un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "/tmp/main.socket");
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path) + 1;
sendto(sd, &tnode, sizeof(int), 0, (struct sockaddr *)&un, len);
close(sd);
return 0;
}
運作結果:
~$./c 100
sockaddr is 1480428387.3096.sock
~$./c 1000
sockaddr is 1480428390.3097.sock
~$./c 10000
sockaddr is 1480428392.3099.sock
~$./c 100000
sockaddr is 1480428395.3100.sock
~$./c 1000001
sockaddr is 1480428398.3101.sock
~$./c 100
sockaddr is 1480428409.3103.sock
~$./s
Recvfrom [4] bytes from >>1480428387.3096.sock:
0x64 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00
Recvfrom [4] bytes from >>1480428390.3097.sock:
0xe8 0x03 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00
Recvfrom [4] bytes from >>1480428392.3099.sock:
0x10 0x27 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00
Recvfrom [4] bytes from >>1480428395.3100.sock:
0xa0 0x86 0x01 0x00 0x00
0x00 0x00 0x00 0x00 0x00
Recvfrom [4] bytes from >>1480428398.3101.sock:
0x41 0x42 0x0f 0x00 0x00
0x00 0x00 0x00 0x00 0x00
Recvfrom [4] bytes from >>1480428409.3103.sock:
0x64 0x00 0x00 0x00 0x00
0x00 0x00 0x00 0x00 0x00
如果在server端讀資料前延遲一段時間如10s,在client端一個sock多次sendto相同資料,server讀取資料仍然和client發送包數量一緻并且接收資料一緻,可知udp每讀取一次都是一包資料,無需做分包處理。
UDP是面向消息的協定,每個UDP段都是一條消息,應用程式必須以消息為機關提取資料,不能一次提取任意位元組的資料。
UDP每讀取一次都是一包資料(UDP已做分包處理)。
TCP需要做分包處理,具體事例可參考文檔“TCP&UDP”。
注意:TCP隻能與接入它的用戶端通信,用戶端必須與伺服器綁定後才能互相通信。
UDP伺服器(準确的說,是UDP端)可以與任意用戶端通信,且兩者之間可以不建立聯系就可直接發送資訊。
2)TCP應用
// unix_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define SOCK_NAME "/tmp/echo.server"
#define LISTEN_BACKLOG 50
#define BUF_SIZE 1024
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char *argv[])
{
int sfd = 0, cfd = 0;
int i = 0, ret = 0;
struct sockaddr_un my_addr, peer_addr;
socklen_t peer_addr_size;
char buf[BUF_SIZE] = {0};
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(sfd < 0){
// printf("%s socket error.\n", SOCK_NAME);
handle_error("socket");
}
unlink(SOCK_NAME);
memset(&my_addr, 0, sizeof(struct sockaddr_un));
my_addr.sun_family = AF_UNIX;
strncpy(my_addr.sun_path, SOCK_NAME, sizeof(my_addr.sun_path)-1);
if(bind(sfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_un)) == -1){
handle_error("bind");
}
if(listen(sfd, LISTEN_BACKLOG) == -1){
handle_error("listen");
}
signal(SIGCHLD, SIG_IGN);
while(1){
peer_addr_size = sizeof(struct sockaddr_un);
cfd = accept(sfd, (struct sockaddr *)&peer_addr, &peer_addr_size);
if(cfd < 0){
handle_error("accept");
}else {
ret = fork();
if(ret < 0){
handle_error("fork");
} else if(ret == 0){
while(1){
ret = read(cfd, buf, sizeof(buf));
buf[ret] = 0;
printf("%s (len %d) recv %d bytes: %s\n", peer_addr.sun_path, peer_addr_size, ret, buf);
for(i = 0; i < ret; i++){
if(buf[i] >= 'a' && buf[i] <='z')
buf[i] += 'A'-'a';
}
write(cfd, buf, ret);
}
}
}
}
return 0;
}
// unix_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stddef.h>
#define SER_NAME "/tmp/echo.server"
#define SOCK_NAME_PRE "/tmp/ECHO"
#define BUF_SIZE 1024
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char *argv[])
{
int cfd = 0;
int ret = 0;
struct sockaddr_un client_addr, server_addr;
char buf[BUF_SIZE]={0};
int len = 0;
cfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(cfd < 0){
handle_error("socket");
}
memset(&client_addr, 0, sizeof(struct sockaddr_un));
client_addr.sun_family = AF_UNIX;
snprintf(client_addr.sun_path, sizeof(struct sockaddr_un), "%s.%d.%ld", SOCK_NAME_PRE, getpid(), time(NULL));
len = offsetof(struct sockaddr_un, sun_path) + strlen(client_addr.sun_path) + 1;
printf("client addr:%s, len:%d\n", client_addr.sun_path, len);
ret = bind(cfd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr_un));
if(ret < 0){
handle_error("bind");
}
memset(&server_addr, 0, sizeof(struct sockaddr_un));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SER_NAME, sizeof(struct sockaddr_un));
len = offsetof(struct sockaddr_un, sun_path) + strlen(server_addr.sun_path) + 1;
// ret = connect(cfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un));
ret = connect(cfd, (struct sockaddr *)&server_addr, len);
if(ret < 0){
handle_error("connect");
}
while(1){
printf("please input the bytes:\n");
scanf("%s", buf);
ret = strlen(buf);
buf[ret] = 0;
write(cfd, buf, ret+1);
ret = read(cfd, buf, sizeof(buf));
if(ret >=1024) ret = 1023;
buf[ret]=0;
printf("conversion %d bytes:[%s]\n", ret, buf);
}
return 0;
}
該例程實作echo回顯且小寫變大寫功能。
3)本地套接字通過curl用HTTP協定通路:
curl --unix-socket /var/run/docker.sock -X GET http:/v1.39/containers/json
curl --unix-socket /var/run/docker.sock -X GET http:/containers/json
4、socket IPC設計
本節内容來自:細說linux IPC(二):基于socket的程序間通信(下)
定義通信資料結構:
typedef struct _ipc_sock_msg_t {
int msg_type;//請求類型
int msg_rc;//服務程序處理結果的傳回值
int msg_buflen;//交換資料的大小
char msg_buf[SOCK_IPC_MAX_BUF];//交換資料的内容
} ipc_sock_msg_t;
服務程序收到客戶程序請求之後首先判斷請求類型,根據請求類型來進行處理。我們首先定義一個函數數組,在服務程序接收請求之前将要處理的所有請求注冊到該函數數組當中來,收到請求之後根據請求類型索引找到處理函數。
函數數組如下:
static int (*sln_ipc_ser_func[SLN_IPC_MAX_TYPE])(
void *recvbuf, int recv_size,
void *sendbuf, int *send_size);
服務程序接收處理之前先将需要處理的函數注冊到函數數組中,如下:
int sln_ipc_ser_func_init(void)
{
int i;
for (i = 0; i < SLN_IPC_MAX_TYPE; i++) {
sln_ipc_ser_func[i] = NULL;
}
sln_ipc_ser_func[SLN_IPC_TYPE_0x1] = sln_ipc_handle_0x1;
sln_ipc_ser_func[SLN_IPC_TYPE_0x2] = sln_ipc_handle_0x2;
return 0;
}
之後服務程序開始監聽,等待連接配接:
#if USE_AF_UNIX
fd = sln_ipc_ser_afunix_listen(SOCK_IPC_NAME);
if (fd < 0) {
return -1;
}
#else
fd = sln_ipc_ser_afinet_listen(SOCK_IPC_SER_LISTEN_PORT);
if (fd < 0) {
return -1;
}
#endif
服務程序接收客戶程序發送的資料,交給函數sln_ser_handle來處理:
static int sln_ipc_ser_accept(int listenfd)
{
int connfd;
ssize_t recvlen;
ipc_sock_msg_t recv_msg;
socklen_t addrlen;
#if USE_AF_UNIX
struct sockaddr_un cltaddr;
#else
struct sockaddr_in cltaddr;
#endif
addrlen = sizeof(cltaddr);
for (;;) {
connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
if (connfd < 0) {
fprintf(stderr, "accept: %s\n", strerror(errno));
continue;
}
if ((recvlen = sln_ipc_recv(connfd, &recv_msg, sizeof(ipc_sock_msg_t))) < 0) {
continue;
}
sln_ser_handle(connfd, &recv_msg);
close(connfd);
}
return 0;
}
其中處理函數sln_ser_handle實作為:
sln_ser_handle(int sockfd, ipc_sock_msg_t *recv_msg)
{
ipc_sock_msg_t send_msg;
memset(&send_msg, 0, sizeof(ipc_sock_msg_t));
send_msg.msg_type = recv_msg->msg_type;
if ((recv_msg->msg_type >= SLN_IPC_MAX_TYPE)
&& (recv_msg->msg_rc < 0)) {
send_msg.msg_rc = SLN_IPC_RC_TYPE;
} else if (NULL == sln_ipc_ser_func[recv_msg->msg_type]) {
send_msg.msg_rc = SLN_IPC_RC_FUNC;
} else {
send_msg.msg_rc
= sln_ipc_ser_func[recv_msg->msg_type](
recv_msg->msg_buf,
recv_msg->msg_buflen,
send_msg.msg_buf,
&send_msg.msg_buflen);
}
if (sln_ipc_send(sockfd, &send_msg, sizeof(ipc_sock_msg_t)) < 0) {
return -1;
}
return 0;
}
用戶端程式:
int
sln_ipc_clt_conn(
int msg_type,
int *ret_code,
void *sendbuf,
int sendlen,
void *recvbuf,
int *recvlen)
{
int connfd;
ssize_t ret_size;
socklen_t addrlen;
ipc_sock_msg_t send_msg, recv_msg;
#if USE_AF_UNIX
if ((connfd = sln_ipc_clt_afunix_conn_init(SOCK_IPC_NAME)) < 0) {
return -1;
}
addrlen = sizeof(struct sockaddr_un);
#else
if ((connfd = sln_ipc_clt_afinet_conn_init(SOCK_IPC_SER_LISTEN_PORT)) < 0) {
return -1;
}
addrlen = sizeof(struct sockaddr_in);
#endif
if (connect(connfd, (struct sockaddr *)&seraddr, addrlen) < 0) {
fprintf(stderr, "connect: %s\n", strerror(errno));
return -1;
}
memset(&send_msg, 0, sizeof(ipc_sock_msg_t));
send_msg.msg_type = msg_type;
if (NULL != sendbuf) {
send_msg.msg_buflen = sendlen;
memcpy(send_msg.msg_buf, sendbuf, sendlen);
}
if ((ret_size = ipc_send(connfd, &send_msg, 3 * sizeof(int) + sendlen)) < 0) {
return -1;
}
if ((ret_size = ipc_recv(connfd, &recv_msg, sizeof(ipc_sock_msg_t))) < 0) {
return -1;
}
if (recv_msg.msg_type != send_msg.msg_type) {
printf("Error msg type!\n");
return -1;
}
*ret_code = recv_msg.msg_rc;
if (NULL != recvbuf) {
*recvlen = recv_msg.msg_buflen;
memcpy(recvbuf, recv_msg.msg_buf, recv_msg.msg_buflen);
}
return 0;
}