Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3595943
  • 博文数量: 208
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7375
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-23 18:56
个人简介

将晦涩难懂的技术讲的通俗易懂

文章分类

全部博文(208)

文章存档

2024年(10)

2023年(9)

2022年(4)

2021年(12)

2020年(8)

2019年(18)

2018年(19)

2017年(9)

2016年(26)

2015年(18)

2014年(54)

2013年(20)

分类: LINUX

2016-09-18 23:51:57

linux TCP发送过程源码分析——socket层

——lvyilong316

内核版本:3.15.2 

Socket数据结构关系

发送流程图

    以下是send()sendto()sendmsg()sendmmsg()的发送流程图,这四个函数除了在系统调用层面上有些差别,在Socket层和TCP层的实现都是相同的。

 

应用层 

    应用层可以使用以下Socket函数来发送数据:


点击(此处)折叠或打开

  1. ssize_t write(int fd, const void *buf, size_t count);
  2. ssize_t send(int s, const void *buf, size_t len, int flags);
  3. ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
  4. ssize_t sendmsg(int s, const struct msghdr *msg, int flags);
  5. int sendmmsg(int s, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags);


     这些发送函数有什么区别呢?当flags0时,send()write()功能相同。send(s, buf, len, flags)sendto(s, buf, len, flags, NULL, 0)功能相同。write()send()在套接字处于连接状态时可以使用,而sendto()sendmsg()sendmmsg()在任何时候都可用。用户层的数据最终都是以消息头来描述的。

struct msghdr


点击(此处)折叠或打开

  1. struct msghdr {
  2.     void *msg_name; /* optional address,目的地址 */
  3.     socklen_t msg_namelen; /* size of address,目的地址的长度 */
  4.     struct iovec *msg_iov; /* scatter/gather array,分散的数据块数组 */
  5.     size_t msg_iovlen; /* #elements in msg_iov,分散的数据块个数 */
  6.     void *msg_control; /* ancillary data, 控制数据 */
  7.     socklen_t msg_controllen; /* ancillary data buffer len,控制数据的长度 */
  8.     int msg_flags; /* flags on received message */
  9. };


struct iovec


点击(此处)折叠或打开

  1. /* Structure for scatter/gather I/O. */
  2. struct iovec {
  3.     void *iov_base; /* Pointer to data. */
  4.     size_t iov_len; /* Length of data. */
  5. };


 发送默认为阻塞发送,也可以设置为非阻塞发送。非阻塞标志:O_NONBLOCKMSG_DONTWAIT

系统调用 

    发送函数是由glibc提供的,声明位于include/sys/socket.h中,实现位于sysdeps/mach/hurd/connect.c中,主要是用来从用户空间进入名为sys_socketcall的系统调用,并传递参数。sys_socketcall()实际上是所有

socket函数进入内核空间的共同入口。


点击(此处)折叠或打开

  1. SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
  2. {
  3.     ...
  4.     switch(call) {
  5.     ...
  6.     case SYS_SEND:
  7.         err = sys_send(a0, (void __user *)a1, a[2], a[3]);
  8.         break;
  9.   
  10.     case SYS_SENDTO:
  11.         err = sys_sendto(a0, (void __user *)a1 a[2], a[3], (struct sockaddr __user *)a[4], a[5]);
  12.         break;
  13.   
  14.     ...
  15.     case SYS_SENDMSG:
  16.         err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);
  17.         break;
  18.   
  19.     case SYS_SENDMMSG:
  20.         err = sys_sendmmsg(a0, (struct msghdr __user *)a1, a[2], a[3]);
  21.         break;
  22.     ...
  23.     }
  24. }


send

    send()其实是sendto()的一种特殊情况。


点击(此处)折叠或打开

  1. SYSCALL_DEFINE4(send, int, fd, void __user *, buff, size_t, len, unsigned, flags)
  2. {
  3.     return sys_sendto(fd, buff, len, flags, NULL, 0);
  4. }


sendto

    sendto()初始化了消息头,接着就调用sock_sendmsg()来处理。  


点击(此处)折叠或打开

  1. SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len, unsigned, flags,
  2.     struct sockaddr __user *, addr, int, addr_len)
  3. {
  4.     struct socket *sock;
  5.     struct sockaddr_storage address;
  6.     int err;
  7.     struct msghdr msg;
  8.     struct iovec iov;
  9.     int fput_needed;
  10.   
  11.     if (len > INT_MAX)
  12.        len = INT_MAX;
  13.   
  14.     /* 通过文件描述符fd,找到对应的socket实例。
  15.      * 以fd为索引从当前进程的文件描述符表files_struct实例中找到对应的file实例,
  16.      * 然后从file实例的private_data成员中获取socket实例。
  17.      */
  18.     sock = sockfd_lookup_light(fd, &err, &fput_needed);
  19.     if (! sock)
  20.         goto out;
  21.   
  22.     /* 初始化消息头 */
  23.     iov.iov_base = buff;
  24.     iov.iov_len = len;
  25.     msg.msg_name = NULL;
  26.     msg.msg_iov = &iov;
  27.     msg.msg_iovlen = 1; /* 只有一个数据块 */
  28.     msg.msg_control = NULL;
  29.     msg.msg_controllen = 0;
  30.     msg.msg_namelen = 0;
  31.   
  32.     if (addr) {
  33.         /* 把套接字地址从用户空间拷贝到内核空间 */
  34.         err = move_addr_to_kernel(addr, addr_len, &address);
  35.         if (err < 0)
  36.             goto out_put;
  37.   
  38.         msg.msg_name = (struct sockaddr *)&address;
  39.         msg.msg_namelen = addr_len;
  40.     }
  41.   
  42.     /* 如果设置了非阻塞标志 */
  43.     if (sock->file->f_flags & O_NONBLOCK)
  44.         flags |= MSG_DONTWAIT;
  45.     msg.msg_flags = flags;
  46.     /* 调用统一的发送入口函数sock_sendmsg() */
  47.     err = sock_sendmsg(sock , &msg, len);
  48.   
  49. out_put:
  50.     fput_light(sock->file, fput_needed);
  51. out:
  52.     return err;
  53. }


