从编程角度看TCP协议(4)python与tcp的三次握手

1 tcp的连接特性

    对于tcp或者udp稍微了解一点的同学,肯定知道一句话,tcp是基于连接的,而udp是无连接的。怎么理解这句话呢?

    无论tcp还是udp,都是用来传输数据的一种协议,都处于网络层之上的传输层。也都需要通信双方的重新向操作系统申请端口,这是通信的前提。

    但不一样的是,udp发送方收到上层应用层程序(不一定非得是应用层协议,也可以自己写的网络程序)需要发送的数据后,直接发送出去,无论接收方是否收到,反正有数据给我,我就发送。

    而tcp在发送数据之前,是需要先通过三次握手建立连接的。在发送过程中,要求收到数据后的一方回复ACK,以示收到数据。发送完成后,可以通过四次挥手关闭连接,也可以维持连接等待下一次数据的发送(也就是所谓的TCP长连接)。

    还有一点要说明的是,udp跟ip协议是不区分客户端和服务器的,也就是说它提供的是P2P的通信。虽然我们平时所见的,基于udp上的dns、tftp、dhcp、radius等等应用层协议有客户端和服务器之分,那那也是应用层协议自己设计如此,udp本身是不区分的。

    tcp是区分客户端和服务器的,三次握手建立连接的过程,一定是客户端主动发起的,而服务器是被动响应。四次挥手拆掉连接的过程,既可以是客户端主动发起,也可以是服务器主动发起。只要一方没有数据发送给对端,那么就可以主动关闭连接,而不管你是客户端还是服务器。

    正是因为有连接的建立和关闭过程,因此tcp引入了状态机的概念,当客户端或者服务器发送或者接收到不同的报文时,就进入不同的状态。

2 三次握手

2.1 三次握手的状态机

    我们先看三次握手建立连接时,报文的交互和状态机的变化,如下图:

å»ºç« TCP è¿æ¥

2.2 python如何控制tcp三次握手

可以看到,对于客户端而言:

  (1)当上层程序要通过tcp发送数据时,会调用一个方法【比如python的的socket.connect()】,通知tcp发送数据包【SYN】建立连接,并把状态机改为【SYN-SENT】状态;

  (2)当客户端tcp收到服务端tcp的数据包【SYN/ACK】时,改变状态机到【ESTABLISHED】状态,表示从客户端到服务器的连接已经成功建立起来。

    对于服务器而言:

  (1)上层程序如果需要对外提供tcp服务时,会调用方法【比如python中的socket.bind((ip, port))】向tcp申请一个端口,ip是向操作系统申请,可以是一个网卡的ip,也可以是所有网卡的ip。申请成功后,继续调用方法【比如python中的socket.listen()】开始在tcp分配的端口上,监听客户端发来的连接建立请求,tcp会把状态机改为【Listen】状态;

  (2)当服务器tcp收到客户端的数据包【SYN】后,就会切换状态机到【SYN-RCVD】状态,并立即回复【SYN/ACK】报文;

  (3)当服务器tcp收到客户端的回复报文【ACK】后,改变状态机到【ESTABLISHED】状态,表示从服务器到客户端的连接已经成功建立起来。

2.3 三次握手时协商了哪些参数

    如上图所示,为客户端【10.140.6.44】与服务器【10.74.97.122】三次握手的报文--【SYN】【SYN/ACK】【ACK】。三次握手除了建立双向的tcp连接以外,其实客户端和服务器还协商了各自支持的一些参数,参数的协商是通过【SYN】和【SYN/ACK】报文来完成的。

2.3.1 SYN报文中,【Flags】字段中的【SYN】标志位置为1,表示这是SYN报文。

    在TCP固定头部里的【Window size value】,表示客户端通过此SYN报文告诉服务器,自己此刻接收窗口的大小。这是TCP的流控机制里面一个参数,后文会讲。另外TCP的【Options】字段出现了,一般都会携带【MSS】、【SACK】、【NOP】字段。

    【MSS】  :占4个字节,客户端用此字段告诉服务器,自己发送的TCP数据段的最大长度,以字节为单位;

    【SACK】:占2个字节,客户端用此字段告诉服务器,自己支持SACK,即选择确认机制,后文会讲;

    【NOP】  :占1个字节,由于【Options】被定义为4字节的整数倍,因此如果出现非4字节的option时,用此option实现4字节对齐;

2.3.2 SYN/ACK报文中,它的【Flags】字段中的【SYN】和【ACK】标志位都被置为1,表示这是SYN/ACK报文。

    在TCP固定头部里的【Window size value】,表示服务器通过此SYN/ACK报文告诉客户端,自己此刻接收窗口的大小,流控机制是双向的,任何一个TCP报文都会存在。

    由于SYN报文出现了【Options】字段,因此SYN/ACK也会利用【Options】来与SYN协商参数,如果支持就回复自己对应字段的值,如果不支持此字段,就为空。

2.4 三次握手的python实现

    我们可以通过python来调用tcp收发数据,即通过前文中的【socket.recv()】和【socket.sendall()】方法控制tcp收发数据。但是,这两个方法我们都没有提供目的ip和端口,数据往谁发送,又是从哪里接收数据?

    其实这也说明了tcp的连接特性,我们在收发数据之前,是需要提前建立连接,在建立连接的过程中,我们会提供目的ip和端口。当然了,tcp是不会主动帮你应用程序建立连接的,需要你调用对应的方法,它才能帮你做事。

    如下所示,为服务器向tcp申请端口,并监听连接的代码。

# !/usr/bin python3
import socket
import contextlib
import logging


@contextlib.contextmanager
def server_prepare_conn(ip, port, backlog=5):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_sk:
        logging.debug("Try to apply for one port from os......")
        try:
            tcp_sk.bind((ip, port))
        except Exception:
            raise
        else:
            tcp_sk.listen(backlog)
            logging.debug("Succeeded to apply one port from os")

        try:
            yield tcp_sk
        except Exception:
            raise

    如下所示,为客户端要求tcp建立连接的代码。

# !/usr/bin python3
import socket
import contextlib
import logging


@contextlib.contextmanager
def client_setup_conn(ip, port):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as tcp_sk:
        logging.debug("Try to setup a connection......")
        try:
            tcp_sk.connect((ip, port))
        except Exception:
            raise
        else:
            logging.debug("Succeeded to setup a connection")

        try:
            yield tcp_sk
        except Exception:
            raise

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值