74 socket 编写程序

857次阅读
没有评论

共计 12837 个字符,预计需要花费 33 分钟才能阅读完成。

引入

首先在前面储备知识中了解了 OSI 七层模型的工作原理以及 TCPUDP的区别之后, 下面的内容就非常容易理解了

74 socket 编写程序


一. 客户端 / 浏览器与服务端架构

在了解 socket 之前我们先要知道什么是 C/S, 什么是 B/C? 我们知道软件之间的通信是基于计算机的三层结构 (应用程序、操作系统、计算机硬件) 来进行的, 而 C/SB/S 是互联网软件通信的两种模式

  • C/S 指的是客户端软件(client)--- 服务端软件(server) : 我们学习 socket 就是为了完成 C/S 架构的开发

  • B/S 指的是浏览器(Browser)------ 服务端软件(server) : 也是 C/S 架构的一种


二. 什么是 socket

Socket 也叫 "套接字"

想要完成一个基于网络通信的 C/S 架构, 那么你必须要知道一堆的网络协议, 因为这些协议是网络通信的核心, 而 socket 诞生之后你就不必为每个协议的细节而苦恼了, 它帮你完成了一些底层该做的事情, 看下图:

74 socket 编写程序

如图中所看到的:

Socket 是应用程与传输层及其以下层之间的抽象层, 它相当于一个门面, 将传输层及其以下部分复杂的 TCP/IP 协议族封装成简单的接口, 提供给应用层调用, 我们只需要遵循 socket 的规则去编写程序就自然遵循了网络通信协议


三. 套接字的发展史以及分类

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix, 即人们所说的 BSD Unix。因此, 有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始, 套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯, 或 IPC。套接字有两种(或者称为有两个种族), 分别是基于文件型的和基于网络型的

1. 基于文件类型的套接字家族

  • 套接字家族名称 : AF_UNIX

unix 一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

2. 基于网络类型的套接字家族

  • 套接字家族名称 : AF_INET

AF_INET6 被用于 ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET 是使用最广泛的一个,python 支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用 AF_INET

3. 传输协议类型

  • 流式套接字 : SOCK_STREAM

流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即 TCP 协议

  • 数据报套接字 : SOCK_DGRAM

数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性, 数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用 UDP 协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理

  • 原始套接字 : SOCK_RAW

原始套接字与标准套接字 (标准套接字指的是前面介绍的流套接字和数据报套接字) 的区别在于:原始套接字可以读写内核没有处理的 IP 数据包,而流套接字只能读取 TCP 协议的数据,数据报套接字只能读取 UDP 协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接

4. 早期套接字的应用

早期套接字并不是用来网络通信的, 一般是用来处理一台计算机之上两个进程之间的相互通信

我们知道计算机开启两个进程, 申请的是内存的空间, 两个进程的内存空间是相互隔离的, 并且是物理隔离, 为的是保证数据的安全性, 那么进程与进程之间的数据是的不到交互的

但是硬盘是所有程序公用的, 于是就可以把一个进程的数据先写到硬盘中去, 让另一个进程从硬盘中取, 这样就实现了进程与进程之间的数据交互

四. 套接字的工作流程

客户端与服务端之间的连接过程主要可以分为四个步骤

1. 服務器绑定 IP + Port 并建立监听

客户端初始化 Socket 动态库后创建套接字,然后指定客户端 Socket 的地址,循环绑定 Socket 直至成功,然后开始建立监听,此时客户端处于等待状态,实时监控网络状态

2. 客户端向服务端发送请求

客户端的 Socket 向服务器端提出连接请求,此时客户端描述出它所要连接的 Socket,指出要连接的 Socket 的相关属性,然后向服务器端 Socket 提出请求

3. 连接请求确认并建立

当服务器端套接字监听到来自客户端的连接请求之后,立即响应请求并建立一个新进程,然后将服务器端的套接字的描述反馈给客户端,由客户端确认之后连接就建立成功

4. 连接建立成功之后进行数据收发

然后客户端和服务器两端之间可以相互通信,传输数据,此时服务器端的套接字继续等待监听来自其他客户端的请求(下面会一步步实现)

74 socket 编写程序

五.Socket 模块的常用函数(方法)

