TCP实现之:套接字

套接字的数据结构按照域的不同可以分为三种:用户态套接字、socket和sock,其中socket结构体是内核中的与用户态相似的套接字数据结构,可以理解为它是为用户态提供的一种接口,而sock结构体比较复杂,它是内核用来进行数据传输的数据结构,可以理解为它是套接字的实现。这三种套接字可谓息息相关。

struct socket

这里的socket又被称为BSD socket(伯克利套接字),它对应着网络模型中的表示层,其定义比较简单,只有7个字段,如下:

struct socket {socket_state      state;short         type;unsigned long      flags;struct socket_wq  *wq;struct file     *file;struct sock       *sk;const struct proto_ops  *ops;
};
  • state:套接字的状态(不是L4连接的状态),可用值为:

    • SS_FREE
    • SS_UNCONNECTED
    • SS_CONNECTING
    • SS_CONNECTED
    • SS_DISCONNECTING
  • type:套接字类型,与用户空间的相同
  • sock:套接字所关联的INET套接字
  • ops:套接字的操作函数。根据协议的不同,其处理函数也不同

proto_ops代表套接字操作函数的结构体,其函数对应关系如下:

inet_stream_ops inet_dgram_ops inet_sockraw_ops
.family PF_INET PF_INET PF_INET
.owner THIS_MODULE THIS_MODULE THIS_MODULE
.release inet_release inet_release inet_release
.bind inet_bind inet_bind inet_bind
.connect inet_stream_connect inet_dgram_connect inet_dgram_connect
.socketpair sock_no_socketpair sock_no_socketpair sock_no_socketpair
.accept inet_accept sock_no_accept sock_no_accept
.getname inet_getname inet_getname inet_getname
.poll tcp_poll udp_poll datagram_poll
.ioctl inet_ioctl inet_ioctl inet_ioctl
.listen inet_listen sock_no_listen sock_no_listen
.shutdown inet_shutdown inet_shutdown inet_shutdown
.setsockopt sock_common_setsockopt sock_common_setsockopt sock_common_setsockopt
.getsockopt sock_common_getsockopt sock_common_getsockopt sock_common_getsockopt
.sendmsg tcp_sendmsg inet_sendmsg inet_sendmsg
.recvmsg sock_common_recvmsg sock_common_recvmsg sock_common_recvmsg
.mmap sock_no_mmap sock_no_mmap sock_no_mmap
.sendpage tcp_sendpage inet_sendpage inet_sendpage
.splice_read tcp_splice_read

下面我们来简单看一下数据发送时socket做了哪些工作。在用户空间创建套接字时,socket系统调用会被调用,该系统调用会调用socket模块的sock_create函数来进行套接字的创建。随后,sock_map_fd函数被调用,该函数用于将socket中的file指针与VFS建立联系,并将文件句柄返回给用户态。

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{int retval;struct socket *sock;int flags;/* Check the SOCK_* constants for consistency.  */BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);flags = type & ~SOCK_TYPE_MASK;if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))return -EINVAL;type &= SOCK_TYPE_MASK;if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;retval = sock_create(family, type, protocol, &sock);if (retval < 0)goto out;return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

在进行数据发送时,可以使用sendto系统调用,这个函数首先会根据用户态传过来的文件句柄来查找对应的socket,并构造msg变量,这个变量可以理解为套接字所发送数据所需要的信息,包括所发送的数据内容、接收方的信息等。随后,sock_sendmsg函数会被调用,这个函数会调用socketops->sendmsg方法。

SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len,unsigned int, flags, struct sockaddr __user *, addr,int, addr_len)
{struct socket *sock;struct sockaddr_storage address;int err;struct msghdr msg;struct iovec iov;int fput_needed;err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);if (unlikely(err))return err;sock = sockfd_lookup_light(fd, &err, &fput_needed);if (!sock)goto out;msg.msg_name = NULL;msg.msg_control = NULL;msg.msg_controllen = 0;msg.msg_namelen = 0;if (addr) {err = move_addr_to_kernel(addr, addr_len, &address);if (err < 0)goto out_put;msg.msg_name = (struct sockaddr *)&address;msg.msg_namelen = addr_len;}if (sock->file->f_flags & O_NONBLOCK)flags |= MSG_DONTWAIT;msg.msg_flags = flags;err = sock_sendmsg(sock, &msg);out_put:fput_light(sock->file, fput_needed);
out:return err;
}

