网络编程之套接字

一:套接字基础概念#

【1】

(1)socket起源

  (1)C/S架构要遵循OSI七层参考模型

  (2)如果我们想自己开发C/S程序 那么就必须在自己的程序编写OSI七层参考模型 效率低下 开发难度大

  (3)socket就是将OSI封装好了 我们只需要调用即可

PS:socke类似于操作系统 将复杂的操作给封装 对外提供简易的接口实现操作

 

(2)图解:

C/S架构基于传统的TCP/IP

 

C/S基于socke架构:

 

二:TCP工作套接字流程#

(1)简易版套接字

例如:

复制代码
import socket

# 获取一个服务端的对象
server = socket.socket()
# 设置连接地址 以及端口号
server.bind(('127.0.0.1',8080))
# 允许最大客户端连接数
server.listen(6)
# 允许客户端发送请求
conn,addr = server.accept()  # conn:TCP连接通道 addr:服务端地址与端口号
print(conn)      # <socket.socket fd=268, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 13436)>
print(addr)     # ('127.0.0.1', 13436)
# 从内存读取数据大小
data = conn.recv(1024)  # b'hello i am client'
print(data)      #
conn.send(b'Hello i am service')
服务端
复制代码
复制代码
import socket

# 获取一个客户端对象
client = socket.socket()

# 与服务端连接的地址
client.connect(('127.0.0.1',8080))
# 网络通信基于二进制
client.send(b'hello i am client')
data = client.recv(1024)
print(data)  # b'Hello i am service'
客户端
复制代码

PS:该版本不能进行循环交互数据

 

(2)循环交互数据版

例如:

复制代码
import socket

# 生成服务端对象
service = socket.socket()

# 设置端口
service.bind(('127.0.0.1',80))

# 设置半地址池
service.listen(6)

# 进入阻塞状态 允许接入
conn ,addr = service.accept()
# 循环交互数据
while True:
    data = conn.recv(1024)  # 从内存接收最大字节
    print(data)
    # 将客户端给我发送的数据 全部大写形式返回
    conn.send(data.upper())
服务端进阶版1
复制代码
复制代码
import socket

client = socket.socket()

# 连接地址+端口
client.connect(('127.0.0.1',8080))
while True:
    client.send(b'hello')
    data = client.recv(1024)
    print(data)
客户端进阶版1
复制代码

 PS:虽然可以进行循环交互数据 但是客户端退出 服务端会报错

 

(3)解决客户端退出报错版

例如:

复制代码
import socket

'''
客户端给我发送什么 我将数据转换成大写 返回给客户端

'''
service = socket.socket()
# 服务端ip+port
service.bind(('127.0.0.1', 8080))
# 最大访问数
service.listen(6)
while True:
    # 当有客户端进行退出 结束下列循环 进行新的循环
    conn, addr = service.accept()
    while True:
        try:
            data = conn.recv(1024)
            # 针对MAC Linux 如果 收不到数据会一直为空 防止陷入一直等待
            if len(data) == 0:
                break
            print(data)
            conn.send(data.upper())
        except  ConnectionRefusedError as e:
            print(e)
            # 捕捉到报错信息 将循环断开
            break
        # 将此次连接给关闭
        conn.close()
服务端进阶版2
复制代码
复制代码
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    # 发送二进制 将字符串转成二进制
    msg = input('输入命令>>:').strip().encode('utf-8')
    '''
    防止客户端发送数据为空 服务端一直等待
    服务端接收不到客户端的数据 无法进行返回
    客户端收不到服务端的回复 一直进行等等
    
    '''
    if len(msg) == 0:
        continue
    client.send(msg)
    data = client.recv(1024)
    print(data)
客户端进阶版2
复制代码

 

三:TCP粘包问题#

(1)产生场景:

  (1)TCP属于流式协议

  (2)当产生的数据流比较小 且数据流发送间隔比较短 

  (3)TCP会一次性将数据打包发送给对方

  (4)如果发送数据比较大 而对方接能力有限 发送数据需要在内存中排队等待等待发送 

  (5)之后输入的内容需要等前面发送数据全部被接收之后 其才能发送

