前言
已经不记得什么时候第一次接触Unix Domain Socket(下文简称UDS),在我印象中,所谓UDS基本等同于本地环回接口(lo)上的TCP或者UDP,而事实上UDS所用的API也确实是套接字API。也许正因为这些先入为主的观点,自从进入后台开发这个领域,基本没有正眼瞧过UDS,觉得不过是单机版的TCP/UDP而已。
最近shane兄的《OIDB分set实践和技术方案》在KM上引发了激烈的讨论,当时不少人提到可以借助eventfd执行事件通知,事后我想实际做一些性能评测时,却遇到一个比较棘手的问题:eventfd创建出来的“文件描述符”在无父子关系的多进程环境中该如何共享呢?大家熟知的pipe有所谓的“命名管道”,可eventfd没有这概念。后来jayyi提到可以借助Unix域协议传递文件描述符,从那一刻起,我才决定好好研究一下这个“最熟悉的陌生人”。
概述
UDS提供两类套接字:字节流套接字(类似TCP)和数据报套接字(类似UDP)。根据《UNIX网络编程卷1》描述,使用UDS有以下3个理由:
1. 在源自Berkeley的实现中,Unix域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍
2. Unix域套接字可用于在同一个主机上的不同进程之间传递描述符
3. Unix域套接字较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施
字节流模式
/*
* unix_stream_server.c
*/
#define _GNU_SOURCE //for struct ucred
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#define SERVER_PATH "/tmp/stream_server.unix"
static int ConnHandler(int fd)
{
struct ucred credentials;
socklen_t ucred_length = sizeof(credentials);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length) < 0) {
perror("getsockopt fail.");
return -1;
}
printf("[CONN]pid=%d uid=%d gid=%d\n", (int)credentials.pid, (int)credentials.uid, (int)credentials.gid);
char buffer[65535];
uint64_t total = 0;
int readn = 0;
while ((readn = read(fd, buffer, sizeof(buffer))) > 0) {
total += readn;
}
if (readn == 0) {
printf("client close conn.\n");
} else {
perror("read fail.");
}
printf("total=%lu\n", total);
close(fd);
return 0;
}
int main(void)
{
unlink(SERVER_PATH);
int fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
perror("open socket fail.");
return -1;
}
struct sockaddr_un local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sun_family = AF_UNIX;
snprintf(local_addr.sun_path, sizeof(local_addr.sun_path), SERVER_PATH);
if (bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("bind fail.");
return -1;
}
if (listen(fd, 0) < 0) {
perror("listen fail.");
return -1;
}
while (1) {
struct sockaddr_un client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int conn_fd = accept(fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (conn_fd < 0) {
perror("accept fail.");
return -1;
}
pid_t child = fork();
if (child == 0) {
return ConnHandler(conn_fd);
}
close(conn_fd);
}
return 0;
}