struct sock

struct sock是网络层的套接字,从上图中我们可以看出网络协议栈各个部分都是使用该套接字作为数据结构的接口。每个sock变量都会有一个与之关联的socket和用户态套接字,它被用来存储连接的信息,常用的字段如下:

struct sock {......socket_lock_t     sk_lock;atomic_t        sk_drops;int            sk_rcvlowat;struct sk_buff_head sk_error_queue;struct sk_buff_head  sk_receive_queue;struct sk_buff_head    sk_write_queue;struct sk_buff_head  sk_error_queue;......struct {atomic_t   rmem_alloc;int      len;struct sk_buff  *head;struct sk_buff    *tail;} sk_backlog;unsigned int     sk_padding : 1,sk_kern_sock : 1,sk_no_check_tx : 1,sk_no_check_rx : 1,sk_userlocks : 4,sk_protocol  : 8,sk_type      : 16;......struct socket       *sk_socket;void         *sk_user_data;truct page_frag   sk_frag;struct sk_buff      *sk_send_head;......struct sock_cgroup_data sk_cgrp_data;struct mem_cgroup  *sk_memcg;void          (*sk_state_change)(struct sock *sk);void            (*sk_data_ready)(struct sock *sk);void          (*sk_write_space)(struct sock *sk);void         (*sk_error_report)(struct sock *sk);int         (*sk_backlog_rcv)(struct sock *sk,struct sk_buff *skb);void                    (*sk_destruct)(struct sock *sk);struct sock_reuseport __rcu  *sk_reuseport_cb;struct rcu_head        sk_rcu;
};

从上面的定义中我们可以看出,该结构体的所有字段都是以sk_开头的,其:

  • sk_protocolsk_type等字段与BSD socket中的相同
  • sk_socket对应着BSD套接字
  • sk_receive_queue是这个套接字接收到的skb队列
  • sk_write_queue是这个套接字要发送的skb链表
  • sk_error_queue错误队列

sock提供了三个队列:sk_receive_queuesk_write_queuesk_error_queue,分别用来处理接收、发送的skb以及出错信息。skb_queue_tail用于skb的入栈操作,skb_dequeue用于skb的出栈操作。

inet_sock

作为协议在进行报文发送过程中所使用到的唯一用来保存协议及报文相关数据及状态的数据结构,不同的协议会根据其具体协议特性来添加新的字段。struct inet_sock用来描述IP协议族的套接字,其中inet指的是ip协议族,即L3和L4层的协议,该套接字是在sock的基础上进行扩展的,其定义如下:

struct inet_sock {struct sock        sk;
#if IS_ENABLED(CONFIG_IPV6)struct ipv6_pinfo    *pinet6;
#endif......__be32          inet_saddr;__s16            uc_ttl;__u16            cmsg_flags;__be16           inet_sport;__u16            inet_id;struct ip_options_rcu __rcu *inet_opt;int           rx_dst_ifindex;__u8         tos;__u8            min_ttl;__u8            mc_ttl;__u8         pmtudisc;__u8           recverr:1,is_icsk:1,freebind:1,hdrincl:1,mc_loop:1,transparent:1,mc_all:1,nodefrag:1;__u8           bind_address_no_port:1;__u8         rcv_tos;__u8            convert_csum;int            uc_index;int            mc_index;__be32         mc_addr;struct ip_mc_socklist __rcu *mc_list;struct inet_cork_full  cork;
};

从其定义可以看出,虽然inet_socksock是不同的数据类型,单由于inet_socksock作为了其第一个数据成员,使得inet_sock类型的变量也可以强制转换为sock进行使用。

udp_sock

