前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >socket连接和通信过程解析

socket连接和通信过程解析

作者头像
theanarkh
发布2019-05-06 15:00:58
1.4K0
发布2019-05-06 15:00:58
举报
文章被收录于专栏:原创分享原创分享

网络通信的标准流程是,服务端新建一个socket,然后在该socket中绑定一个地址,再设置该socket为监听socket,然后阻塞在accept等待连接。客户端新建一个socket,然后connect到一个服务端的地址。下面分析一下这个过程。看多个客户端或者多个连接是如何在一个监听的socket中完成通信的。 服务器收到一个syn包的时候,在tcp_rcv中进行处理。该函数根据tcp数据包中的目的端口和ip,源端口和ip找到一个最匹配的socket

代码语言:javascript
复制
sk = get_sock(&tcp_prot, th->dest, saddr, th->source, daddr);
下面是get_socket的代码。
struct sock *get_sock(struct proto *prot, unsigned short num,
                unsigned long raddr,
                unsigned short rnum, unsigned long laddr)
{
    struct sock *s;
    struct sock *result = NULL;
    int badness = -1;
    unsigned short hnum;

    hnum = ntohs(num);

    for(s = prot->sock_array[hnum & (SOCK_ARRAY_SIZE - 1)];
            s != NULL; s = s->next) 
    {
        int score = 0;

        if (s->num != hnum) 
            continue;

        if(s->dead && (s->state == TCP_CLOSE))
            continue;
        /* local address matches? */
        if (s->saddr) {
            if (s->saddr != laddr)
                continue;
            score++;
        }
        /* remote address matches? */
        if (s->daddr) {
            if (s->daddr != raddr)
                continue;
            score++;
        }
        /* remote port matches? */
        if (s->dummy_th.dest) {
            if (s->dummy_th.dest != rnum)
                continue;
            score++;
        }
        /* perfect match? */
        // 全匹配,直接返回
        if (score == 3)
            return s;
        /* no, check if this is the best so far.. */
        if (score <= badness)
            continue;
        // 记录最好的匹配项
        result = s;
        badness = score;
      }
      return result;
}

监听socket中有本机的ip和监听的端口。所以根据tcp数据包,可以找到对应的socket。接着判断找到的socket的状态。

代码语言:javascript
复制
if(sk->state!=TCP_ESTABLISHED)    {

        // 是监听socket则可能是一个syn包  
        if(sk->state==TCP_LISTEN)
        {   // 不存在收到ack包的可能,发送重置包
            if(th->ack) 
                tcp_reset(daddr,saddr,th,sk->prot,opt,dev,sk->ip_tos, sk->ip_ttl);

            // 不存在这种可能的各种情况,直接丢包               
            if(th->rst || !th->syn || th->ack || ip_chk_addr(daddr)!=IS_MYADDR)
            {
                kfree_skb(skb, FREE_READ);
                release_sock(sk);
                return 0;
            }

            // 是个syn包,建立连接
            tcp_conn_request(sk, skb, daddr, saddr, opt, dev, tcp_init_seq());
                }
}

tcp_conn_request函数很长,下面只列出关键代码。sock结构体是tcp层的表示,socket结构体是更上层的抽象,比如unix域套接字,也是使用socket结构体,然后在unix域实现的时候,使用unix_proto_data结构体。socket和sock结构体是互相指向的。tcp维护了一个哈希链表,以监听的端口号为因子进行哈希。

代码语言:javascript
复制
// 分配一个新的sock结构用于连接连接
newsk = (struct sock *) kmalloc(sizeof(struct sock), GFP_ATOMIC);
// 从listen套接字复制内容,再覆盖某些字段
memcpy(newsk, sk, sizeof(*newsk));
// 进入第二次握手状态
newsk->state = TCP_SYN_RECV;
// 记录ip
newsk->dummy_th.source = skb->h.th->dest;
newsk->dummy_th.dest = skb->h.th->source;
newsk->daddr = saddr;
newsk->saddr = daddr;
// 放到tcp的socket哈希队列
put_sock(newsk->num,newsk);
// 放到tcp的socket哈希队列
put_sock(newsk->num,newsk);
// 分配一个skb
buff = newsk->prot->wmalloc(newsk, MAX_SYN_SIZE, 1, GFP_ATOMIC);
// 发送ack,即第二次握手
newsk->prot->queue_xmit(newsk, ndev, buff, 0);
// skb关联的socket为newsk,accept的时候摘取skb时即拿到该socket返回给应用层
skb->sk = newsk;
skb_queue_tail(&sk->receive_queue,skb);
至此完成了tcp的两次握手。
等收到客户端的ack的时候,在tcp_ack中完成第三次握手。
       if(sk->state==TCP_SYN_RECV){
        tcp_set_state(sk, TCP_ESTABLISHED);
    }

此时的内存视图如下。

在这里插入图片描述 第一个sock结构体就是负责监听的,第二个就是建立连接后,新建的,其中新建的sock和监听sock有很多字段是一样的。不过新建的sock里记录了目的端口、ip、源端口、源ip。这时候accept函数就会返回新建的sock,我们就会对这个sock进行读写操作(其实是对上层的sockect结构体进行操作)。当我们阻塞在read的时候,如果收到了一个数据包,处理函数还是tcp_rcv,第一步还是根据tcp包的源ip、端口、目的ip、目的端口到sock池子里查找最匹配的sock结构体,这时候找到的就是刚才我们新建的那个sock。因为他四个都匹配上了。接着在tcp_data函数里把数据包对应的skb结构体放到新建sock的receive_queue。read从阻塞中返回,并拿到数据包的数据。 通过上面的分析,我们可以知道几点,第一,监听的sock和数据通信的sock结构是一样的,但是他们的receive_queue里的数据包含义不一样,监听sock的receive_queue中的节点代表的是即将或者已经建立连接的节点,而数据通信的sock的receive_queue中的节点是通信的数据。我们知道的第二点是多个客户端或者连接,是如何在一个监听的sock中完成tcp的建立,又是从监听的sock中过渡到数据通信sock,最后在新加的sock中完成数据通信的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档