Python 笔记02 (网络交互 TCP/UDP)

一 socket网络及差别介绍

TCP(传输控制协议)和UDP(用户数据报协议)是两种常见的互联网传输协议,它们之间有很多区别,包括以下几个主要方面:

1.1 TCP

TCP是传输控制协议,是面向连接的通讯协议(如:打电话),通过三次握手建立连接,通讯完成时四次挥手,一般应用在对安全性、完整性有严格要求的场景,如FTP、SMTP、HTTP等

  1. 优点:TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序
  2. 缺点:TCP相对于UDP速度慢一点,效率低,而且要求系统资源较多,每个连接都会占用系统的CPU、内存等硬件资源

1.2 UDP

UDP是用户数据报协议,是面向无连接的通讯协议(如:发短信)

  1. 优点:UDP速度快、操作简单、要求系统资源较少
  2. 缺点:不可靠,可能会出现丢包、乱序、数据不完整

1.3 TCP 与 UDP 的区别:

  1. 连接 TCP 是面向连接的传输层协议,传输数据前先要建立连接UDP 是不需要连接,即刻传输数据。
  2. 服务对象 TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信
  3. 可靠性 TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。UDP 是尽最大努力交付,不保证可靠交付数据。
  4. 拥塞控制、流量控制 TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率
  5. 首部开销 TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。UDP 首部只有 8 个字节,并且是固定不变的,开销较小,对系统资源要求较少。
  6. 实时性 UDP 具有较好的实时性,工作效率比 TCP 协议高。

1.4 应用场景

  1. 由于 TCP 是面向连接,能保证数据 的可靠性交付,因此经常用于:(20/21端口)FTP 文件传输HTTP / HTTPS(80端口) 、SMTP(简单邮件传送协议)、TELNET(远程终端协议)
  2. 由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:包总量较少的通信,如 DNS 、SNMP、TFTP(简单文件传输协议) 等、视频、音频等多媒体通信、广播通信

1.5 Socket数据传输方式

常用的有两种:

  1. SOCK_STREAM:表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。针对于面向连接的TCP服务应用;
  2. SOCK_DGRAM:表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。对应于无连接的 UDP服务应用。

1.6 服务器端

from socket import *
server = socket(AF_INET, SOCK_DGRAM)
server_host_port = ('127.0.0.1', 6000)   # 服务器的IP地址和端口

# 接收数据前绑定端口
server.bind(server_host_port)

while True:
    # 接收数据
    data = server.recvfrom(1024)

    # print('data:', data)  # (b'\xe4\xbd\xa0\xe5\xa5\xbd', ('127.0.0.1', 61328))
    print('访问者:', data[0].decode('utf-8'))  # 你好
    # print(f'客户端的IP:{data[1][0]}  \n客户端的端口:{data[1][1]}')

    """重新发送数据"""
    send_data = input('客服说:')
    server.sendto(send_data.encode('utf-8'), data[1])

# server.close()

1.7 客户端

from socket import *
client = socket(AF_INET, SOCK_DGRAM)
server_host_port = ('127.0.0.1', 6000)   # 指定数据接收方

while True:
    data = input('访问者:')
    data = data.encode('utf-8')
    client.sendto(data, server_host_port)   # 发送数据

    if data.decode('utf-8') == 'bye':
        break

    """接收返回数据数据"""
    recv_data = client.recvfrom(1024)
    print(f"客服说:{recv_data[0].decode('utf-8')}")

print('程序关闭')
client.close()

二 UDP协议

udp的交互使用:sendto 和 recvfrom

SOCK_DGRAM:表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。对应于无连接的 UDP服务应用。

2.1 服务器代码

from socket import *
import struct
server_socket = socket(AF_INET, SOCK_DGRAM)

host_port = ('localhost', 8888)   # 端口号

# 开始监听
server_socket.bind(host_port)

# 接收数据
data = server_socket.recvfrom(1024)
print(data, type(data))

# 解析操作码
recv_data = data[0]
new_data = struct.unpack('!H', recv_data[:2])
print('客户端请求的操作码:', new_data)

# 解析文件名
file_name = recv_data[2:-7].decode('utf-8')
print('客户端请求下载的文件名:', file_name)
server_socket.close()

2.2 客户端代码

from socket import *
import struct
client_socket = socket(AF_INET, SOCK_DGRAM)

host_port = ('localhost', 8888)   # 端口号

file_name = input('请输入需要上传的文件名:').encode('utf-8')
print('file_name:', file_name, len(file_name))
data = struct.pack('!H%dsb5sb' % len(file_name), 1, file_name, 0, 'octet'.encode('utf-8'), 0)

# 发送数据
client_socket.sendto(data, host_port)
client_socket.close()

三 TCP协议

tcp的交互使用:send 和 recv

SOCK_STREAM:表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。针对于面向连接的TCP服务应用;

3.1 TCP服务器代码

from socket import *
import random

# 创建SOCKET对象
server_socket = socket(AF_INET, SOCK_STREAM)

# 绑定IP和端口
host_port = ('', 6666)   # 不写本机所有
server_socket.bind(host_port)

# 设置listen
server_socket.listen(5)