虽然INET协议族使用的套接口数据结构都是struct inet_sock,但各个协议都会对其再一次进行不同程度的扩展,以UDP协议为例,它在struct inet_sock的基础上定义了struct udp_sock,如下:

struct udp_sock {struct inet_sock inet;
#define udp_port_hash       inet.sk.__sk_common.skc_u16hashes[0]
#define udp_portaddr_hash   inet.sk.__sk_common.skc_u16hashes[1]
#define udp_portaddr_node   inet.sk.__sk_common.skc_portaddr_nodeint         pending;   /* Any pending frames ? */unsigned int   corkflag;  /* Cork is required */__u8       encap_type;    /* Is this an Encapsulation socket? */unsigned char  no_check6_tx:1,/* Send zero UDP6 checksums on TX? */no_check6_rx:1;/* Allow zero UDP6 checksums on RX? */__u16      len;       /* total length of pending frames */__u16        pcslen;__u16        pcrlen;__u8         unused[3];int (*encap_rcv)(struct sock *sk, struct sk_buff *skb);void (*encap_destroy)(struct sock *sk);struct sk_buff **  (*gro_receive)(struct sock *sk,struct sk_buff **head,struct sk_buff *skb);int           (*gro_complete)(struct sock *sk,struct sk_buff *skb,int nhoff);
};

当UDP协议收到skb包时,udp_rcv函数会被调用,该函数随后会调用__udp4_lib_rcv函数,在__udp4_lib_rcv函数中会完成skb到sock的交付。

首先我们来看一下,skb_steal_sock函数会被调用,这个函数用来获取skb结构体中的*sock字段(也不知道这个sock是啥时候赋值进去的)。获取到sock后,udp_queue_rcv_skb会被调用,以将skb加到sock的接收队列sk_receive_queue中,然后调用sock_put用来减少sock的引用计数。注意,当sock的引用计数为0时,该sock会被销毁。

 sk = skb_steal_sock(skb);if (sk) {struct dst_entry *dst = skb_dst(skb);int ret;if (unlikely(sk->sk_rx_dst != dst))udp_sk_rx_dst_set(sk, dst);ret = udp_queue_rcv_skb(sk, skb);sock_put(sk);/* a return value > 0 means to resubmit the input, but* it wants the return to be -protocol, or 0*/if (ret > 0)return -ret;return 0;}

当skb中没有找到sk,__udp4_lib_lookup_skb函数会被调用,这个函数用于从udp_table中进行sk的查找。udp_table中的sk存储在两个哈希表中,一个是以dport,即目的端口,为键值,记为Hash1;另一个是以daddrdport,即目的地址和端口,为键值,记为Hash2

在进行查找时,它会先从Hash1中进行查找,当查找到的sk数量大于10的时候再从Hash2中查找,从而加快查找的速度。通过这种方式查找,会获得一个sk的链表,通过计算链表中的sk与skb的匹配程度来选取一个最合适的sk来处理skb。当skb的sportsaddrdportdaddr与sk一样时,认为他们完全匹配,此时直接返回sk。

从上面的分析我们可以看出,当接收到skb时,与其sportsaddrdportdaddr完全一致的sk会获得该skb的处理权。当找不到这样的sk时,daddrINADDR_ANYdport与skb的dport相同的sk会获得该skb的处理权,这种sk也就是监听dport端口的套接字。

 sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);if (sk)return udp_unicast_rcv_skb(sk, skb, uh);

在进行UDP数据发送时,其函数调用关系为:
sk->sendmsg -->inet_sendmsg -->udp_prot->udp_sendmsg

参考链接:Linux networking,Linux内核分析 - 网络[十二]:UDP模块 - socket

