Linux基础知识之socketpair

简介

  socketpair会创建一对无名套接字的描述符,具有全双工通信特性(描述符可读也可写),他的域只能为AF_UNIX(本地).

应用场景

  socketpair主要用于C/S模式的进程间通讯.由于binder通信具有透传fd的特性,使得socketpair不再受限于亲缘关系进程

函数原型

SYNOPSIS

1
2
3
4
#include <sys/types.h>    
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sv[2]);

DESCRIPTION

TYPE 说明
SOCK_STREAM TCP协议,提供有序,面向连接,双向,可靠的传输
SOCK_DGRAM UDP协议,提供面向无连接,不可靠的数据包传输
SOCK_SEQPACKET 提供有序,双向,可靠的传输
EFD_CLOEXEC 该标志位设置后,当执行exec族函数时,会自动关闭fd.防止越权和造成fd leak
EFD_NONBLOCK 该标志位设置后,执行IO操作时不会阻塞,会立即返回.

想必大家对SOCK_STREAM与SOCK_SEQPACKET有疑问,这2个描述基本一样那他们有没区别呢?我们直接查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Af_unix.c (kernel-4.9\net\unix)

static int unix_seqpacket_sendmsg(struct socket *sock, struct msghdr *msg,
size_t len)
{
int err;
struct sock *sk = sock->sk;

err = sock_error(sk);
if (err)
return err;

if (sk->sk_state != TCP_ESTABLISHED)
return -ENOTCONN;

if (msg->msg_namelen)
msg->msg_namelen = 0;

return unix_dgram_sendmsg(sock, msg, len);
}

可以看到SOCK_SEQPACKET类型的发送数据流走的是SOCK_DGAM(UDP协议)的发送接口,该接口不具备TCP协议的重传功能

RETURN VALUE
  On success, zero is returned
  On error, -1 is returned, and errno is set appropriately

例子

fork
如上图所示,TASK_A进程执行fork产生子进程TASK_B,子进程TASK_B会共享父进程的文件表项,因此子进程具备和父进程通信的条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/*本例子的流程为: 
* 1. 调用socketpair得到一对双向fd
* 2. 调用fork,产生子进程
* 3. 父进程向子进程发送信息,子进程接收并打印
* 4. 子进程向父进程发送信息,父进程接收并打印
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>

void childLoop(int fd) {
char recvMsg[128] = { 0 };
char *sendMsg = "I am Child";
if (read(fd, recvMsg, sizeof(recvMsg) / sizeof(*recvMsg)) < 0) {
printf("%s read fail reason: %s\n", __func__, strerror(errno));
return;
}

printf("%s read msg : %s \n", __func__, recvMsg);


if (write(fd, sendMsg, strlen(sendMsg) + 1) < 0) {
printf("%s write fail reason: %s\n", __func__, strerror(errno));
return;
}
}

void parentLoop(int fd) {
char recvMsg[128] = { 0 };
char *sendMsg = "I am Father";

if (write(fd, sendMsg, strlen(sendMsg) + 1) < 0) {
printf("%s write fail reason: %s\n", __func__, strerror(errno));
return;
}

if (read(fd, recvMsg, sizeof(recvMsg) / sizeof(*recvMsg)) < 0) {
printf("%s read fail reason: %s\n", __func__, strerror(errno));
return;
}

printf("%s read msg : %s \n", __func__, recvMsg);
}
int main(int argc __unused, char **argv __unused) {
int pid;
int fd[2];
int err = 0;
signal(SIGCHLD, SIG_IGN); //忽略子进程停止信号,防止产生僵尸进程
err = socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fd);

if (err != 0) {
printf("socketpair fail reason: %s\n", strerror(errno));
return -1;
}

pid = fork();

if (pid < 0) {
printf("fork fail reason: %s\n", strerror(errno));
return -1;

}

if (pid == 0) { //子进程
close(fd[0]); //关闭fd[0]
childLoop(fd[1]);
close(fd[1]); //关闭fd[1]
return 0;
}else { //父进程
close(fd[1]); //关闭fd[1]
parentLoop(fd[0]);
close(fd[0]); //关闭fd[0]
return 0;
}

return 0;
}

运行结果

1
2
3
4
generic_arm64:/ # example
example
childLoop read msg : I am Father
parentLoop read msg : I am Child

本章完