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 测试效果: 2015-07-18 21:45:41