1.Socket 的实例化 (得到一个套接字对象)

  • 格式 : socket(family, type, protocal=0)

  • 三个参数 : 1. 常用协议族, 2.socket 类型, 3. 指定的协议(默认 0)

  • AF_INET(默认值)、AF_INET6、AF_UNIX、AF_ROUTE 等

  • SOCK_STREAM(TCP 类型)(默认值)、SOCK_DGRAM(UDP 类型)、SOCK_RAW(原始类型)
  • 通常不写, 默认为 "0", 使用默认的协议和类型:

python
s=socket.socket() # 等同于下面
socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 等同于上面
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 如果想用 UDP 就得这样写

2. 服务端套接字函数

函数 说明
s.bind() 绑定地址(host,port)到套接字,在 AF_INET 下, 以元组(host,port)的形式表示地址
s.listen() 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量, 该值至少为 1,一般为 5 就够用了
s.accept() 被动接受 TCP 客户端连接,(阻塞式)等待连接的到来

3. 客户端套接字函数

函数 说明
s.connect() 主动初始化 TCP 服务器连接,。一般 address 的格式为元组(hostname,port),如果连接出错,返回 socket.error 错误
s.connect_ex() connect()函数的扩展版本, 出错时返回出错码, 而不是抛出异常

4. 公共用途的套接字函数

函数 说明
s.recv() 接收 TCP 数据,数据以字符串形式返回,缓冲区 (bufsize) 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略
s.send() 发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小(提示: 在输入空的时候小于)
s.sendall() 完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常
s.recvfrom() 接收 UDP 数据,与 recv()类似,但返回值是(data_bytes,address)。其中 data_bytes 是接收的 bytes 类型的数据,address 是发送数据的地址, 以元组 ('IP', port) 形式表示
s.sendto() 发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值(重用 IP 和端口)
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值

5. 面向锁的套接字方法

函数 说明
s.setblocking(flag) 如果 flag 为 0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值), 非阻塞模式下,如果调用 recv()没有发现任何数据,或 send()调用无法立即发送数据,那么将引起 socket.error 异常
s.settimeout(timeout) 设置套接字操作的超时期,timeout 是一个浮点数,单位是秒。值为 None 表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 connect())
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回 None

6. 面向文件的套接字函数

函数 说明
s.fileno() 返回套接字的文件描述符
s.makefile() 创建一个与该套接字相关连的文件

六. 基于 TCP 实现的套接字

TCP 基于连接通信, 所以必须先启动服务端, 在启动客户端

1. 模拟打电话简单通信连接

  • 服務端
import socket

🍓买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 第一个参数,是基于网络套接字,第二个是种类:tcp 称为流式协议,udp 称为数据报协议(SOCK_DGRAM)🍓插卡 / 绑卡(绑定地址和端口号)phone.bind(('127.0.0.1', 8060))
# 一个元组,这里绑定的是一个本机回环地址,只能本机和本机通信,一般用于测试阶段


🍓开机(处于监听状态)phone.listen(5)  
# 5 是设置的半连接池,限制的是请求数,只能同时与一台客户机通信,其它的挂起

🍓等待电话连接(等待客户机的请求连接)print('等待连接...')
conn, client_addr = phone.accept()  
#(conn: 三次握手建立的链接,client_addr: 客户端的 IP 和端口)

print(conn)
print(client_addr)

🍓开始通信:收 / 发消息
data = conn.recv(1024)    # 最大接受的字节数 1024
print('来自客户端的数据:', data)
conn.send(data.upper())   # 返回数据

🍓挂掉电话连接(关闭连接)conn.close()

🍓关机(关闭服务器)phone.close()
  • 客户端
import socket

🍓买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(phone)

🍓拨电话(客户端地址会变,不需要绑定地址)phone.connect(('127.0.0.1', 8060))
# 指定服务器 IP 和端口, 一个元组,这里是客户机与服务端建立连接

🍓开始通信:发 / 收消息, 通信循环
while True: 
    msg = input('请输入消息 >>').strip()
    if len(msg) == 0:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)  # 最大接受字节数 1024
    print(data.decode("utf-8"))

🍓关闭客户端
phone.close()
  • 问题 : 重启服务端报错 Address already in uer (端口被占用) 解决方法
🍓在 bind( ) 函数之前加上一条 'socket' 配置
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 表示重用 ip 和 端口

🍓或者更改服务端端口号

2.tcp 实现一个简单聊天程序

一来一回, 并且一个连接断开才可以连接下一个

  • 服务端
import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.bind(('127.0.0.1', 8060))
phone.listen(5)

