暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

linux socketpair 性能怎样-源码分析

haha视界 2022-11-11
726
  • 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 大小伪文件节点进行读写操作。

文章转载自haha视界,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论