socket IPC(本地套接字Domain)
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地址 + 端口号”了,而是一个有效的路径。
结论:Unix 本地套接字的地址结构体中包含两个成员,其中 sun_family 表示协议族,填 AF_LOCAL 或 AF_UNIX 即可;sun_path 表示一个路径名。
补充:路径名,其实还要分为两种,普通路径名 / 抽象路径名。- 普通路径名是一个正常的字符串,也就是说,sun_path 字段是以空字符(’\0’)结尾的。
- 而抽象路径名,sun_path 字段的第一个字节需要设置成 NULL(’\0’),所以
在计算抽象路径名的长度的时候就要特别小心了,否则在解析抽象路径名时就有可能出现异常情况,因为抽象路径名不是像解析普通路径名那样,解析到第一个 NULL 就可以停止了
。
使用抽象路径名的好处
1.因为不会再在文件系统中创建文件了,就不需要担心与文件系统中已存在的文件产生名字冲突
2.不需要在使用完套接字之后删除,当套接字被关闭之后会自动删除这个抽象名
3.其他API
其他的一些 API,比如 listen()、accept()、connect(),以及数据通信用的 read()、write()、recv()、send()、recvfrom()、sendto()、recvmsg()、sendmsg(),用法跟网络 socket 基本一样,主要是地址结构体需要注意一下
4.常用API
1)用于创建类似于无名管道(pipe)的本地套接字
int socketpair(int domain, int type, int protocol, int sv[2]);
2)当不再需要这个 Unix 域套接字时,应删除路径名对应的文件
int unlink(const char *pathname);
int remove(const char *pathname);
注意,如果是抽象路径名,就不需要在使用完本地套接字后手动删除对应的套接字文件,因为当本地套接字被关闭之后,内核会自动删除这个抽象名。
3)获取本地套接字的地址信息
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
TCP(字节流套接字)
【unixstr_serv.c】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIXSTR_PATH "/tmp/unix.str"
#define LISTENQ 5
#define BUFFER_SIZE 256
int main(void)
{
int listenfd, connfd;
socklen_t len;
struct sockaddr_un servaddr, cliaddr;
if(-1 == (listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)))
{
perror("socket");
exit(EXIT_FAILURE);
}
unlink(UNIXSTR_PATH);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXSTR_PATH);
if(-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
{
perror("bind");
exit(EXIT_FAILURE);
}
listen(listenfd, LISTENQ);
len = sizeof(cliaddr);
if(-1 == (connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len)))
{
perror("accept");
exit(EXIT_FAILURE);
}
char buf[BUFFER_SIZE];
while(1)
{
bzero(buf, sizeof(buf));
if(read(connfd, buf, BUFFER_SIZE) == 0) break;
printf("Receive: %s", buf);
}
close(listenfd);
close(connfd);
unlink(UNIXSTR_PATH);
return 0;
}
【unixstr_cli.c】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIXSTR_PATH "/tmp/unix.str"
#define LISTENQ 5
#define BUFFER_SIZE 256
int main(void)
{
int sockfd;
struct sockaddr_un servaddr;
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXSTR_PATH);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
char buf[BUFFER_SIZE];
while(1)
{
bzero(buf, sizeof(BUFFER_SIZE));
printf(">> ");
if(fgets(buf, BUFFER_SIZE, stdin) == NULL)
{
break;
}
write(sockfd, buf, strlen(buf));
}
close(sockfd);
return 0;
}
UDP(数据报套接字)
1)类似于上面 TCP 的例子,我们使用绝对路径名”/tmp/unix.dg”来实现一个数据报的本地套接字,一端接收数据,一端发送数据。
2)需要注意的是,跟网络套接字不一样,对于 Unix 域套接字的 UDP 客户端,必须 bind 一个路径名到 UDP 套接字,以使得 UDP 服务器有发送应答的目的地。
【unixdg_serv.c】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIXDG_PATH "/tmp/unix.dg"
#define BUFFER_SIZE 256
int main(void)
{
int sockfd;
struct sockaddr_un servaddr, cliaddr;
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);
unlink(UNIXDG_PATH);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXDG_PATH);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
socklen_t len = sizeof(cliaddr);
char buf[BUFFER_SIZE];
while(1)
{
bzero(buf, BUFFER_SIZE);
if(0 == recvfrom(sockfd, buf, BUFFER_SIZE, 0, (struct sockaddr *)&cliaddr, &len))
{
break;
}
printf("recvfrom: %s", buf);
}
close(sockfd);
unlink(UNIXDG_PATH);
return 0;
}
【unixdg_cli.c】
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#define UNIXDG_PATH "/tmp/unix.dg"
#define BUFFER_SIZE 256
int main(void)
{
int sockfd;
struct sockaddr_un servaddr, cliaddr;
socklen_t len;
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);
/* local address */
bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sun_family = AF_LOCAL;
strcpy(cliaddr.sun_path, UNIXDG_PATH);
bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
/* remote address */
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXDG_PATH);
len = sizeof(servaddr);
char buf[BUFFER_SIZE];
while(1)
{
bzero(buf, BUFFER_SIZE);
printf(">> ");
if(fgets(buf, BUFFER_SIZE, stdin) == NULL)
{
break;
}
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, len);
}
close(sockfd);
return 0;
}