例如:

复制代码
import socket

service = socket.socket()
service.bind(('127.0.0.1', 8080))
service.listen(6)

conn,addr = service.accept()
print(conn.recv(1024))  # b'hellohellohello'
print(conn.recv(1024))  # b''
print(conn.recv(1024))  # b''
服务端
复制代码
复制代码
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))

client.send(b'hello')
client.send(b'hello')
client.send(b'hello')
客户端
复制代码

PS:如上述中 客户端一次性将数据发送给服务端

 

(2)解决TCP粘包问题思路:

  (1)由于我们在接收数据时候 不能一次性知道对方发来的数据大小是多少

  (2)我们需要有一个第三方告诉我们该发来的数据包大小是多少

  (3)但是这个第三方数据大小我们不能确定

  (4)因此我们需要通过一个方法将第三方数据大小给固定 同时第三方告诉我们接收数据大小

 

(3)struct模块

  (1)作用:

    (1)将数据打包成一个固定的长度

    (2)数据解包的时候 还原成原始数据长度

例如:

复制代码
import struct

res = '1234156132156146313215613512361514531'
print(len(res))  # 37

# 将数据封装
res1 = struct.pack('i',len(res))
# 打印封装之后的长度
print(len(res1))    # 4

# 进行解封装
res2 = struct.unpack('i',res1)[0]
print(res2) # 37
复制代码

 

(4)解决TCP粘包基础版

例如:

复制代码
import socket, struct, subprocess
service = socket.socket()
service.bind(('127.0.0.1',8080))
service.listen(6)

while True:
    # 允许外部接入
    con,add = service.accept()
    # 与客户端循环交互
    while True:
        # 防止客户端退出 进行异常捕获
        try:
            cmd = con.recv(1024)
            if len(cmd) == 0:
                break
            # 对方传来的是二进制 需要转换成字符串获取数据
            cmd = cmd.decode('utf-8')
            # 从管道中获取数据
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 将数据返回给对方
            res = obj.stderr.read() + obj.stdout.read()
            # 制作包头 以i模式
            header = struct.pack('i',len(res))
            # 发送包头
            con.send(header)
            # 发送真实数据
            con.send(res)
        except ConnectionResetError as e:
            print(e)
            break
    con.close()
服务端
复制代码
复制代码
import socket, struct

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    # 输入命令 转换成二进制传输
    cmd = input('请输入命令>>:').strip().encode('utf-8')
    if len(cmd) == 0:
        continue
    # 发送数据
    client.send(cmd)
    # 接收包头
    header = client.recv(4)
    # 解封包头 获取内部数据
    real_size = struct.unpack('i',header)[0]

    # 循环打印内部接收数据

    # 定义初始接收值
    recv_size = 0

    # 真实数据大小
    real_data = b''

    while recv_size < real_size:
        # 每次初始接收1024
        data = client.recv(1024)

        # 真实数据大小
        real_data += data

        # 真实接收值 不能确定最终数据就是剩余1024 所以每次接收是接收值长度
        recv_size += len(data)

        # 转码打印
    print(real_data.decode('gbk'))
客户端
复制代码

 

(5)解决TCP粘包问题进阶版

  (1)在上述版本中 虽然将数据封装成固定的大小

  (2)但是封装能力有限的 当数据特别大的时候 struct模块不能封装

  (3)假如我想对数据进行一些描述 例如:电影 附带演员名单 以及影评等 上述版本就不能满足了 

  解决办法:

  (1)将数据封装在字典内部 

  (2)字典可以对内部数据进行描述

例如:

复制代码
import socket, struct, subprocess, json

service = socket.socket()
service.bind(('127.0.0.1', 8080))
service.listen(6)

while True:
    # 允许外部接入
    con, add = service.accept()
    # 与客户端循环交互
    while True:
        # 防止客户端退出 进行异常捕获
        try:
            cmd = con.recv(1024)
            if len(cmd) == 0:
                break
            # 对方传来的是二进制 需要转换成字符串获取数据
            cmd = cmd.decode('utf-8')
            # 从管道中获取数据
            obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            # 将数据返回给对方
            res = obj.stderr.read() + obj.stdout.read()
            # 定义一个字典
            info = {'name': 'SR', 'file_size': len(res)}

            # 将字典转成字符串 统计长度
            json_dict = json.dumps(info)
            # 定义一个字典包头
            header_dict = struct.pack('i', len(json_dict))
            # 发送字典包头
            con.send(header_dict)
            # 发送字典数据 以二进制发送
            con.send(json_dict.encode('utf-8'))
            # 发送真实数据
            con.send(res)
        except ConnectionResetError as e:
            print(e)
            break
    con.close()
服务端进阶版2
复制代码
复制代码
import socket, struct, json

client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
    # 输入命令 转换成二进制传输
    cmd = input('请输入命令>>:').strip().encode('utf-8')
    if len(cmd) == 0:
        continue
    # 发送数据
    client.send(cmd)

    # 接收字典包头
    dict_header = client.recv(4)

    # 解封字典包头 获取字典
    dict_size = struct.unpack('i', dict_header)[0]


    # 接收字典数据
    dict_byte = client.recv(dict_size)

    # 对方传送的二进制 进行转码 以及序列化
    json_dict = json.loads(dict_byte.decode('utf-8'))

    # 循环打印内部接收数据

    # 定义初始接收值
    recv_size = 0

    # 真实数据大小
    real_data = b''

    while recv_size < json_dict.get('file_size'):
        # 每次初始接收1024
        data = client.recv(1024)

        # 真实数据大小
        real_data += data

        # 真实接收值 不能确定最终数据就是剩余1024 所以每次接收是接收值长度
        recv_size += len(data)

        # 转码打印
    print(real_data.decode('gbk'))
客户端进阶版2
复制代码

PS:

服务端:

  (1)制作字典包头

  (2)发送字典包头

  (3)发送字典数据

  (4)发送真实数据

客户端:

  (1)接收字典包头

  (2)解封字典包头

  (3)接收字典数据

  (4)接收真实数据

小练习:自定义一个上传电影文件

例如:

复制代码
import socket, json, struct
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(6)

while True:
    # 循环接收客户端请求
    conn,addr = server.accept()
    # 循环与客户端建立请求
    while True:
        # 异常捕获
        try:
            # 接收字典包头
            header_dict = conn.recv(4)
            # 解封字典包头 获取内部字典信息
            dict_info = struct.unpack('i',header_dict)[0]
            # 获取字典数据
            dict_data = conn.recv(dict_info)
            # 进行反序列化 并进行转码
            json_dict = json.loads(dict_data.decode('utf-8'))



            # 初始接收值
            recv_size = 0

            # # 真实数据大小
            # real_data = b''
            with open(json_dict.get('movie_name'),'wb') as file:
                while recv_size < json_dict.get('movie_size'):
                    data = conn.recv(1024)
                    file.write(data)
                    # real_data += data
                    recv_size += len(data)
                print('接收成功!')
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()
服务端
复制代码
复制代码
import socket, os, json, struct

client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
    # 获取文件路径
    MOVIE_PATH = r'C:\Users\SR\Desktop\test'
    # 获取该路径下文件列表
    movie_list = os.listdir(MOVIE_PATH)
    # 将文件打印 供用户选择
    for index, movies in enumerate(movie_list, 1):
        print(index, movies)

    # 用户进行选择
    choice = input('请输入电影编号>>:').strip()
    # 判断是否输入数字
    if not choice.isdigit():
        print('请输入数字!')
        continue
    choice = int(choice)

    # 用户选择电影成功
    movie_name = movie_list[choice - 1]

    # 拼接绝对路径寻找电影
    movie_path = os.path.join(MOVIE_PATH, movie_name)
    # 获取文件大小
    movie_size = os.path.getsize(movie_path)
    # 定义字典
    movie_info = {
        'movie_name':movie_name,
        'movie_size':movie_size,
        'movie_info':'豆瓣评分满分'
    }
    # 将字典进行序列化成字符串 转换成二进制进行传输
    json_dict = json.dumps(movie_info).encode('utf-8')
    # 转换成二进制进行传输
    # json_byte = json_dict.encode('utf-8')
    # 定义字典包头
    header_dict = struct.pack('i',len(json_dict))
    # 发送字典包头
    client.send(header_dict)
    # 发送字典
    client.send(json_dict)
    # 循环发送真实数据
    with open(movie_path,'rb') as file:
        for line in file:
            client.send(line)
        print('上传成功!')