TCP实现之:套接字相关推荐

  1. TCP和UDP套接字编程

    一.Socket简单介绍 如果要在应用层调用传输层的服务,进行相关程序的设计,就要涉及到套接字编程.套接字也称之为Socket,本质上它就是利用传输层提供的一系列Api来进行网络应用程序的设计. 网络 ...

  2. LINUX 下tcp 和 udp 套接字收发缓冲区的大小决定规则 .

    const int udp_recvbufsize = 384 * 1024 ; int result = ::setsockopt(m_hSocket, SOL_SOCKET, SO_RCVBUF, ...

  3. TCP流式套接字的异步事件WSAAsyncSelect编程

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! WSAA ...

  4. day26-2 基于TCP协议的套接字编程

    目录 基于TCP协议的套接字编程 套接字 套接字工作流程 基于TCP协议的套接字编程(简单) 服务端 客户端 基于TCP协议的套接字编程(循环) 服务端 客户端1 客户端2 基于TCP协议的套接字编程 ...

  5. 创建一个TCP流式套接字

    #python网络套接字模块 from socket import *HOST = '172.60.50.218' PORT = 8888 ADDR = (HOST,PORT) BUFFERSIZE ...

  6. python-基于tcp协议的套接字(加强版)及粘包问题

    一.基于tcp协议的套接字(通信循环+链接循环) 服务端应该遵循: 1.绑定一个固定的ip和port 2.一直对外提供服务,稳定运行 3.能够支持并发 基础版套接字: from socket impo ...

  7. TCP、UDP套接字的数据传输

    tcp发送数据: 1 #include <sys/types.h> 2 #include <socket.h> 3 ssize_t send(int sockfd,const ...

  8. java 套接字 访问tcp_Java 网络编程(五) 使用TCP/IP的套接字(Socket)进行通信

    套接字Socket的引入 为了能够方便地开发网络应用软件,由美国伯克利大学在Unix上推出了一种应用程序访问通信协议的操作系统用调用socket(套接字). socket的出现,使程序员可以很方便地访 ...

  9. 基于TCP/IP的套接字服务器端和客户端编程

    (本文章内容来源于网络,仅供学习之用,别无二心,希望不要有纠纷,谢谢!) 基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符.客户端随后调用conne ...

  10. linux的tcp时间戳,Linux套接字的Linux时间戳

    我正在一个项目中工作,以便从文件Linux时间戳记中提到的用于TCP套接字的NIC获取接收和传输 时间戳.但所有文档和测试编码都是针对UDP套接字完成的.但我正在获取NIC的传输时间戳,而不是获取接收 ...

最新文章

  1. Java方法调用事件_Java中的事件处理和Java中actionPerformed方法的执行
  2. Python中的驻留机制:小数据池和代码块
  3. C#_解决在控制台中输入Ctrl+Z的问题
  4. 2017/08/03 工作日志
  5. 没错!Python 杀死了 Excel!
  6. 【SQL】substr截取结果和想象中有差异?
  7. 天若OCR文字识别软件
  8. win10默认壁纸_Win10系统待机锁频壁纸怎么提取?
  9. 不小心把移动硬盘设置为活动分区后的解决方法
  10. 做人做事要有上进心2
  11. Leetcode 79. 单词搜索(迷宫回溯)
  12. 要是卢安娜的飓风可以触发所有远程英雄的技能,哪些英雄最强?
  13. 算法基础:k最近邻算法
  14. 【数据库】聚簇索引与非聚簇索引
  15. Hadoop实战-MR倒排索引(三)
  16. 深入理解生成对抗网络(GAN 基本原理,训练崩溃,训练技巧,DCGAN,CGAN,pix2pix,CycleGAN)
  17. 袁萌:Linux病毒为何不会泛滥成灾?
  18. 微信分享,登陆支付等接口调用 白屏原因 记录一下
  19. 租车App第一次迭代报告
  20. 如何编写知识竞赛抢答赛规则和流程策划书

热门文章

  1. 将一个大于4的正整数分解为连续的正整数之和,请显示全部分解结果。
  2. Github 加载不出来,解决方法
  3. 成绩排序(p32)排序
  4. 数独(九宫格)的高效算法
  5. 最新WordPress网址导航主题模板+自适应手机端
  6. 数据结构_图_最短路径_狄杰斯特拉(Dijkstra)算法
  7. SQL查询请假时间明细表
  8. deprecated的用法
  9. Managing Spring beans with JMX
  10. 每周总结:用心工作,善于发现,勤于总结