• TCP协议在socket中的初始化


    TCP

    TCP通过校验和、序列号、确认应答、重发控制、连接管理和窗口控制实现可靠传输。

    TCP通过确认应答ACK来实现有保障的数据传输,但是由于各种原因,目标主机可能无法收到ACK信号,导致源主机不停重发数据。为此,引入序列号与确认信号相结合,实现有效的重发控制。

    作为面向连接的协议,TCP在数据通信前,通过TCP首部发送一个SYN包作为建立连接的请求等待确认应答。一个连接的建立与断开至少要来回发送7个包才能完成。其中建立连接三次握手,需要发送3个包。切断连接四次挥手,需要发送4个包。

    LINUX & TCP

    下图展示了LINUX中TCP客户机和服务器交互时调用的基本套接字函数。


    (图源:UNIX网络编程-第三版)

    一个进程要进行网络读写,第一个执行的就是socket(),它定义在socket.h文件中:

    #include <sys/socket.h>
    int socket(int family, int type, int protocol);
    

    其中family指定协议类型簇(IPv4、IPv6、Unix域协议、路由套接字、密钥套接字),type指定套接字类型,protocol指定协议的类型常量(TCP、UDP、SCTP)。socket()函数返回套接字描述符。

    跟踪调用栈


    (图源:https://www.jianshu.com/p/5d82a685b5b6)

    启动qemuOS,打开GDB调试,连接到远程。在__sys_socket()函数处打上断点,追踪函数调用栈:

    在gdb中根据情况交替使用nextstep命令,追踪程序运行。函数调用了__sock_create()实际执行socket创建任务。对__socket_create()进行追踪。

    __sock_create()函数中

    sock_alloc()

    __socket_create()中,又调用了sock_alloc()函数将socket分配给一个结构体变量sock
    这里,sock的类型为struct socket*,在linux源码目录下使用正则表达式find -name "*.h" | xargs grep "strcut socket {" -rn找到该结构体定义在./linux-5.0.1/include/linux/net.h中,如下:

    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是socket的状态,type是类型,flags是标志位,eq是等待队列,file是指针列表。sock是一个重要的结构体,同理可以找到结构体sock的定义,根据注释描述,它表示一个网络层的socket。

    new_inode_pseudo()

    接着调用了new_inode_pseudo()函数,在相应的文件系统中创建一个inode。其代码位于./linux-5.0.1/fs/inode.c中:

    struct inode *new_inode_pseudo(struct super_block *sb)
    {
    	struct inode *inode = alloc_inode(sb);
    
    	if (inode) {
    		spin_lock(&inode->i_lock);
    		inode->i_state = 0;
    		spin_unlock(&inode->i_lock);
    		INIT_LIST_HEAD(&inode->i_sb_list);
    	}
    	return inode;
    }
    

    inode为socket文件系统中的节点。inode和socket对象是连结在一起的,可以根据inode取得socket对象。分配inode后,应用程序可以通过文件描述符对socket进行读写访问。

    协议簇初始化socket

    继续往下,有一条语句:

    pf = rcu_dereference(net_families[family]);
    

    用于查找内核初始化时注册的协议簇。当传入的family字段存在于协议簇中时,又继续执行一条语句:

    err = pf->create(net, sock, protocol, kern);
    

    这一步调用的是inet_create()函数,它位于./net/ipv4/Af_inet.c中。

    inet_create()

    这个函数主要完成四个工作:
    1 将socket的state设置为SS_UNCONNECTED

    sock->state = SS_UNCONNECTED;
    

    2 根据type字段找到对应套接字类型

    lookup_protocol:
    	err = -ESOCKTNOSUPPORT;
    	rcu_read_lock();
    	list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
    
    		err = 0;
    		/* Check the non-wild match. */
    		if (protocol == answer->protocol) {
    			if (protocol != IPPROTO_IP)
    				break;
    		} else {
    			/* Check for the two wild cases. */
    			if (IPPROTO_IP == protocol) {
    				protocol = answer->protocol;
    				break;
    			}
    			if (IPPROTO_IP == answer->protocol)
    				break;
    		}
    		err = -EPROTONOSUPPORT;
    	}
    
    	if (unlikely(err)) {
    		if (try_loading_module < 2) {
    			rcu_read_unlock();
    			/*
    			 * Be more specific, e.g. net-pf-2-proto-132-type-1
    			 * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
    			 */
    			if (++try_loading_module == 1)
    				request_module("net-pf-%d-proto-%d-type-%d",
    					       PF_INET, protocol, sock->type);
    			/*
    			 * Fall back to generic, e.g. net-pf-2-proto-132
    			 * (net-pf-PF_INET-proto-IPPROTO_SCTP)
    			 */
    			else
    				request_module("net-pf-%d-proto-%d",
    					       PF_INET, protocol);
    			goto lookup_protocol;
    		} else
    			goto out_rcu_unlock;
    	}
    
    	err = -EPERM;
    	if (sock->type == SOCK_RAW && !kern &&
    	    !ns_capable(net->user_ns, CAP_NET_RAW))
    		goto out_rcu_unlock;
    
    	sock->ops = answer->ops;
    	answer_prot = answer->prot;
    	answer_flags = answer->flags;
    	rcu_read_unlock();
    

    3 初始化socket的sk

    sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
    if (!sk)
        goto out;
    
    err = 0;
    if (INET_PROTOSW_REUSE & answer_flags)
        sk->sk_reuse = SK_CAN_REUSE;
    

    4 建立socket与sock之间的关系

    inet = inet_sk(sk);
    inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
    
    inet->nodefrag = 0;
    
    if (SOCK_RAW == sock->type) {
        inet->inet_num = protocol;
        if (IPPROTO_RAW == protocol)
            inet->hdrincl = 1;
    }
    
    if (net->ipv4.sysctl_ip_no_pmtu_disc)
        inet->pmtudisc = IP_PMTUDISC_DONT;
    else
        inet->pmtudisc = IP_PMTUDISC_WANT;
    
    inet->inet_id = 0;
    
    sock_init_data(sock, sk);
    

    5 使用具体协议初始化socket
    这一步通过hook机制(回调函数)实现。

    回到__sys_socket()

    在函数的最后使用sock_map_fd(),将初始化好的socket与文件系统关联,对于TCP和其他协议来说,区别在于socket结构体中family字段的不同:

    return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    

    参考文献

    [1] https://www.jianshu.com/p/5d82a685b5b6

  • 相关阅读:
    mysql拼接字符串和过滤字符的方法
    python ichat使用学习记录
    php简单混淆类加密文件如何解密?
    如何读取xml文件,根据xml节点属性查询并输出xml文件
    GoldenDict
    R群体
    samtools中faidx索引格式
    Conservation and the genetics of population重要语录
    图形分类
    电脑网络知识理解
  • 原文地址:https://www.cnblogs.com/Mr-Tiger/p/12102196.html
Copyright © 2020-2023  润新知