socketpair使用示例
源码简析
socket.c 系统调用
af_unix.c 本机通信采用unix方式
2个socket做pair结对绑定
发送和接收API
发送方式
接收方式
总结
socketpair和pipe的区别
嫌麻烦的,直接跳到末尾看总结吧😊
socketpair使用示例
//android BitTube.cpp
void BitTube::init(size_t rcvbuf, size_t sndbuf) {
int sockets[2];
//helin: socket pair, AF_UNIX 域,SOCK_SEQPACKET 类型
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets) == 0) {
size_t size = DEFAULT_SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));
// since we don't use the "return channel", we keep it small...
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
fcntl(sockets[0], F_SETFL, O_NONBLOCK);
fcntl(sockets[1], F_SETFL, O_NONBLOCK);
//helin: reset 实现: system/core/base/include/android-base/unique_fd.h; 先close旧fd,再赋值为新fd
mReceiveFd.reset(sockets[0]);
mSendFd.reset(sockets[1]); //helin
} else {
mReceiveFd.reset();
ALOGE("BitTube: pipe creation failed (%s)", strerror(errno));
}
}
socket pair 使用 AF_UNIX 域,SOCK_SEQPACKET 类型;SOCK_SEQPACKET 属于数据报方式。
sockets[0]和sockets[1] 两个分别对应读和写。(记忆方式:01读写)
源码简析
socket.c 系统调用
syscall 定义 socketpair api:
SYSCALL_DEFINE4(socketpair, int, family, int, type, int, protocol,
int __user *, usockvec)
{
return __sys_socketpair(family, type, protocol, usockvec);
}
__sys_socketpair实现:
/*
* Create a pair of connected sockets.
*/
int __sys_socketpair(int family, int type, int protocol, int __user *usockvec)
{
struct socket *sock1, *sock2;
int fd1, fd2, err;
struct file *newfile1, *newfile2;
...
fd1 = get_unused_fd_flags(flags);
...
fd2 = get_unused_fd_flags(flags);
...
err = put_user(fd1, &usockvec[0]);
...
err = put_user(fd2, &usockvec[1]);
...
err = sock_create(family, type, protocol, &sock1);
...
err = sock_create(family, type, protocol, &sock2);
...
err = security_socket_socketpair(sock1, sock2);
...
err = sock1->ops->socketpair(sock1, sock2);
...
newfile1 = sock_alloc_file(sock1, flags, NULL);
...
newfile2 = sock_alloc_file(sock2, flags, NULL);
...
fd_install(fd1, newfile1);
fd_install(fd2, newfile2);
return 0;
...
}
linux中,socket也算文件。拿2个没有使用的fd1 fd2, 对应创建2个 file,关联上对应的socket。
af_unix.c 本机通信采用unix方式
static const struct proto_ops unix_seqpacket_ops = {
.family = PF_UNIX,
...
.socketpair = unix_socketpair, helin
...
.sendmsg = unix_seqpacket_sendmsg, helin
.recvmsg = unix_seqpacket_recvmsg, helin
...
};
2个socket做pair结对绑定
static int unix_socketpair(struct socket *socka, struct socket *sockb) //helin
{
struct sock *ska = socka->sk, *skb = sockb->sk;
/* Join our sockets back to back */
sock_hold(ska);
sock_hold(skb);
//helin: 双向绑定 -- 关键
unix_peer(ska) = skb;
unix_peer(skb) = ska;
init_peercred(ska);
init_peercred(skb);
if (ska->sk_type != SOCK_DGRAM) {
//helin: 设置state,直接建联了
ska->sk_state = TCP_ESTABLISHED;
skb->sk_state = TCP_ESTABLISHED;
socka->state = SS_CONNECTED;
sockb->state = SS_CONNECTED;
}
return 0;
}
建立绑定关系:
ska->peer = skb;
skb->peer = ska;
发送和接收API
static int unix_seqpacket_sendmsg(struct socket *sock, struct msghdr *msg, helin
size_t len)
{
...
return unix_dgram_sendmsg(sock, msg, len); //helin
}
static int unix_seqpacket_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
...
return unix_dgram_recvmsg(sock, msg, size, flags); //helin
}
可以看出:seqpacket对应的是dgram,数据报方式。
发送方式
/*
* Send AF_UNIX data.
*/
static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,
size_t len)
{
struct sock *sk = sock->sk;
struct net *net = sock_net(sk);
struct unix_sock *u = unix_sk(sk);
DECLARE_SOCKADDR(struct sockaddr_un *, sunaddr, msg->msg_name);
struct sock *other = NULL;
int namelen = 0; /* fake GCC */
int err;
unsigned int hash;
struct sk_buff *skb;
long timeo;
struct scm_cookie scm;
int data_len = 0;
int sk_locked;
...
skb_put(skb, len - data_len);
skb->data_len = data_len;
skb->len = len;
//helin: msg 复制到skb
err = skb_copy_datagram_from_iter(skb, 0, &msg->msg_iter, len);
...
//helin: 发送端skb 插入 接收端的接收队列尾- 指针方式,只有1个skb
skb_queue_tail(&other->sk_receive_queue, skb);
unix_state_unlock(other);
other->sk_data_ready(other);
sock_put(other);
scm_destroy(&scm);
return len;
...
}
把用户空间的msg data copy到kernel层的skb中, 再把skb通过添加到对端sock的 sk_receive_queue 队列尾部。
接收方式
static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
struct scm_cookie scm;
struct sock *sk = sock->sk;
struct unix_sock *u = unix_sk(sk);
struct sk_buff *skb, *last;
long timeo;
int skip;
int err;
...
do {
mutex_lock(&u->iolock);
skip = sk_peek_offset(sk, flags);
//helin: 阻塞读取sk_receive_queue接收队列
skb = __skb_try_recv_datagram(sk, &sk->sk_receive_queue, flags,
&skip, &err, &last);
if (skb) {
if (!(flags & MSG_PEEK))
scm_stat_del(sk, skb);
break;
}
mutex_unlock(&u->iolock);
if (err != -EAGAIN)
break;
} while (timeo &&
!__skb_wait_for_more_packets(sk, &sk->sk_receive_queue,
&err, &timeo, last));
...
//helin: 接收端的sk_receive_queue队列取出数据到skb,复制到msg中
err = skb_copy_datagram_msg(skb, skip, msg, size);
...
}
接收端从sk_receive_queue队列取出数据到skb,复制到msg中,完成数据的发送和接收。
总结

整体来看,通信效率还算比较高的。2次数据拷贝:发送端把用户空间msg数据复制到kernel的skb;接收端从kernel把skb复制到接收端的用户空间msg buffer中。
socketpair和pipe的区别
socketpair 是双工通信,可以双向读写。通信数据大小可以通过 setsockopt api的SO_RCVBUF、SO_SNDBUF 参数进行设置,或者走系统默认的,可以通过 sysctl 查看。
而pipe 是半双工通信,只能一端写一端读。模拟个16个 page 大小伪文件节点进行读写操作。