sock_sendmsg

    sock_sendmsg()在初始化异步IO控制块后,调用__sock_sendmsg()


点击(此处)折叠或打开

  1. int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
  2. {
  3.     struct kiocb iocb;
  4.     struct sock_iocb siocb;
  5.     int ret;
  6.   
  7.     init_sync_kiocb(&iocb, NULL);
  8.     iocb.private = &siocb;
  9.   
  10.     ret = __sock_sendmsg(&iocb, sock, msg, size);
  11.   
  12.     /* iocb queued, will get completion event */
  13.     if (-EIOCBQUEUED == ret)
  14.         ret = wait_on_sync_kiocb(&iocb);
  15.   
  16.     return ret;
  17. }
  18.   
  19. /* AIO控制块 */
  20. struct kiocb {
  21.     struct file *ki_filp;
  22.     struct kioctx *ki_ctx; /* NULL for sync ops,如果是同步的则为NULL */
  23.     kiocb_cancel_fn *ki_cancel;
  24.     void *private; /* 指向sock_iocb */
  25.     union {
  26.         void __user *user;
  27.         struct task_struct *tsk; /* 执行io的进程 */
  28. } ki_obj;
  29.     __u64 ki_user_data; /* user's data for completion */
  30.     loff_t ki_pos;
  31.     size_t ki_nbytes; /* copy of iocb->aio_nbytes */
  32.   
  33.     struct list_head ki_list; /* the aio core uses this for cancellation */
  34.     /* If the aio_resfd field of the userspace iocb is not zero,
  35.      * this is the underlying eventfd context to deliver events to.
  36.      */
  37.     struct eventfd_ctx *ki_eventfd;
  38. };


__sock_sendmsg()

    __sock_sendmsg()会调用Socket层的发送函数,如果是SOCK_STREAM,那么接着就调用inet_sendmsg()处理。


点击(此处)折叠或打开

  1. static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
  2.        struct msghdr *msg, size_t size)
  3. {
  4.     int err = security_socket_sendmsg(sock, msg, size);
  5.     return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
  6. }


 __sock_sendmsg_nosec  


点击(此处)折叠或打开

  1. static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
  2.         struct msghdr *msg, size_t size)
  3. {
  4.     struct sock_iocb *si = kiocb_to_siocb(iocb);
  5.     si->sock = sock;
  6.     si->scm = NULL;
  7.     si->msg = msg;
  8.     si->size = size;
  9.     /* 调用Socket层的操作函数,如果是SOCK_STREAM,则proto_ops为inet_stream_ops, 函数指针指向inet_sendmsg()
  10.      */
  11.     return sock->ops->sendmsg(iocb, sock, msg, size);
  12. }


    sendmsg()sendmmsg()在系统调用函数中也是拷贝用户空间的数据到内核消息头,最后调用Socket层的发送函数inet_sendmsg()进行下一步处理,这里不再赘述。

Socket

    SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中发送函数为inet_sendmsg()


点击(此处)折叠或打开

  1. const struct proto_ops inet_stream_ops = {
  2.     .family = PF_INET,
  3.     .owner = THIS_MODULE,
  4.     ...
  5.     .sendmsg = inet_sendmsg,
  6.     ...
  7. };


inet_sendmsg

    inet_sendmsg()主要调用TCP层的发送函数tcp_sendmsg()来处理。


点击(此处)折叠或打开

  1. int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size)
  2. {
  3.     struct sock *sk = sock->sk;
  4.     sock_rps_record_flow(sk);
  5.   
  6.     /* We may need to bnd the socket.
  7.      * 如果连接还没有分配本地端口,且允许自动绑定,那么给连接绑定一个本地端口。
  8.      * tcp_prot的no_autobaind为true,所以TCP是不允许自动绑定端口的。
  9.      */
  10.     if (! inet_sk(sk)->inet_num && ! sk->sk_prot->no_autobind && inet_autobind(s))
  11.         return -EAGAIN;
  12.   
  13.     /* 如果传输层使用的是TCP,则sk_prot为tcp_prot,sendmsg指向tcp_sendmsg() */
  14.     return sk->sk_prot->sendmsg(iocb, sk, msg, size);
  15. }
  16.    
  17. /* Automatically bind an unbound socket. */
  18. static int inet_autobind(struct sock *sk)
  19. {
  20.     struct inet_sock *inet;
  21.   
  22.     /* We may need to bind the socket. */
  23.     lock_sock(sk);
  24.   
  25.     /* 如果还没有分配本地端口 */
  26.     if (! inet->inet_num) {
  27.   
  28.         /* SOCK_STREAM套接口的TCP操作函数集为tcp_prot,其中端口绑定函数为
  29.          * inet_csk_get_port()
  30.          */
  31.         if (sk->sk_prot->get_port(sk, 0)) {
  32.             release_sock(sk);
  33.             return -EAGAIN;
  34.         }
  35.         inet->inet_sport = htons(inet->inet_num);
  36.     }
  37.   
  38.     release_sock(sk);
  39.     return 0;
  40. }


函数调用(红线)和数据结构关系(蓝线)如下图:

阅读(15203) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~