UNIX Domain Socket的特点
1.UNIX Domain Socket为什么比TCP/IP在本地上通信更加快
因为UNIX Domain Socket不经过网络协议栈 / 不需要打包拆包 / 计算校验和 / 维护序号和应答,只是将应用层数据从一个进程拷贝到另一个进程
2.UNIX Domain Socket也提供面向流和面向数据包的两种API接口,类似于TCP和UDP,但是面向消息的 UNIX Domain Socket也是可靠的,消息既不会丢失也不会失序。
3.全双工
4.目前UNIX Domain Socket已经成为最广泛的IPC机制
Unix 本地套接字 API
1.socket创建套接字
mysocket = socket(int socket_family, int socket_type, int protocol)
网络socket | UNIX Domain Socket | |
---|---|---|
address family | AF_INET或PF_INET | AF_UNIX |
地址格式不同 | sockaddr_in(IP+Port) | sockaddr_un(本地文件) |
type | SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW | SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW |
Protocol | 0 | 0 |
2.绑定地址bind
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
my_addr参数
- struct sockaddr*
- struct sockaddr_in*
- struct sockaddr_un*
对于 Unix 本地套接字来说,绑定的地址就不是原来的“IP地址 + 端口号”了,而是一个有效的路径。
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX ,2字节*/
char sun_path[UNIX_PATH_MAX]; /* 路径名 */
};
当不再需要这个 Unix 域套接字时,应删除路径名对应的文件
int unlink(const char *pathname);
int remove(const char *pathname);
注意: 如果是抽象路径名,就不需要在使用完本地套接字后手动
删除对应的套接字文件,因为当本地套接字被关闭之后,内核会自动删除这个抽象名。
文件名:本地socket进程通信的关联文件命名方式有两种
- 具体文件名
服务器绑定(bind) socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。
这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。
缺点:这个文件很容易被其他程序不经意中删除,这导致很奇怪的问题,而很难发现
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path,UNIX_DOMAIN,sizeof(server_addr.sun_path)-1);
server_len = sizeof(struct sockaddr_un);
- 抽象命名空间
这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。
后者的实现过程与前者的差别是,后者在对地址结构成员sun_path字符数组赋值的时候,必须把第一个字节置0(因第二种方式会对首字节置0,可以在命名字符串SERVER_NAME前添加一个占位符@),即sun_path[0] = 0,代码如:#define SERVER_NAME @"/tmp/socket_server"
#define SERVER_NAME @"/tmp/socket_server"
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SERVER_NAME);
server_addr.sun_path[0]=0;
server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);
offsetof函数详解
引出offsetof函数的原因:因为struct sockaddr_un结构体的成员sun_path存放文件路径,该路径通常很短,占据不了sizeof(sun_path)的长度,因此
size_t offsetof(type, member); //表示member元素在type的偏移位置
1.具体文件名的长度 =
offsetof(struct sockaddr_un,sun_path) + strlen(cli_addr.sun_path);
2.抽象文件名的长度 =
offsetof(struct sockaddr_un,sun_path) + 1 + strlen(cli_addr.sun_path+1);
//用抽象命名地址,那么sun_path[0]必须为’\0’
当接收(accept,recvfrom等)数据时,Linux也不会在sun_path后面附上一个'\0',所以你必须根据返回的长度谨慎地处理字符串。
3.其他API
其他的一些 API,比如 listen()、accept()、connect(),以及数据通信用的 read()、write()、recv()、send()、recvfrom()、sendto()、recvmsg()、sendmsg(),用法跟网络 socket 基本一样,主要是地址结构体需要注意一下
C/S模型代码
基于SOCK_STREAM式套接字
client
UNIX socket的client 与 网络socket的client不同,它需要两个与socket相关的操作,即
- 给自己绑定一个本地unix socket:cli_addr
- connect的参数,用于连接服务器的unix socket:svr_addr
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#define UNIX_DOMAIN_CLI "/tmp/UNIX.CLI"
#define UNIX_DOMAIN_SVR "/tmp/UNIX.SVR"
int main()
{
unlink(UNIX_DOMAIN_CLI);
//unlink(UNIX_DOMAIN_SVR);
//创建本地socket
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
//初始化本地unix socket
struct sockaddr_un cli_addr;
bzero(&cli_addr,sizeof(cli_addr));
cli_addr.sun_family = AF_UNIX;
memcpy(cli_addr.sun_path, UNIX_DOMAIN_CLI, sizeof(cli_addr.sun_path));
//bind
socklen_t len = offsetof(struct sockaddr_un,sun_path) + strlen(cli_addr.sun_path);
bind(sockfd,(struct sockaddr*)&cli_addr,len);
printf("len = %d\n",len);
//初始化对方unix socket
struct sockaddr_un svr_addr;
bzero(&svr_addr,sizeof(svr_addr));
svr_addr.sun_family = AF_UNIX;
memcpy(svr_addr.sun_path, UNIX_DOMAIN_SVR, sizeof(svr_addr.sun_path));
len = offsetof(struct sockaddr_un,sun_path) + strlen(svr_addr.sun_path);
printf("len = %d\n",len);
//connect
int result = connect(sockfd, (struct sockaddr *)&svr_addr, len);
if(result == -1)
{
perror("connect failed: ");
exit(1);
}
printf(">> ");
char buffer[1024];
while(fgets (buffer, sizeof(buffer), stdin))
{
write(sockfd, buffer,sizeof(buffer));
memset(buffer,0,sizeof(buffer));
read(sockfd, buffer,sizeof(buffer));
printf("recv=%s\n>>",buffer);
}
close(sockfd);
return 0;
}
Server
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
//本地socket文件
#define UNIX_DOMAIN_SVR "/tmp/UNIX.SVR"
//如果使用抽象文件方式,可以使用如 @"/tmp/socket_server"
int main()
{
unlink(UNIX_DOMAIN_SVR);
//创建本地socket
int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
memcpy(server_addr.sun_path,UNIX_DOMAIN_SVR,sizeof(server_addr.sun_path)); //本地socket文件地址
//绑定socket到本地文件
socklen_t len = offsetof(struct sockaddr_un,sun_path) + strlen(server_addr.sun_path);
bind(server_sockfd,(struct sockaddr*)&server_addr,len);
printf("bind server_sockfd, len = %d\n",len);
//listen
listen(server_sockfd, 5);
int client_sockfd;
struct sockaddr_un client_addr;
//accept
/*
accept函数调用成功后
client_addr / len 将会被赋值
client_addr / len的值是由对方connect的参数决定的
*/
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
printf("accept success, client's len = %d\n",len);
//打印对方client绑定的文件名
len -= offsetof(struct sockaddr_un,sun_path);
client_addr.sun_path[len-1] = '\0';
char buffer[1024];
while(1)
{
printf("client[%s] say, >> ",client_addr.sun_path);
//读取数据
read(client_sockfd, buffer, sizeof(buffer));
printf("%s",buffer);
printf("response message, << %s",buffer);
//发送数据
write(client_sockfd, buffer, sizeof(buffer));
}
return 0;
}