学习笔记--TCP发送和接收数据

  • TCP协议
  • 三次握手
  • 四次挥手
  • UDP协议介绍
  • TCP通信
  • TCP客户端构建流程
  • TCP服务端
  • TCP与UDP区别
  • socket之send和recv原理剖析
  • send原理剖析
  • recv原理剖析
  • send和recv原理剖析图
  • 黏包
  • 黏包现象
  • 解决黏包现象


TCP协议

TCP协议,传输控制协议,是一种面向连接的(通信双方必须先建立连接才能进行数据的传输)、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。
TCP通信需要经过创建连接、数据传送、终止连接三个步骤.
TCP 通信模型中,在通信开始之前,一定要先建立相关连接,才能发送数据。

三次握手

Android tcp发送消息并等待响应 发送tcp请求_python

第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
完成了三次握手,客户端和服务器端就可以开始传送数据。

四次挥手

Android tcp发送消息并等待响应 发送tcp请求_python_02

第一次挥手:A数据传输完毕需要断开连接,A的应用进程向其TCP发出连接释放报文段(FIN = 1,序号seq = u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1状态,等待B的确认。

第二次挥手:B收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT关闭等待状态,此时的TCP处于半关闭状态,A到B的连接释放。而A收到B的确认后,进入FIN-WAIT-2状态,等待B发出的连接释放报文段。

第三次挥手:当B数据传输完毕后,B发出连接释放报文段(FIN = 1,ACK = 1,序号seq = w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A 的最后确认。

第四次挥手:A收到B的连接释放报文段后,对此发出确认报文段(ACK = 1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSE状态。

#TCP特点

  • 面向连接
  • 可靠传输
  • TCP采用发送应答机制
  • 超时重传
  • 错误校验
  • 流量控制和阻塞管理

UDP协议介绍

当应用程序希望通过UDP与一个应用程序通信时,传输数据之前源端和终端不建立连接。当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。

TCP通信

Android tcp发送消息并等待响应 发送tcp请求_数据_03

TCP客户端构建流程

  • 1.创建socket
  • 2.链接服务器
  • 3.接收数据
  • 4.关闭套接字
import socket
#创建套接字
tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#建立连接
tcp_client.connect(('127.0.0.1',8050))
data = input('输入信息:')
#发送数据
tcp_client.send(data.encode('utf-8'))
#接收数据
new_data = tcp_client.recv(1024)
print(new_data.decode('utf-8'))
#关闭套接字
tcp_client.close()

TCP服务端

1.socket创建套接字
2.bind绑定IP和port
3.listen使套接字变为可以被动链接
4.accept等待客户端的链接
5.recv/send接收发送数据

import socket
#创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#建立绑定
tcp_server_socket.bind(('127.0.0.1',8050))
#监听
tcp_server_socket.listen()
#等待客户端连接
new_server_socket,addr = tcp_server_socket.accept()
#接收数据
data = new_server_socket.recv(1024)
print(data.decode('utf-8'))
new_data=input('输入传送数据:')
#发送数据
new_server_socket.send(new_data.encode('utf-8'))
new_server_socket.close()
tcp_server_socket.close()

TCP与UDP区别

  • TCP面向连接;UDP是无连接的,即发送数据之前不需要建立连接。
  • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
  • UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
  • 每一条TCP连接只能是点到点的:UDP支付一对一,一对多和多对一和多对多的交互通信。
  • TCP对系统资源要求较多,UDP对系统资源要求较少。

socket之send和recv原理剖析

当创建一个TCP socket对象的时候会有一个发送缓冲区和一个接收缓冲区,这个发送和接收缓冲区指的就是内存中的一片空间。

send原理剖析

send发数据,必须得通过网卡发送数据,应用程序是无法直接通过网卡发送数据的,它需要调用操作系统接口,也就是说,应用程序把发送的数据先写入到发送缓冲区(内存中的一片空间),再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡。

recv原理剖析

应用软件是无法直接通过网卡接收数据的,它需要调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区(内存中的一片空间),应用程序再从接收缓存区获取客户端发送的数据。

send和recv原理剖析图

Android tcp发送消息并等待响应 发送tcp请求_客户端_04

黏包

黏包现象

当发送网络数据时,tcp协议会根据Nagle算法将时间间隔短,数据量小的多个数据包打包成一个数据包,先发送到自己操作系统的缓存中,然后操作系统将数据包发送到目标程序所对应操作系统的缓存中,最后将目标程序从缓存中取出,而第一个数据包的长度,应用程序并不知道,所以会直接取出数据或者取出部分数据,留部分数据在缓存中,取出的数据可能第一个数据包和第二个数据包粘到一起。
服务端

import socket

def main():
    #创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    #建立绑定
    tcp_server_socket.bind(('127.0.0.1',8050))
    #监听
    tcp_server_socket.listen()
    # 等待客户端连接
    while True:
        new_server_socket, addr = tcp_server_socket.accept()
        #接收数据
        while True:
            g_data = new_server_socket.recv(1024)
            print(g_data.decode('utf-8'))
            if g_data == b'':
                break

        # 发送数据
        # new_data=input('输入传送数据:')
        # new_server_socket.send(new_data.encode('utf-8'))
        new_server_socket.close()
    tcp_server_socket.close()

if __name__ == '__main__':
    main()

客户端

import socket
def main():

    #创建套接字
    tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #建立连接
    tcp_client.connect(('127.0.0.1',8050))
        # data = input('输入信息:')
    #分两次发送数据
    tcp_client.send('data'.encode('utf-8'))
    tcp_client.send('has'.encode('utf-8'))

    #接收数据
        # new_data = tcp_client.recv(1024)
        # print(new_data.decode('utf-8'))
    #关闭套接字
    tcp_client.close()

if __name__ == '__main__':
    main()

运行之后,我们在服务端接收到的信息为datahas。

解决黏包现象

这里我们需要引用struct模块,在客户端使用模块中的stuct.pack()方法,在服务端使用stuct.unpack()方法。客户端发送数据前,先对数据数据长度,根据数据长度来接收指定大小的数据。

struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型,可以把这里的字符串理解为字节流,或字节数组)。

语法格式:struct.pack(fmt, v1, v2, …),参数fmt是格式字符串详细见下图,v1, v2, …表示要转换的python值。返回值是一个元组。

Android tcp发送消息并等待响应 发送tcp请求_python_05

struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。
语法格式:struct.unpack(fmt, string)

服务端

import socket,struct

def main():
    #创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

    #建立绑定
    tcp_server_socket.bind(('127.0.0.1',10001))
    #监听
    tcp_server_socket.listen()
    while True:
        # 等待客户端连接
        new_server_socket, addr = tcp_server_socket.accept()
        while True:
            #接收数据长度
            data = new_server_socket.recv(4) #由于'i'格式占用四个字节,指定接收内容
            # print(data)
            #实际数据的长度
            try:
                num = struct.unpack('i',data)[0]
            except:
                break
            if num < 1024:  #当数据小于1024时
                total_data = new_server_socket.recv(num).decode('utf-8')
                print(total_data)
            #根据数据长度接收数据
            else:
                l_data = 0
                total_data = ''  #接收结果
                while l_data < num: #当数据大于1024
                #接收数据
                    g_data = new_server_socket.recv(1024).decode('utf-8')  #每次接收1024
                    total_data += g_data
                    l_data += len(g_data)
                print(total_data)
        new_server_socket.close()
    tcp_server_socket.close()

if __name__ == '__main__':
    main()

客户端

import socket,struct

def send_data(data):
    msg = data  # 数据内容
    b_data = struct.pack('i', len(msg))  # 将数据转化为字节流
    tcp_client.send(b_data)
    tcp_client.send(msg.encode('utf-8'))


if __name__ == '__main__':
    # 创建套接字
    tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 建立连接
    tcp_client.connect(('127.0.0.1',10001))
    info = 'ad'
    send_data(info)
    info = 'qaq.123456'
    send_data(info)
    # 关闭套接字
    tcp_client.close()