程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> linux TCP發送過程源碼分析——socket層

linux TCP發送過程源碼分析——socket層

編輯:JAVA綜合教程

linux TCP發送過程源碼分析——socket層


linuxTCP發送過程源碼分析——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);


這些發送函數有什麼區別呢?當flags為0時,send()和write()功能相同。send(s, buf, len, flags)和sendto(s, buf, len, flags, NULL, 0)功能相同。write()和send()在套接字處於連接狀態時可以使用,而sendto()、sendmsg()和sendmmsg()在任何時候都可用。用戶層的數據最終都是以消息頭來描述的。

lstructmsghdr


  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. };


lstructiovec


  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_NONBLOCK、MSG_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. case SYS_SENDTO:
  10. err = sys_sendto(a0, (void __user *)a1 a[2], a[3], (struct sockaddr __user *)a[4], a[5]);
  11. break;

  12. ...
  13. case SYS_SENDMSG:
  14. err = sys_sendmsg(a0, (struct msghdr __user *)a1, a[2]);
  15. break;

  16. case SYS_SENDMMSG:
  17. err = sys_sendmmsg(a0, (struct msghdr __user *)a1, a[2], a[3]);
  18. break;
  19. ...
  20. }
  21. }


lsend

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. }


lsendto

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. if (len > INT_MAX)
  11. len = INT_MAX;

  12. /* 通過文件描述符fd,找到對應的socket實例。
  13. * 以fd為索引從當前進程的文件描述符表files_struct實例中找到對應的file實例,
  14. * 然後從file實例的private_data成員中獲取socket實例。
  15. */
  16. sock = sockfd_lookup_light(fd, &err, &fput_needed);
  17. if (! sock)
  18. goto out;

  19. /* 初始化消息頭 */
  20. iov.iov_base = buff;
  21. iov.iov_len = len;
  22. msg.msg_name = NULL;
  23. msg.msg_iov = &iov;
  24. msg.msg_iovlen = 1; /* 只有一個數據塊 */
  25. msg.msg_control = NULL;
  26. msg.msg_controllen = 0;
  27. msg.msg_namelen = 0;

  28. if (addr) {
  29. /* 把套接字地址從用戶空間拷貝到內核空間 */
  30. err = move_addr_to_kernel(addr, addr_len, &address);
  31. if (err < 0)
  32. goto out_put;

  33. msg.msg_name = (struct sockaddr *)&address;
  34. msg.msg_namelen = addr_len;
  35. }

  36. /* 如果設置了非阻塞標志 */
  37. if (sock->file->f_flags & O_NONBLOCK)
  38. flags |= MSG_DONTWAIT;
  39. msg.msg_flags = flags;
  40. /* 調用統一的發送入口函數sock_sendmsg() */
  41. err = sock_sendmsg(sock , &msg, len);

  42. out_put:
  43. fput_light(sock->file, fput_needed);
  44. out:
  45. return err;
  46. }


lsock_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. init_sync_kiocb(&iocb, NULL);
  7. iocb.private = &siocb;

  8. ret = __sock_sendmsg(&iocb, sock, msg, size);

  9. /* iocb queued, will get completion event */
  10. if (-EIOCBQUEUED == ret)
  11. ret = wait_on_sync_kiocb(&iocb);

  12. return ret;
  13. }

  14. /* AIO控制塊 */
  15. struct kiocb {
  16. struct file *ki_filp;
  17. struct kioctx *ki_ctx; /* NULL for sync ops,如果是同步的則為NULL */
  18. kiocb_cancel_fn *ki_cancel;
  19. void *private; /* 指向sock_iocb */
  20. union {
  21. void __user *user;
  22. struct task_struct *tsk; /* 執行io的進程 */
  23. } ki_obj;
  24. __u64 ki_user_data; /* user's data for completion */
  25. loff_t ki_pos;
  26. size_t ki_nbytes; /* copy of iocb->aio_nbytes */

  27. struct list_head ki_list; /* the aio core uses this for cancellation */
  28. /* If the aio_resfd field of the userspace iocb is not zero,
  29. * this is the underlying eventfd context to deliver events to.
  30. */
  31. struct eventfd_ctx *ki_eventfd;
  32. };


l__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. }


l__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. };


linet_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. /* We may need to bnd the socket.
  6. * 如果連接還沒有分配本地端口,且允許自動綁定,那麼給連接綁定一個本地端口。
  7. * tcp_prot的no_autobaind為true,所以TCP是不允許自動綁定端口的。
  8. */
  9. if (! inet_sk(sk)->inet_num && ! sk->sk_prot->no_autobind && inet_autobind(s))
  10. return -EAGAIN;

  11. /* 如果傳輸層使用的是TCP,則sk_prot為tcp_prot,sendmsg指向tcp_sendmsg() */
  12. return sk->sk_prot->sendmsg(iocb, sk, msg, size);
  13. }

  14. /* Automatically bind an unbound socket. */
  15. static int inet_autobind(struct sock *sk)
  16. {
  17. struct inet_sock *inet;

  18. /* We may need to bind the socket. */
  19. lock_sock(sk);

  20. /* 如果還沒有分配本地端口 */
  21. if (! inet->inet_num) {

  22. /* SOCK_STREAM套接口的TCP操作函數集為tcp_prot,其中端口綁定函數為
  23. * inet_csk_get_port()。
  24. */
  25. if (sk->sk_prot->get_port(sk, 0)) {
  26. release_sock(sk);
  27. return -EAGAIN;
  28. }
  29. inet->inet_sport = htons(inet->inet_num);
  30. }

  31. release_sock(sk);
  32. return 0;
  33. }


函數調用(紅線)和數據結構關系(藍線)如下圖:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved