Linux/unix下的IPC-UNIX Domain Socket

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

发表评论