热门标签 | 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



推荐阅读
  • 本文详细介绍了Socket在Linux内核中的实现机制,包括基本的Socket结构、协议操作集以及不同协议下的具体实现。通过这些内容,读者可以更好地理解Socket的工作原理。 ... [详细]
  • 基于51单片机的多项目设计实现与优化
    本文探讨了基于51单片机的多个项目的设计与实现,包括PID控制算法的开关电源设计、八音电子琴仿真设计、智能抽奖系统控制设计及停车场车位管理系统设计。每个项目均采用先进的控制技术和算法,旨在提升系统的效率、稳定性和用户体验。 ... [详细]
  • linux网络子系统分析(二)—— 协议栈分层框架的建立
    目录一、综述二、INET的初始化2.1INET接口注册2.2抽象实体的建立2.3代码细节分析2.3.1socket参数三、其他协议3.1PF_PACKET3.2P ... [详细]
  • 首部|接口类型_OSI 7层模型 & TCP/IP协议首部封装格式解析
    首部|接口类型_OSI 7层模型 & TCP/IP协议首部封装格式解析 ... [详细]
  • 近期在研究Java IO流技术时,遇到了一个关于如何正确读取Doc文档而不出现乱码的问题。本文将详细介绍使用Apache POI库处理Doc和Docx文件的具体方法,包括必要的库引入和示例代码。 ... [详细]
  • 本文详细介绍了Oracle RMAN中的增量备份机制,重点解析了差异增量和累积增量备份的概念及其在不同Oracle版本中的实现。通过对比两种备份方式的特点,帮助读者选择合适的备份策略。 ... [详细]
  • 构建Python自助式数据查询系统
    在现代数据密集型环境中,业务团队频繁需要从数据库中提取特定信息。为了提高效率并减少IT部门的工作负担,本文探讨了一种利用Python语言实现的自助数据查询工具的设计与实现。 ... [详细]
  • 本文详细介绍了Golang中string类型的内部结构及其特性,包括字符串的定义、表示方式、数据结构以及相关的操作方法,如字符串拼接和类型转换等。 ... [详细]
  • 本文详细介绍了Objective-C中的面向对象编程概念,重点探讨了类的定义、方法的实现、对象的创建与销毁等内容,旨在帮助开发者更好地理解和应用Objective-C的面向对象特性。 ... [详细]
  • 本文介绍了一个基本的同步Socket程序,演示了如何实现客户端与服务器之间的简单消息传递。此外,文章还概述了Socket的基本工作流程,并计划在未来探讨同步与异步Socket的区别。 ... [详细]
  • Kubernetes Services详解
    本文深入探讨了Kubernetes中的服务(Services)概念,解释了如何通过Services实现Pods之间的稳定通信,以及如何管理没有选择器的服务。 ... [详细]
  • 本文介绍了SIP(Session Initiation Protocol,会话发起协议)的基本概念、功能、消息格式及其实现机制。SIP是一种在IP网络上用于建立、管理和终止多媒体通信会话的应用层协议。 ... [详细]
  • 本文介绍了实时流协议(RTSP)的基本概念、组成部分及其与RTCP的交互过程,详细解析了客户端请求格式、服务器响应格式、常用方法分类及协议流程,并提供了SDP格式的深入解析。 ... [详细]
  • 如题:2017年10月分析:还记得在没有智能手机的年代大概就是12年前吧,手机上都会有WAP浏览器。当时没接触网络原理,也不 ... [详细]
  • Spring Boot与Graylog集成实现微服务日志聚合与分析
    本文介绍了如何在Graylog中配置输入源,并详细说明了Spring Boot项目中集成Graylog的日志聚合和分析方法,包括logback.xml的多环境配置。 ... [详细]
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社区 版权所有