UNIX Domain Socket 是基于socket发展而来的,是linux/unix下一种IPC(Inter-Process Communication 进程间通讯)机制,它无需向内核网络协议栈一样拆包打包,只是将数据从一个进程拷贝到另外一个进程。在这种模式下,无论使用 SOCKET_STREAM 还是 SOCKET_DGRAM 都是可以的,因为同一台电脑上基本上不存在数据丢失的情况,下面的案例实现了一个最小化的 domain socket 模型。
公共头文件
代码语言:javascript复制/* 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] = ‘ ’;
printf(“client bind filename %sn”, 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 测试效果: