Python学习笔记之 网络编程(socket套接字编程)

socket套接字编程

服务端&客户端流程

TCP服务端&客户端示例代码:

# 服务端
import socket,threading
def jieshou(a,b):
    print(f'建立连接{a}{b}')
    while True:
        c = a.recv(1024) # 接收消息
        if c:
            shuju = c.decode('gbk') # 解码
            print(f'{b}数据:{shuju}')
        else:
            break
    a.close()

if __name__ == '__main__':
    tcp = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    tcp.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)  # 设置端口号复用
    tcp.bind(("",9090)) # 绑定地址,端口号
    tcp.listen(128) # 设置监听
    while True:
        a, b = tcp.accept()
        thread1 = threading.Thread(target=jieshou,args=(a,b)) # 创建子线程
        thread1.daemon = True # 设置为守护线程
        thread1.start() # 执行
    tcp.close()
# 客户端
import socket

if __name__ == '__main__':
    my_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    my_socket.connect(("172.40.91.185",9090)) # 连接服务端
    my_socket.send('Hello'.encode('gbk')) # 发送消息
    my_socket.close()

运行结果:

客户端:

服务端:

UDP客户端&服务端示例:

# 客户端
import socket

udp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建套接字
udp.sendto('Hello'.encode('gbk'),("172.40.91.185",10000)) # 发送消息
udp.close()
# 服务端
import socket
'''
UDP不用连接,直接接收数据
'''
if __name__ == '__main__':
    udp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 创建套接字
    udp.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True) # 设置端口号复用
    udp.bind(("",10000)) # 绑定地址
    while True:
        data, addr = udp.recvfrom(2048) # 接收消息
        if not data:
            break
        print(f'来自{addr}的数据:{data}')
    udp.close()

运行结果:

客户端:

服务端:

解析

1.创建套接字

sockfd = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM,proto=0,fileno=None)

功能:创建套接字

参数:

family -> 网络地址类型: AF_INET_表示_IPv4、 AF_INET6_表示_IPv6

type -> 套接字类型: SOCK_STREA(数据流,基于TCP)、 SOCK_DGRAM(数据包,基于UDP)

_proto -> _通常为0, 选择子协议

返回值: 套接字对象

2.设置端口号复用

sockfd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)

3.绑定地址

本地地址:'localhost', '127.0.0.1'

网络地址:'172.40.91.185'

自动获取地址:'0.0.0.0'

sockfd.bind(("",9090)) # bind((IP地址,端口号))

4.设置监听(TCP)

sockfd.listen(n)

功能: 将套接字设置为监听套接字,确定监听队列大小

参数: 监听队列大小(没什么用,摆设而已,一般会开多线程连接)

5.等待处理客户端连接请求(TCP)

connfd, addr = sockfd.accept()

功能: 阻塞等待处理客户端请求

返回值: 元组

_connfd -> _ 客户端连接套接字

_addr -> _ 连接的客户端地址

6.连接服务端(TCP)

sockfd.connect((IP地址,端口号))

功能: 连接指定的服务端

7.收发消息

TCP版: data = connfd.recv(buffersize)

UDP版: data = connfd.recvfrom(buffersize)

功能: 接收消息, 如果一方断开连接,则会返回空字符串

参数: 每次传输的数据大小

TCP粘包

原因: TCP以字节流方式传输,没有边界消息。多次发送的消息被一次接收,此时就会形成粘包

影响: 如果每次发送的内容是一个独立含义,需要接收端独立解析,此时粘包会有影响

处理方法

1. 人为的添加消息边界

2. 控制发送速度
3. 使用struct

import struct

struct.pack(format,v)
功能:打包字节对象(bytes)
参数:format 格式化字符 'i' int; 'q' long long
返回:bytes

实例:文件传输助手

# 发送端
import socket,os
from struct import pack

def send_file(file_name,file_socket:socket.socket):
    try:
        f = open(file_name,'rb')
        size = os.path.getsize(file_name)
        if size < 1024:
            read_size = 500
        elif size < 1024*1024 and size >= 1024:
            read_size = 500*1024
        else:
            read_size = 500*1024*1024
        file_socket.send(pack('q',size))
        del size
        file_socket.recv(1024)
        while True:
            data = f.read(read_size)
            if not data:
                break
            file_socket.send(data)
        f.close()
    except FileNotFoundError:
        print(f'没有找到{file_name}')

if __name__ == '__main__':
    try:
        file_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        file_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
        ip = str(input('请输入接收端IP:'))
        port = int(input('请输入接收端端口号:'))
        file_socket.connect((ip,port))
        print('连接成功,准备开始传输。。。')
        path = r'%s' % (input('请输入文件所在文件夹:') + '/')
        print(file_socket.recv(1024).decode('gbk'))
        file_name = str(input())
        file_socket.send(file_name.encode('gbk'))
        send_file(path + file_name,file_socket)
    except ConnectionResetError:
        print('接收端断开连接')
# 接收端
import socket,os
from time import time
from struct import unpack
from tqdm import tqdm

DOWNLOAD_PATH = 'D:/Python文件传输/' # 传输目录

def download(file_name,file_socket:socket.socket):
    file_size = unpack('q',file_socket.recv(1024))[0]
    if file_size < 1024:
        print(f'文件大小:{file_size} B')
    elif file_size < 1024*1024 and file_size >= 1024:
        print(f'文件大小:{file_size/1024} KB')
    else:
        print(f'文件大小:{file_size/(1024*1024)} MB')
    f = open(file_name,'wb')
    print('开始传输...')
    download_size = 2048
    file_socket.send('开始传输'.encode('gbk'))
    start = time()
    for i in tqdm(range(int(file_size/download_size) + 1)):
        data = file_socket.recv(download_size)
        f.write(data)
    end = time()
    f.close()
    print('传输完成!')
    print(f'大约耗时{end - start}秒')

if __name__ == '__main__':
    os.chdir(DOWNLOAD_PATH)
    try:
        file_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        file_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)
        file_socket_port = int(input('请输入端口号:'))
        file_socket.bind(('',file_socket_port))
        print('成功启动,等待连接。。。')
        file_socket.listen(1)
        f_socket, f_addr = file_socket.accept()
        print(f'建立连接{f_addr}')
        f_socket.send('请输入文件名'.encode('gbk'))
        file_name = f_socket.recv(1024)
        download(file_name.decode('gbk'),f_socket)
        f_socket.close()
        file_socket.close()
    except ConnectionResetError:
        print('发送端已断开连接')
    except UnicodeDecodeError:
        print('文件编码错误,请检查文件格式是否正确')
原文地址:https://www.cnblogs.com/zhujiangyu/p/13542390.html