while True:
    # 等待客户端连接
    client_socket, addr = server_socket.accept()
    print('客户端已连接,3次握手完成! ')
    # print('client_socket:', client_socket)

    # 接收数据
    data = client_socket.recv(1024).decode('utf8')
    print('data:', data)

    oper_code = data.split(':')[0]  # 操作码
    recv_data = data.split(':')[1]  # 需要上传和下载的文件

    if oper_code == '1':  # 下载操作
        file_read = open(recv_data, 'r', encoding='utf8')
        data = file_read.read()
        # 将数据发给客户端
        client_socket.send(data.encode('utf-8'))
        file_read.close()

    elif oper_code == '2':  # 上传操作
        file_write = open(str(random.randint(1000, 9999)) + '.txt', 'w', encoding='utf-8')
        file_write.write(recv_data)
        file_write.close()
        print('服务器接收完成!')

    elif oper_code == '0':  # 已退出
        print(recv_data)

3.2 客户端代码

from socket import *

while True:
    client_socket = socket(AF_INET, SOCK_STREAM)

    # 指定要连接的IP
    host_port = ('127.0.0.1', 6666)

    # 开始连接服务器
    client_socket.connect(host_port)

    choice = eval(input('请选择操作: 0.退出  1.下载  2.上传  \n'))

    if choice == 1:
        file_name = input('请输入要下载的文件名:')
        # 告诉服务器要下载的文件名
        join_data = (str(choice) + ':' + file_name).encode('utf-8')
        # 发送数据
        client_socket.send(join_data)

        # 接收服务器返回的数据
        recv_data = client_socket.recv(1024).decode('utf-8')
        # 写入本地磁盘
        download = open(file_name, 'w', encoding='utf-8')
        download.write(recv_data)
        download.close()
        print('下载完成')

    elif choice == 2:  # 上传
        path_name = input('请输入要上传的文件名:')
        # 本地读取
        upload = open(path_name, 'r', encoding='utf-8')
        upload_data = upload.read()

        # 拼接数据结构
        data = (str(choice) + ':' + upload_data).encode('utf-8')
        # 向服务器发送数据
        client_socket.send(data)
        upload.close()
        print('数据上传成功!')

    elif choice == 0:
        # 告诉服务器已退出
        client_socket.send((str(choice) + ':' + '客户端已退出').encode('utf-8'))
        break

print('客户端关闭')

TCP连接时三次握手

  • 第一次:客户端向服务器端发送连接报文(SYN=1),同时选择一个初始序列号 seq=x,一起发送

  • 第二次:服务器收到报文后向客户端发报文,确认报文为:(ACK=1,SYN=1),确认号为ack=x+1,同时服务器初始化一个序列号:seq=y,一起发送

  • 第三次:客户端向服务器端发送报文(ACK=1),同时发送:ack=x+1,seq=y+1进行确认

TCP断开时四次挥手:开时四次挥手:

  • 第一次:客户端发送释放报文并停止发送数据(FIN=1),带上序列号 seq=u,客户端进入终止等待状态(FIN-WAIT-1)

  • 第二次:服务器收到报文后释放报文,发出确认报文(ACK=1,ack=u+1),并且带上序列号 seq=v,服务器进入关闭等待状态(CLOSE-WAIT)

  • 第三次:服务器在数据传输完毕后发送连接释放报文(FIN=1,ack=u+1),同时发送序列号:seq=w,服务器进入最后确认状态(LAST-ACK)

  • 第四次:客户端收到释放报文后,向服务器发送报文(ACK=1,ack=w+1),发送序列号 seq=u+1,客户端进入时间等待状态(TIME-WAIT)。服务器接收到报文后直接关闭,客户端需要等2**MSL(最长报文段寿命)后结束

四 使用TCP传输较大的文件

4.1 服务端代码

from socket import *
import struct
server_socket = socket(AF_INET, SOCK_STREAM)  # TCP

host_port = ('', 6666)
server_socket.bind(host_port)

server_socket.listen(1024)
# 开始监听
conn_socket, addr = server_socket.accept()
print('查看服务器接收到的请求地址:', addr)

# 接收数据
data_header = conn_socket.recv(4)
# 解包
size = struct.unpack('!i', data_header)[0]
print('size:', size)

# 将收到的数据上传到磁盘
file = open('a.pptx', 'wb')
recv_size = 0
while recv_size < size:
    data_pack = conn_socket.recv(1024)
    recv_size += len(data_pack)

    file.write(data_pack)

file.close()
print('服务器接收完成')
conn_socket.close()
server_socket.close()

4.2 客户端代码

from socket import *
import os.path
import struct

client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(('192.168.146.1', 6666))

# 发送内容
file_path = './111.pptx'

# 获取文件大小
size = os.path.getsize(file_path)
print('size:', size)

# 对struct数据进行打包
data = struct.pack('!i', size)
# 发送数据
client_socket.send(data)

file = open(file_path, 'rb')
# 循环读取 1024
while True:
    data_pack = file.read(1024)
    if not data_pack:
        break
    client_socket.send(data_pack)   # 直接发

client_socket.close()

猜你喜欢

转载自blog.csdn.net/March_A/article/details/133254296