while True:
    print('start recv...')
    conn, client_addr = phone.accept()
    print(f" 鏈接了一个客户端{client_addr}")
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0: break
            print(f'来自客户的数据:\033[1;30;46m{data.decode("utf-8")}\033[0m')
            user = input(" 请输入发送内容(q 退出)>>")
            if user.lower() == "q":break
            conn.send(user.encode("utf-8"))
        except ConnectionResetError:
            break

    conn.close()
  • 客户端
import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8060))

while True:
    msg = input('输入发送内容(q 退出)>>').strip()
    if msg.lower() == "q":break
    if len(msg) == 0:continue
    phone.send(msg.encode('utf-8'))
    data = phone.recv(1024)
    print(f" 来自派大星的消息 : \033[1;30;46m{data.decode('utf-8')}\033[0m")

phone.close()

3.tcp 模拟 xshell 实现远程执行命令

有一些功能无法实现 : cd 等

  • 服务端
from socket import *
import subprocess

server = socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",8090))
server.listen(5)

while 1:
    print(" 等待连接...")
    conn,addr = server.accept()
    print(f" 来自 {addr} 的连接 ")
    while 1:
        try:
            cmd = conn.recv(1024)
            # 运行系统命令
            obj = subprocess.Popen(cmd.decode("utf-8"),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            date = stdout + stderr
            conn.send(date)  # 返回命令执行的结果
        except Exception:
            break
    conn.close()
  • 客户端
from socket import *

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

while True:
    cmd = input('>>').strip()
    if len(cmd) == 0: continue
    client.send(cmd.encode("utf-8"))
    data = client.recv(1024)
    print(data.decode('gbk'))

当接收的数据超过 1024 字节, 我们只能接收 1024 字节, 可以在客户端调高最大接收量, 但这并不是我们的解决方案, 于是我们就可以对接收的字节进行循环接收, 直到接收完为止

  • 客户端循环接收数据
from socket import *

client = socket(AF_INET,SOCK_STREAM)
client.connect(("127.0.0.1",8090))

while True:
    cmd = input("cmd >>").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode("utf-8"))
    while 1:
        date = client.recv(1024)
        print(date.decode("utf-8"),end="")
        if len(date) < 1024: break  # 当接收的字节小于 1024, 则代表这次能收干净

九. 基于 UDP 实现的套接字

udp 是无连接的, 所以县启动哪一段都没有关系

1.udp 实现与多个用户通信

可回多个客户端的信息, 回完一个紧接着可回下一个, 不需要连接

  • 服務端
server  = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",8087))
while 1:
    msg,addr = server.recvfrom(1024)
    print(f'来自 {addr[0]} 的消息:{msg.decode("utf-8")}')
    inp = input(" 请输入发送内容 >>").strip()
    server.sendto(inp.encode("utf-8"),addr)
  • 客户端 1
from socket  import *

client = socket(AF_INET,SOCK_DGRAM)
while 1:
    msg = input(" 请输入发送内容 >>").strip()
    if len(msg) == 0:continue
    client.sendto(msg.encode("utf-8"),("127.0.0.1",8087))
    msg,addr = client.recvfrom(1024)
    print(f'对方的消息 : {msg.decode("utf-8")}')
  • 客户端二
from socket  import *

client = socket(AF_INET,SOCK_DGRAM)
while 1:
    msg = input(" 请输入发送内容 >>").strip()
    if len(msg) == 0:continue
    client.sendto(msg.encode("utf-8"),("127.0.0.1",8087))
    msg,addr = client.recvfrom(1024)
    print(f'对方的消息 : {msg.decode("utf-8")}')

2.udp 实现时间格式化服务器

  • 服务端
from socket import *
from time import strftime

server = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",8089))

print("start recv....")
while True:
    msg,addr = server.recvfrom(1024)
    print(f"[{addr[0]}]连接成功 ")
    if not msg:
        fmt = "%Y-%m-%d %X"
    else:
        fmt = msg.decode("utf-8")
    time_fmt = strftime(fmt)
    server.sendto(time_fmt.encode("utf-8"),addr)
  • 客户端
from socket import *

client = socket(AF_INET,SOCK_DGRAM)

print(" 输入时间格式, 返回格式化后的时间 ")
while True:
    inp = input(" 请输入时间格式(例:%Y %m %d)>>").strip()
    client.sendto(inp.encode("utf-8"),("127.0.0.1",8089))
    date = client.recv(1024)
    print(date.decode("utf-8"))

十. 字符编码问题

使用 subprocess 模块执行系统命令得到的结果是字节, 并且是是以当前平台的编码为准, 值得注意的是 : 在 Windows 平台上读出来的内容是 GBK 编码的, 在接收时就需要使用 GBK 解码, 且从管道里只能读一次结果

十一. 黏包现象

ps : 只有 TCP 才会出现黏包现象, UDP 不会出现黏包现象

0.socket 收发消息原理

其实我们发送数据并不是直接发送给对方, 而是应用程序将数据发送到本机操作系统的缓存里边, 当数据量小, 发送的时间间隔短, 操作系统就会在缓存区先攒够一个 TCP 段再通过网卡一起发送, 接收数据也是一样的, 先在操作系统的缓存存着, 然后应用程序再从操作系统中取出数据

74 socket 编写程序

1. 为什么产生黏包

  • 主要原因 : TCP称为流失协议, 数据流会杂糅在一起, 接收端不清楚每个消息的界限, 不知道每次应该去多少字节的数据
  • 次要原因 : TCP为了提高传输效率会有一个 nagle 优化算法, 当多次 send 的数据字节都非常少, nagle算法就会将这些数据合并成一个 TCP 段再发送, 这就无形中产生了黏包

2. 产生黏包的两种情况 :

  • 发送端需要等待缓冲区满了才将数据发送出去, 如果发送数据的时间间隔很短, 数据很小, 就会合到一起, 产生黏包
  • 接收方没有及时接收缓冲区的包, 造成多个包一起接收, 如果服务端一次只接收了一小部分, 当服务端下次想接收新的数据的时候, 拿到的还是上次缓冲区里剩余的内容

3. 通过 send 数据长度的方式来控制接收 (解决黏包问题的方式)

  • 服务端
from socket import *
import subprocess

server = socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",8090))
server.listen(5)

while 1:
    print(" 等待连接...")
    conn,addr = server.accept()
    print(f" 来自 {addr} 的连接 ")
    while 1:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode("utf-8"),
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            date = stdout + stderr
            date_len = str(len(date)).encode("utf-8")  # 得到数据长度
            conn.send(date_len)  # 将数据的长度先发送
            conn.send(date)      # 再发送真实数据
        except Exception:
            break
    conn.close()
  • 客户端
from socket import *

client = socket(AF_INET,SOCK_STREAM)
# client.connect(("192.168.12.222",8090))
client.connect(("127.0.0.1",8090))

while True:
    cmd = input("cmd >>").strip()
    if len(cmd) == 0:continue
    client.send(cmd.encode("utf-8"))
    date_len = int(client.recv(1024).decode("utf-8"))  # 先接收数据长度
    recv_len = 0
    while 1:
        date = client.recv(1024)
        recv_len += len(date)
        print(date.decode("gbk"),end="")
        if recv_len == date_len: break  # 当数据长度与接收到的数据长度相等则结束

效率低 : 程序运行的速度远快于网络传输的速度, 如果在发送真实数据之前先send 该数据的字节流长度, 那么就会放大网络延迟带来的性能损耗

4. 使用 struct 模块实现精准数据字节接收 (比较高效解决 tcp 协议的黏包方法)

高效在哪? 为字节流加上一个自定义固定长度的报头, 报头中就包含了该字节流长度, 然后只需要将整个数据加报头 send 一次到对端, 对端先取出固定长度的报头, 然后在取真实的数据

struct 模块在前面章节我对它的使用做了简单介绍👉🏻struct 传送门

  • 服务端
from socket import *
import subprocess
import struct
import json

server = socket(AF_INET, SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    print('connection....')
    conn, client_addr = server.accept()
    print('已经连接:', client_addr)

    while True:
        try:
            cmd = conn.recv(1024)
            if len(cmd) == 0: break
            obj = subprocess.Popen(cmd.decode('utf-8'),
                shell=True,
                stderr=subprocess.PIPE,
                stdout=subprocess.PIPE)

            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            # 制作一个报头字典(模板)header_dic = {
                'header_name': 'shawn',
                'total_size': len(stderr) + len(stdout),
                'heash': 'songhaixing',
            }
            # 将报头 (字典) 序列化成字符串类型
            header_json = json.dumps(header_dic)
            # 将报头 (字符串) 转换成 byte 类型
            header_byte = header_json.encode('utf-8')
            # 将报头信息打包成 4 字节大小, 里面包含的报头的长度
            header_size = struct.pack('i', len(header_byte))

            # 先发送打包的 4bytes,4bytes 包含了报头长度
            conn.send(header_size)
            # 再发送报头(客户端已经拿到了(解包出)报头的长度)conn.send(header_byte)
            # 发送真实数据
            data = stdout + stderr
            conn.send(data)

            # 可以合并到一起只 send 一次, 但字符拼接又降低了效率(不推荐)
            # msg = header_size + header_byte + stdou t+ stderr  
            # conn.send(msg)
        except Exception:
            break
    conn.close()

