Golang源码分析之net/http(一)Socket及TCP理解

·  阅读 907
Golang源码分析之net/http(一)Socket及TCP理解

这是Golang源码分析之net/http主题的第一篇,我们先来了解一下linux内核网络的基础架构,主要包括Socket以及TCP更深层次的理解

本文代码均为golang 版本:go1.14.13

一、Socket

1、Socket是什么

Socket(套接字),用来描述IP地址和端口,是通信链的句柄。应用程序可以通过socket向网络发送请求或者应答请求。Socket是支持TCP/IP协议的网络通信的基本操作单元,是对通信端点的抽象表示,包含进行通信的所必须5种信息:连接使用的协议、本地IP/端口、远端IP/端口。

在应用程序中,socket对应一个描述符;在内核中,它对应一个管理通信过程的对象(socket struct结构) 。在Linux系统内核中,struct socket结构对象不仅封装了管理通信过程的数据信息,而且封装了负责网络通信的功能函数。通过这些接口,应用程序触发系统调用来使用上述功能函数,从而访问网络服务。

不同协议族有不同的套接字,如下图:

image.png

SOCK_STREAM,这类套接字采用了TCP协议服务,提供可靠的双工顺序数据流,能保证数据不丢失、不错序。

SOCK_DGRAM,这类套接字采用了UDP协议服务,提供双工数据传送,但不保证数据能可靠地到达。即使数据到达了,也不能保证顺序正确。

对于Web客户/服务器使用INET套接字实现网络通信。

Web服务器的大致工作流程为:首先,创建一个采用TCP协议服务的套接字(SOCK_STREAM套接字),并通过该套接字观察是否有客户连接请求,如果有连接请求,则服务器接收该请求,并建立新的套接字来表示与该客户的连接;然后,服务器通过这个新套接字把主页数据发给客户程序。

Web客户程序的工作流程大致为:首先,创建一个采用TCP协议服务的套接字;然后,通过该套接字向Web服务器发出连接请求(请求中要指定Web服务器的IP地址和端口),如果服务器接收了连接请求,则客户程序通过该套接字获取服务器传来的网页文件,并通过浏览器显示出来;最后,如果准备退出,则客户程序将关闭与服务器的连接

2、TCP协议传输网络数据Socket接口

image.png

2.1 服务器程序流程
  • socket()

    调用socket函数创建监听套接字(Golang代码)

    func socket(af int32, typ int32, protocol int32) (handle Handle, err error) 
    复制代码
    参数含义
    af协议族,应将INET套接字设置为AF_INET
    typ套接字的传输类型(SOCK_STREAM,TCP协议服务;SOCK_DGRAM,UDP服务)
    protocol特定协议,常为0
    返回值socket 句柄
  • bind()

    调用bind函数把监听套接字与某地址绑定

    func bind(s Handle, name unsafe.Pointer, namelen int32) (err error)
    复制代码
    参数含义
    ssocket函数返回的套接字描述符
    name指向内核struct sockaddr的指针,包括协议族、IP、端口等
    namelenstruct sockaddr的大小
    返回值nil成功
  • listen()

    服务器程序调用listen函数来观察本地端口,检查是否有客户程序的连接请求

    func listen(s Handle, backlog int32) (err error)
    复制代码
    参数含义
    s套接字描述符,它是服务器程序建立的用于监听客户连接请求的套接字
    backlog允许的连接个数。在服务器调用accept函数允许客户连接请求之前,客户需要排队等待
  • accept()

    客户程序调用connect函数请求连接服务器。监听到连接请求后,服务器把客户请求插入等待队列,直到调用accept函数允许该请求。这里的accept函数返回一个套接字描述符来标志双方的连接。

    func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) 
    复制代码
    参数含义
    s监听套接字描述符
    rsa请求连接的客户地址信息
    addrlen地址长度
    返回值fd 返回一个描述符
2.2 客户程序流程
  • socket()

    调用socket函数创建套接字;见客户端创建套接字的接口

  • connect()

    客户程序调用connect向服务器发起连接请求。如果采用TCP传输协议,函数connect将启动TCP的三次握手过程

    func connect(s Handle, name unsafe.Pointer, namelen int32)(err error)
    复制代码
    参数含义
    s套接字描述符,它是连接请求方的套接字
    name服务器地址,指向存放对方地址的数据结构struct sockaddr(包括目的端口和IP地址信息)
    namelen地址解构的长度sockaddr的大小

    以上就是socket提供给客户程序结口,供客户端与服务端进行通信。

二、TCP三次握手

接下来详细学习TCP建立连接的流程。如下图:

image.png

在第一章节,学习了使用客户端和服务端是怎么使用socket的,server在创建socket之后,开始阻塞进行监听,是否有客户端进行连接。当有客户端进行TCP连接的时候,开始进行三次握手

我们按照图上的序号顺序进行讲解:

1、第一次握手

① 客户端在connect()之后主动连接服务器,发送SYN,且序列号为x。此时客户端的状态为SYN_SENT

若客户端长时间没有收到 SYN+ACK 报文,则会重发 SYN 包,重发的次数由 tcp_syn_retries 参数控制,内核参数是6次。

image.png

通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后,第六次是在超时重传 32秒后。如果仍然服务端没有回应 ACK,客户端就会终止三次握手。

② 服务端收到客户端的请求后,会根据本地半连接队列是否满进行不同的操作。

​ 服务器本地存有一个半连接队列(syn queue)。其大小由底层配置文件决定,可以看到默认是256个

image.png

  • ​ 半连接队列没满

​ 服务器将该连接的状态设置为SYN_RCVD,并把连接信息放到半连接队列里面

  • ​ 半连接队列已满

​ 服务器不会将该连接的状态变为SYN_RCVD,且将该连接丢弃。

SYN flood攻击就是利用这个原理。攻击方的客户端只发送SYN分节给服务器,然后对服务器发回来的SYN+ACK什么也不做,直接忽略掉, 不发送ACK给服务器;这样就可以占据着服务器的半连接队列的资源,导致正常的客户端连接无法连接上服务器。

但是若队列设置的较小,就会有大量的请求都会被丢弃,此时应对的方法是syncookies

syncookies 的工作原理:服务器根据当前状态计算出一个值,放在服务器发出的 SYN+ACK 报文中发出,当客户端返回 ACK 报文时,取出该值验证,如果合法,就认为连接建立成功。

开启 syncookies 功能 syncookies 参数主要有以下三个值:

  • 0 值,表示关闭该功能;
  • 1 值,表示仅当 SYN 半连接队列放不下时,再启用它;
  • 2 值,表示无条件开启功能;

查看机器syncookies的值为1,默认开启syncookies

image.png

2、第二次握手

③ 服务器返回SYN y,ACK x+1。ACK表示对客户端请求的应答,新发送SYN的序列号y。客户端的状态从SYN_SENT变为ESTABLISHED

当网络繁忙、不稳定时,报文丢失就会变严重。此时会重发SYN+ACK包

内核参数tcp_synack_retries控制SYN+ACK包重传的次数,默认是5次。

image.png

重传会经历 1、2、4、8、16 秒,最后一次重传后会继续等待 32 秒,如果服务端仍然没有收到 ACK,才会关闭连接,故共需要等待 63 秒。

3、第三次握手

④ 客户端发送ACK y+1。表示对服务端的响应。

⑤ 服务端收到客户端的响应之后,按照本地的全连接队列(accept queue)是否满进行处理

​ 全连接队列最大值,系统设置默认值为128。

image.png

​ 但全连接的最大值=min(backlog,somaxconn),这里的backlog是connect的入参。

  • 全连接队列没满

服务端接受客户端的ACK,从状态SYN_RCVD变为ESTABLISHED,然后服务器从半连接队列(syn queue)拿出连接放到本地的全连接队列(accept queue)

  • 全连接队列已满

服务端收到客户端的ACK,不会改变状态。

根据内核参数tcp_abort_on_overflow 的值执行相应动作

image.png

0值表示:如果 accept 队列满了,那么 server 扔掉 client 发过来的 ack

1值表示:如果 accept 队列满了,server 发送一个 RST 包给 client,表示废掉这个握手过程和这个连接,Client会出现(connection reset by peer)错误

注意:connect() 函数在三次握手成功之后会返回成功或者超时失败。

PS : 如何查看服务端accept队列的长度

通过 ss -ltn 查看

-l 显示正在监听的socket

-n 不解析服务的名称

-t 只显示tcp socket

  • Recv-Q:当前 accept 队列的大小,也就是当前已完成三次握手并等待服务端 accept() 的 TCP 连接;
  • Send-Q:accept 队列最大长度,上面的输出结果说明监听 25 端口的 TCP 服务,accept 队列的最大长度为 100;

image.png

引用

分类:
后端
标签: