python必坑知识点(网络编程)

发布时间 2023-08-23 14:56:47作者: hai_sir

1 基础

1.1 作业题1

# 1. 简述 二层交换机 & 路由器 & 三层交换机 的作用。
"""
二层交换机:构建局域网并实现局域网内数据的转发。
路由器:实现跨局域网进行通信。
三层交换机:具备二层交换机和路由的功能。
"""

# 2. 简述常见词:IP、子网掩码、DHCP、公网IP、端口、域名的作用。
"""
IP,本质上是一个32位的二进制,通过 . 等分为了4组8位二进制。
子网掩码,用于指定IP的网络地址和主机地址。
DHCP,网络设备中的一个服务,用于给接入当前网络的电脑自动设置 IP、子网掩码、网关。(动态分配IP、子网掩码、网关)
公网IP,一般企业拉专线时会给固定的公网IP,只有具备公网IP才可以被互联网上的其他电脑访问。
端口,IP用于表示某台电脑,端口则表示此电脑上的具体程序。0-65535
域名,与IP构造对应关系,方便用户记忆。
"""

2 TCP和UDP

2.1 基础

# IP可复用
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 非阻塞
server.setblocking(False)
# TCP和UDP
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

2.1.1 TCP实例

  1. server

    import os
    import json
    import socket
    import struct
    
    
    def recv_data(conn, chunk_size=1024):
        # 获取头部信息:数据长度
        has_data_len = 0
        bytes_data = b''
        while has_data_len < 4:
            chunk = conn.recv(4 - has_data_len)
            has_data_len += len(chunk)
            bytes_data += chunk
        data_length = struct.unpack('i', bytes_data)[0]
    
        # 获取数据
        data_list = []
        has_read_data_size = 0
        while has_read_data_size < data_length:
            size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size
            chunk = conn.recv(size)
            data_list.append(chunk)
            has_read_data_size += len(chunk)
    
        data = b"".join(data_list)
    
        return data
    
    
    def recv_file(conn, save_file_name, chunk_size=1024):
        save_file_path = os.path.join('files', save_file_name)
        # 获取头部信息:数据长度
        has_read_size = 0
        bytes_list = []
        while has_read_size < 4:
            chunk = conn.recv(4 - has_read_size)
            bytes_list.append(chunk)
            has_read_size += len(chunk)
        header = b"".join(bytes_list)
        data_length = struct.unpack('i', header)[0]
    
        # 获取数据
        file_object = open(save_file_path, mode='wb')
        has_read_data_size = 0
        while has_read_data_size < data_length:
            size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size
            chunk = conn.recv(size)
            file_object.write(chunk)
            file_object.flush()
            has_read_data_size += len(chunk)
        file_object.close()
    
    
    def run():
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # IP可复用
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
        sock.bind(('127.0.0.1', 8001))
        sock.listen(5)
        while True:
            conn, addr = sock.accept()
    
            while True:
                # 获取消息类型
                message_type = recv_data(conn).decode('utf-8')
                if message_type == 'close':  # 四次挥手,空内容。
                    print("关闭连接")
                    break
                # 文件:{'msg_type':'file', 'file_name':"xxxx.xx" }
                # 消息:{'msg_type':'msg'}
                message_type_info = json.loads(message_type)
                if message_type_info['msg_type'] == 'msg':
                    data = recv_data(conn)
                    print("接收到消息:", data.decode('utf-8'))
                else:
                    file_name = message_type_info['file_name']
                    print("接收到文件,要保存到:", file_name)
                    recv_file(conn, file_name)
    
            conn.close()
        sock.close()
    
    
    if __name__ == '__main__':
        run()
    
    
  2. client

    import os
    import json
    import socket
    import struct
    
    
    def send_data(conn, content):
        data = content.encode('utf-8')
        header = struct.pack('i', len(data))
        conn.sendall(header)
        conn.sendall(data)
    
    
    def send_file(conn, file_path):
        file_size = os.stat(file_path).st_size
        header = struct.pack('i', file_size)
        conn.sendall(header)
    
        has_send_size = 0
        file_object = open(file_path, mode='rb')
        while has_send_size < file_size:
            chunk = file_object.read(2048)
            conn.sendall(chunk)
            has_send_size += len(chunk)
        file_object.close()
    
    
    def run():
        client = socket.socket()
        client.connect(('127.0.0.1', 8001))
    
        while True:
            """
            请发送消息,格式为:
                - 消息:msg|你好呀
                - 文件:file|xxxx.png
            """
            content = input(">>>")  # msg or file
            if content.upper() == 'Q':
                send_data(client, "close")
                break
            input_text_list = content.split('|')
            if len(input_text_list) != 2:
                print("格式错误,请重新输入")
                continue
    
            message_type, info = input_text_list
    
            # 发消息
            if message_type == 'msg':
    
                # 发消息类型
                send_data(client, json.dumps({"msg_type": "msg"}))
    
                # 发内容
                send_data(client, info)
    
            # 发文件
            else:
                file_name = info.rsplit(os.sep, maxsplit=1)[-1]
    
                # 发消息类型
                send_data(client, json.dumps({"msg_type": "file", 'file_name': file_name}))
    
                # 发内容
                send_file(client, info)
    
        client.close()
    
    
    if __name__ == '__main__':
        run()
    
    

2.1.2 UDP实例

  1. server

    import socket
    
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    server.bind(('127.0.0.1', 8002))
    
    while True:
        data, (host, port) = server.recvfrom(1024) # 阻塞
        print(data, host, port)
        server.sendto("好的".encode('utf-8'), (host, port))
    
  2. client

    import socket
    
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    while True:
        text = input("请输入要发送的内容:")
        if text.upper() == 'Q':
            break
        client.sendto(text.encode('utf-8'), ('127.0.0.1', 8002))
        data, (host, port) = client.recvfrom(1024)
        print(data.decode('utf-8'))
    
    client.close()
    

2.2 三次握手,四次挥手

三次握手情景:
1、客户端主机说:“我可以给你发送数据吗?”
2、服务器说:“可以的,不过我可能也会给你发送数据。”
3、客户端:“好,那我开始互相发送数据吧。”

四次挥手的情景大致是这样的:
1、客户端主机说:“我没有数据了,断开连接吧。 ”
2、服务器说:“好,但是我还有数据(不断给客户端发送数据,此时客户端已经不能给服务端发送数据了,但是必须要就收服务端发来的数据)。”
3、(当服务端给客户端发完数据后)服务端说:“我发完了,断开连接吧。”
4、客户端说:“好,断开连接吧。”

2.3 OSI七层模型

img

2.4 对比

UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅8字节 首部最小20字节,最大60字节
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输

3 粘包

当客户端向服务端放松数据时,服务端由于网络等原因,无法迅速的接受到数据,这时如果客户端连续的发送多条数据,服务端接收时无法区分客户端发来几条数据,而是直接接收全部(数据量如果小的话),这时客户端发送的几条消息就会被服务端当成一条数据接收。
处理:
	客户端每次发送数据时,先把数据的长度按固定的字节数发送到服务端,服务端在接收时,先按固定的字节数把客户端要发送的数据的长度获取一下,之后再根据数据长度获取客户端发来的数据。

4 阻塞和非阻塞

server.setblocking(False)  # 加上就变为了非阻塞

如果代码变成了非阻塞,程序运行时一旦遇到 `accept`、`recv`、`connect` 就会抛出 BlockingIOError 的异常。

5 IO多路复用

IO多路复用 + 非阻塞,可以实现让TCP的服务端同时处理多个客户端的请求
"""
优点:
	1. 干点那其他的事。
	2. 让服务端支持多个客户端同时来连接。
"""

# ################### socket服务端 ###################
import select
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)  # 加上就变为了非阻塞
server.bind(('127.0.0.1', 8001))
server.listen(5)

inputs = [server, ]  # socket对象列表 -> [server, 第一个客户端连接conn ]

while True:
    # 当 参数1 序列中的socket对象发生可读时(accetp和read),则获取发生变化的对象并添加到 r列表中。
    # r = []
    # r = [server,]
    # r = [第一个客户端连接conn,]
    # r = [server,]
    # r = [第一个客户端连接conn,第二个客户端连接conn]
    # r = [第二个客户端连接conn,]
    r, w, e = select.select(inputs, [], [], 0.05)
    for sock in r:
        # server
        if sock == server:
            conn, addr = sock.accept()  # 接收新连接。
            print("有新连接")
            # conn.sendall()
            # conn.recv("xx")
            inputs.append(conn)
        else:
            data = sock.recv(1024)
            if data:
                print("收到消息:", data)
            else:
                print("关闭连接")
                inputs.remove(sock)
    # 干点其他事 20s

# ################### socket客户端 ###################
import socket

client = socket.socket()
# 阻塞
client.connect(('127.0.0.1', 8001))

while True:
    content = input(">>>")
    if content.upper() == 'Q':
        break
    client.sendall(content.encode('utf-8'))

client.close()