🔰疑问 : 前面说到多次 send 会扩大网络延迟带来的效率问题, 那为什么还要分四次 send ?

​ 其实在前面 socket 收发消息的原理图哪里就给出了答案, 数据先是发送到自己操作系统的缓存内, 时间间隔短, 数据量小的会被合在一起在发送, 这就是 TCP 协议 nagle 优化算法做的事(有提升效率的功能, 当然也带来了黏包问题)

  • 客户端
from socket import *
import json
import struct

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

while True:
    cmd = input('请输入命令 >>').strip()
    if len(cmd) == 0: continue
    client.send(cmd.encode('utf-8'))

    # 接收 byte_4
    byte_4 = client.recv(4)
    # 解包出报头长度
    header_len = struct.unpack('i', byte_4)[0]
    # 使用长度接收 byte
    header_byte = client.recv(header_len)
    # byte---->str
    header_str = header_byte.decode('utf-8')
    # str---->dic
    header_dic = json.loads(header_str)
    # 拿到真正数据的大小
    total_size = header_dic['total_size']

    recv_size = 0
    while 1:
        data = client.recv(1024)
        recv_size += len(data)
        print(data.decode('gbk'),end="")
        if recv_size == total_size:break

5.udp 没有黏包问题

udp 被称为数据报协议, 每次发送的数据都是一个数据报, 一次 sendto 对应一次 recvfrom, 不会产生黏包

udp 又被称为不可靠协议, 不可靠在哪里? 比如发送方发送了 10bytes 的数据, 而接收方只接收 8bytes 的数据, 那么剩下的两个 bytes 将会被丢弃, 并且在不同的平台有不同的表现, 下面我们来进行试验 :

  • Windows 平台下试验客户端
from socket import *

client = socket(AF_INET,SOCK_DGRAM)

msg = "1234567890".encode("utf-8")  # "utf-8" 编码格式的数字和字母都是 1bytes
msg2 = "12345".encode("utf-8")      # 发送 5bytes
msg3 = "123".encode("utf-8")        # 发送 3bytes

client.sendto(msg,("127.0.0.1",8989))
client.sendto(msg2,("127.0.0.1",8989))
client.sendto(msg3,("127.0.0.1",8989))
  • 服务端
from socket import *

server = socket(AF_INET,SOCK_DGRAM)
server.bind(("127.0.0.1",8989))

date,addr = server.recvfrom(8)    # 发送方发送 10bytes 只接收 8bytes
date2,addr2 = server.recvfrom(3)  # 发送方发送 5bytes 只接收 3bytes
date3,addr3 = server.recvfrom(1)  # 发送方发送 3bytes 只接收 1bytes

print(date.decode("utf-8"))
print(date2.decode("utf-8"))
print(date3.decode("utf-8"))
  • 运行结果

74 socket 编写程序

  • Linux 平台运行下试验客户端
from socket import *

client = socket(AF_INET,SOCK_DGRAM)

msg = "1234567891".encode("utf-8")
msg2 = "12345".encode("utf-8")
msg3 = "123".encode("utf-8")

client.sendto(msg,("192.168.12.222",8989))
client.sendto(msg2,("192.168.12.222",8989))
client.sendto(msg3,("192.168.12.222",8989))
  • 服务端
from socket import *

server = socket(AF_INET,SOCK_DGRAM)
server.bind(("192.168.12.222",8989))

date,addr = server.recvfrom(8)
date2,addr2 = server.recvfrom(3)
date3,addr3 = server.recvfrom(1)

print(date.decode("utf-8"))  # 注意: 如果发送方发送的是汉字, 一个汉字三个字节, 如果接受不完整解码出来会报错
print(date2.decode("utf-8"))
print(date3.decode("utf-8"))
  • 运行结果

74 socket 编写程序

发现只接收了最大接收量, 剩余的字节被丢弃了

十二.socketserver 实现并发

下一篇介绍如何使用 socketserver 模块实现并发效果

正文完
 
shawn
版权声明:本站原创文章,由 shawn 2023-06-16发表,共计12837字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)