热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

TCP实现之:套接字

TCP实现之:套接字套接字的数据结构按照域的不同可以分为三种:用户态套接字、socket和sock,其中socket结构体是内核中的与用

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_opsinet_dgram_opsinet_sockraw_ops
.familyPF_INETPF_INETPF_INET
.ownerTHIS_MODULETHIS_MODULETHIS_MODULE
.releaseinet_releaseinet_releaseinet_release
.bindinet_bindinet_bindinet_bind
.connectinet_stream_connectinet_dgram_connectinet_dgram_connect
.socketpairsock_no_socketpairsock_no_socketpairsock_no_socketpair
.acceptinet_acceptsock_no_acceptsock_no_accept
.getnameinet_getnameinet_getnameinet_getname
.polltcp_polludp_polldatagram_poll
.ioctlinet_ioctlinet_ioctlinet_ioctl
.listeninet_listensock_no_listensock_no_listen
.shutdowninet_shutdowninet_shutdowninet_shutdown
.setsockoptsock_common_setsockoptsock_common_setsockoptsock_common_setsockopt
.getsockoptsock_common_getsockoptsock_common_getsockoptsock_common_getsockopt
.sendmsgtcp_sendmsginet_sendmsginet_sendmsg
.recvmsgsock_common_recvmsgsock_common_recvmsgsock_common_recvmsg
.mmapsock_no_mmapsock_no_mmapsock_no_mmap
.sendpagetcp_sendpageinet_sendpageinet_sendpage
.splice_readtcp_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 !&#61; O_CLOEXEC);BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) !&#61; SOCK_TYPE_MASK);BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);flags &#61; type & ~SOCK_TYPE_MASK;if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))return -EINVAL;type &&#61; SOCK_TYPE_MASK;if (SOCK_NONBLOCK !&#61; O_NONBLOCK && (flags & SOCK_NONBLOCK))flags &#61; (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;retval &#61; sock_create(family, type, protocol, &sock);if (retval <0)goto out;return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}

在进行数据发送时&#xff0c;可以使用sendto系统调用&#xff0c;这个函数首先会根据用户态传过来的文件句柄来查找对应的socket&#xff0c;并构造msg变量&#xff0c;这个变量可以理解为套接字所发送数据所需要的信息&#xff0c;包括所发送的数据内容、接收方的信息等。随后&#xff0c;sock_sendmsg函数会被调用&#xff0c;这个函数会调用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 &#61; import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);if (unlikely(err))return err;sock &#61; sockfd_lookup_light(fd, &err, &fput_needed);if (!sock)goto out;msg.msg_name &#61; NULL;msg.msg_control &#61; NULL;msg.msg_controllen &#61; 0;msg.msg_namelen &#61; 0;if (addr) {err &#61; move_addr_to_kernel(addr, addr_len, &address);if (err <0)goto out_put;msg.msg_name &#61; (struct sockaddr *)&address;msg.msg_namelen &#61; addr_len;}if (sock->file->f_flags & O_NONBLOCK)flags |&#61; MSG_DONTWAIT;msg.msg_flags &#61; flags;err &#61; sock_sendmsg(sock, &msg);out_put:fput_light(sock->file, fput_needed);
out:return err;
}

struct sock

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

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

从上面的定义中我们可以看出&#xff0c;该结构体的所有字段都是以sk_开头的&#xff0c;其&#xff1a;


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

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


inet_sock

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

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

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


udp_sock

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

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包时&#xff0c;udp_rcv函数会被调用&#xff0c;该函数随后会调用__udp4_lib_rcv函数&#xff0c;在__udp4_lib_rcv函数中会完成skb到sock的交付。

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

sk &#61; skb_steal_sock(skb);if (sk) {struct dst_entry *dst &#61; skb_dst(skb);int ret;if (unlikely(sk->sk_rx_dst !&#61; dst))udp_sk_rx_dst_set(sk, dst);ret &#61; 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&#xff0c;__udp4_lib_lookup_skb函数会被调用&#xff0c;这个函数用于从udp_table中进行sk的查找。udp_table中的sk存储在两个哈希表中&#xff0c;一个是以dport&#xff0c;即目的端口&#xff0c;为键值&#xff0c;记为Hash1&#xff1b;另一个是以daddrdport&#xff0c;即目的地址和端口&#xff0c;为键值&#xff0c;记为Hash2

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

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

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

在进行UDP数据发送时&#xff0c;其函数调用关系为&#xff1a;
sk->sendmsg -->inet_sendmsg -->udp_prot->udp_sendmsg

在这里插入图片描述


参考链接&#xff1a;Linux networking&#xff0c;Linux内核分析 - 网络[十二]&#xff1a;UDP模块 - socket



推荐阅读
  • Question该提问来源于开源项目:react-native-device-info/react-native-device-info ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 概述H.323是由ITU制定的通信控制协议,用于在分组交换网中提供多媒体业务。呼叫控制是其中的重要组成部分,它可用来建立点到点的媒体会话和多点间媒体会议 ... [详细]
  • SQL Server 2008 到底需要使用哪些端口?
    SQLServer2008到底需要使用哪些端口?-下面就来介绍下SQLServer2008中使用的端口有哪些:  首先,最常用最常见的就是1433端口。这个是数据库引擎的端口,如果 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • ubuntu用sqoop将数据从hive导入mysql时,命令: ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • 第七课主要内容:多进程多线程FIFO,LIFO,优先队列线程局部变量进程与线程的选择线程池异步IO概念及twisted案例股票数据抓取 ... [详细]
  • 给出一群女孩的重量和颜值和她们的朋友关系现在有一个舞台ab是朋友bc是朋友ac就是朋友给出最大承重可以邀请这些女孩来玩对于每一个朋友团体全邀请or邀请一个or不邀请问能邀请的女孩的 ... [详细]
  • [翻译]PyCairo指南裁剪和masking
    裁剪和masking在PyCairo指南的这个部分,我么将讨论裁剪和masking操作。裁剪裁剪就是将图形的绘制限定在一定的区域内。这样做有一些效率的因素࿰ ... [详细]
  • 域名解析系统DNS
    文章目录前言一、域名系统概述二、因特网的域名结构三、域名服务器1.根域名服务器2.顶级域名服务器(TLD,top-leveldomain)3.权威(Authoritative)域名 ... [详细]
author-avatar
王友仁国珍_326
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有