客户端
复制代码

 

四:UDP#

(1)UDP基础套接字:

  (1)UDP属于数据包协议(自带包头)

  (2)socket默认封装TCP协议 需要手工修改socket类型

例如:

复制代码
import socket

# 启用UDP协议
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
# UDP没有半连接池概念

data,addr = server.recvfrom(1024)
# 客户端发来的数据
print(data) # b'hello'
# 客户端连接的地址
print(addr)  # ('127.0.0.1', 57571)
# 发送给客户端的数据与地址
server.sendto(data.upper(),addr)
服务端
复制代码
复制代码
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)
# 发送给服务端数据以及地址
client.sendto(b'hello',server_address)
# 服务端发送数据 以及地址
data,addr = client.recvfrom(1024)
print('服务端发来的数据',data) # 服务端发来的数据 b'HELLO'
print('服务端发送的地址',addr) # 服务端发送的地址 ('127.0.0.1', 8080)
客户端
复制代码

 

(2)TCP与UDP的区别

  (1)UDP允许发送数据为空

例如:

复制代码
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
while True:
    data,addr = server.recvfrom(1024)
    server.sendto(data.upper(),addr)
    date = data.decode('utf-8')
    print(date)
服务端
复制代码
复制代码
import socket
clint = socket.socket(type=socket.SOCK_DGRAM)
ser_address = ('127.0.0.1',8080)
while True:
    cmd = input('请输入指令>>:').strip().encode('utf-8')
    clint.sendto(cmd,ser_address)
    clint.recvfrom(1024)
客户端
复制代码

  (2)UDP没有半连接池概念

  (3)UDP没有粘包问题

例如:

复制代码
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
data, addr = server.recvfrom(1024)
print(data)
data, addr1 = server.recvfrom(1024)
print(data)
data, addr2 = server.recvfrom(1024)
print(data)

'''
b'hello'
b'hello'
b'hello'
'''
服务端
复制代码
复制代码
import socket
clint = socket.socket(type=socket.SOCK_DGRAM)
ser_address = ('127.0.0.1',8080)
clint.sendto(b'hello',ser_address)
clint.sendto(b'hello',ser_address)
clint.sendto(b'hello',ser_address)
客户端
复制代码

  (4)UDP协议支持并发

 

(3)socketserver模块

  作用:其含有一些内置方法

    (1)TCP可以通过调用一些内置方法 可以让TCP可以像UDP似的 实现并发效果

例如:

复制代码
import socketserver

class MySocket(socketserver.BaseRequestHandler):
    def handle(self):
        print('接收客户端请求')

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MySocket)
    server.serve_forever()
服务端
复制代码
复制代码
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    cmd = input('请输入>>:').strip()
客户端
复制代码

PS:

  (1)其会时时监听某些地址 

  (2)当有地址发来请求的时候 其会调用handle函数处理

  

  (2)UDP:

    (1)了解即可:

复制代码
class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        # print('来啦 老弟')
        while True:
            data,sock = self.request
            print(self.client_address)  # 客户端地址
            print(data.decode('utf-8'))
            sock.sendto(data.upper(),self.client_address)


if __name__ == '__main__':
    """只要有客户端连接  会自动交给自定义类中的handle方法去处理"""
    server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer)  # 创建一个基于TCP的对象
    server.serve_forever()  # 启动该服务对象
服务端
复制代码
复制代码
import socket
import time

client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)

while True:
    client.sendto(b'hello',server_address)
    data,addr = client.recvfrom(1024)
    print(data.decode('utf-8'),addr)
    time.sleep(1)
客户端
复制代码

PS:UDP请求连接速度特别快 需要通过time.sleep降低请求速度

 

posted @   SR丶  阅读(292)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示
CONTENTS