x86/debian gnu linux/gcc
1. 概念
UNIX Domain Socket是在socket的框架上发展出一种IPC机制,socket API原本是为网络通讯设计的。
UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的”socket地址”是IP地址加端口号,而”UNIX Domain Socket的地址”是一个socket类型的文件在文件系统中的路径(isan one of ipc),这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
2 IPC -- UNIX Domian socket code
(1) server.c(just one name of a process)
/*Filename: uds_ipc_server.c
*Brife: Bind UNIX DOMAIN SOCKET's fd to socket address as server
* When client connect, IPC will come true
*Author: One fish
*Date: 2014.9.10 Wed
*/
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#define FILE_PATH "./UDS_ser_file.socket"
#define QLEN 10
int bind_unix_dmn_socket(const char *filename);
int serv_accept(int listenfd, uid_t *uidptr);
int main(void)
{
int listenfd;
uid_t uidptr;
listenfd = bind_unix_dmn_socket(FILE_PATH);
if (listenfd > 0) {
serv_accept(listenfd, &uidptr);
}
return 0;
}
//Bind UNIX Domain socket to an address
int bind_unix_dmn_socket(const char *filename)
{
int fd, size;
struct sockaddr_un un;
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, filename);
//Create one UNIX Domain socket
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket error");
return -1;
}
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(FILE_PATH);
//Bind UNIX Domain socket to filename by fd
if (bind(fd, (struct sockaddr *)&un, size) < 0) {
perror("bind error");
close(fd);
return -2;
}
printf("UNIX Domain socket(%d) bind to\" %s\" success\n", fd, filename);
//Tell kernel we're a server
if (listen(fd, QLEN) < 0) {
fputs("Listen failed", stderr);
close(fd);
return -3;
}
return fd;
}
//Accept a client connect
int serv_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, err, rval;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
//Accept client connect to
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0 ) {
return -1;
}
len -= offsetof(struct sockaddr_un, sun_path);
un.sun_path[len] = 0;
if (stat(un.sun_path, &statbuf) < 0) {
close(clifd);
return -2;
}
if (S_ISSOCK(statbuf.st_mode) == 0) {
close(clifd);
return -3;
}
if (uidptr != NULL) {
*uidptr = statbuf.st_uid;
}
printf("client %s which uid is %d connect to this server\n", un.sun_path, *uidptr);
char str[]= "Wellcome to UNIX Domain socket of IPC";
send(clifd, str, strlen(str), 0);
unlink(un.sun_path);
return clifd;
}
|
与网络socket编程类似,在bind之后要listen,表示通过bind的地址(也就是socket文件)提供服务。
通过accept得到客户端地址也应该是一个socket文件,如果不是socket文件就返回错误码,如果是socket文件,在建立连接后这个文件就没有用了,调用unlink把它删掉,通过传出参数uidptr返回客户端程序的user id。
(2) client.c(just one process's name)
/*Filename: uds_ipc_client.c
*Brife: Bind UNIX Domain socket's fd to socket address as client.
* Then connect server via server's socket address
*Author: One fish
*Date: 2014.9.10 Wed
*/
#include <stdio.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define CLI_PATH "/var/tmp/"
#define SER_FILE_PATH "./UDS_ser_file.socket"
int cli_conn(const char *filename);
int main(void)
{
cli_conn(SER_FILE_PATH);
return 0;
}
//Create a client endpoint and connect to a server.
int cli_conn(const char *filename)
{
int fd, len, err, rval;
struct sockaddr_un un;
//Create a UNIX domain stream socket
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
return -1;
}
//Fill client's socket address
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(un.sun_path);
//Bind client's socket fd to its socket address
if (bind(fd, (struct sockaddr *)&un, len) < 0) {
close(fd);
return -2;
}
//Fill socket address structure with server's address
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, filename);
len = offsetof(struct sockaddr_un, sun_path) + strlen(filename);
//Connect to server
if (connect(fd, (struct sockaddr *)&un, len) < 0) {
close(fd);
return -4;
}
char rvbuf[80] = "Server no say";
recv(fd, rvbuf, 78, 0);
printf("Server's words:%s\n", rvbuf);
return fd;
}
|
与网络socket编程不同的是,UNIX Domain Socket客户端一般要显式调用bind函数,而不依赖系统自动分配的地址。客户端bind一个自己指定的socket文件名的好处是,该文件名可以包含客户端的pid以便服务器区分不同的客户端。
compile and execute
in one terminal:
lly@debian:~/mydir/lly_books/linux_c_programming_osl/socket$gcc uds_ipc_server.c -o uds_ipc_server lly@debian:~/mydir/lly_books/linux_c_programming_osl/socket$./uds_ipc_server UNIX Domain socket(3) bind to" ./UDS_ser_file.socket"success |
Now in another terminal:
lly@debian:~/mydir/lly_books/linux_c_programming_osl/socket$gcc uds_ipc_client.c -o uds_ipc_client lly@debian:~/mydir/lly_books/linux_c_programming_osl/socket$./uds_ipc_client |
The result:
uds_ipc_server: uds_ipc_client: |
[2014.9.10 - 16:56]
LCNote Over.