基于TCP协议的套接字编程(socket编程)、基于UDP协议的套接字编程、粘包现象、struct模块

发布时间 2023-07-05 15:02:08作者: Maverick-Lucky

基于TCP协议的套接字编程(socket编程)

Socket:

  套接字,它是应用层和传输层之间的一个抽象层,把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

                                                       

 

套接字的分类:

  AF_UNIX:用在局域网中

  AF_INET:用在互联网中

客户端和服务端的启动:

  先启动服务端,服务端启动起来之后,等待客服端的连接,然后接收客服端发送的消息,进行通信。

套接字的工作流程:

TCP服务端先启动 ----> 实例化得到socket对象 ---->通过bind绑定IP信息 ----> 通过listen监听,等待客户端发送信息 ----> accept :接收,接受客户端发送的信息 ----> 阻塞直到有客服端连接 ----> receive,接受客户端发来的数据 ----> send :回应客户端,给客户端发送信息 ----> close:断开和客户端的连接 ----> close:关掉服务端

TCP客户端启动 ----> 实例化得到socket对象 ----> connect:建立与服务端的连接 ----> send:给服务端发送信息 ----> receive:接受服务端发送的信息 ----> close :关闭与服务端的连接

如图:

                                                           

简易版套接字编程

服务端:

import socket
# 实例化得到socket对象
server = socket.socket()
# server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化得到socket对象
# server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 实例化得到socket对象

# bind:绑定IP信息
server.bind(('127.0.0.1',8000)) # ip地址 + 端口(port)
# listen:监听,等待客户端发送信息
server.listen(3) # 3代表的是半连接池,代表可以等待连接的客户端的数量
# accept:等待客户端发来的信息
"""
sock:代表的是当前客户端的连接对象
addr:代表的是客户端的信息(ip+port)
"""
sock,addr = server.accept() # 代码走到这会停住,直到客户端发来信息。
# receive:真正的取到客户端发来的消息
data = sock.recv(1024) # 表示接收的最大数据字节,数据类型必须是字节类型
print('客户端发来的消息:%s' % data)

# send: 服务端给客户端发送消息
sock.send(b'hi') # 数据类型必须是字节类型
# close:断开与客户端之间的连接
sock.close()
# close:关掉服务端
server.close()

客户端:

import socket
"""
# SOCK_STREAM:使用的是TCP协议
# SOCK_DGRAM:使用的是UDP协议
"""
# 实例化socket,得到了socket对象
client = socket.socket()
# client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化得到socket对象
# client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 实例化得到socket对象

# connect:连接服务端
client.connect(('127.0.0.1',8000))

# send:发送信息给服务端
client.send(b'hello')

# recevie:接收服务端发来的消息
data = client.recv(1024) # 一次接收最大的数据量,字节类型
print('服务器发来的消息: %s' % data)
# close:断开与服务端的连接
client.close()

加上通信循环

每当客户端发送一次消息,服务端就会接收一次,当客户端发送完毕,会断开连接。而服务端不会,会继续等待下一次客户端发送消息。

服务端:

import socket


"""
# SOCK_STREAM:使用的是TCP协议
# SOCK_DGRAM:使用的是UDP协议
"""
# AF_INET:指定这个就完事了
# 买了个手机
server = socket.socket()  # 实例化得到socket对象
# server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化得到socket对象
# server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 实例化得到socket对象

###2. 服务端绑定信息
# 给手机买了个手机卡
server.bind(("127.0.0.1", 8002))

###3.监听消息
server.listen(3)  # 3代表的是半连接池:可以等待的客户端的数量
###4. 接收消息


while True:
    sock, addr = server.accept()  # 代码走到这里会停住,等待接收客户端发来的消息

    """
    sock:代表的是当前客户端的连接对象
    addr:代表的是客户端的信息(ip+port)
    """

    ###5.真正的取到客户端发来的消息
    data = sock.recv(1024) # 接收的最大数据,字节类型

    print("客户端发来的消息:%s" % data)
    ###6. 服务端个客户端发消息
    sock.send(data.upper())  # 数据类型必须是字节类型

    ###7. 断开与客户端之间的连接
    sock.close()

###8. 关机
server.close()

客户端:

import socket
"""
# SOCK_STREAM:使用的是TCP协议
# SOCK_DGRAM:使用的是UDP协议
"""
# 实例化socket,得到了socket对象
client = socket.socket()
# client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化得到socket对象
# client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 实例化得到socket对象

# connect:连接服务端
client.connect(('127.0.0.1',8001))

# send:发送信息给服务端
client.send(b'hello')

# recevie:接收服务端发来的消息
data = client.recv(1024) # 一次接收最大的数据量,字节类型
print('服务器发来的消息: %s' % data)
# close:断开与服务端的连接
client.close()

加上连接循环

服务端

import socket
# 实例化得到socket对象
server = socket.socket()
# server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化得到socket对象
# server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 实例化得到socket对象

# bind:绑定IP信息
server.bind(('127.0.0.1',8001)) # ip地址 + 端口(port)
# listen:监听,等待客户端发送信息
server.listen(3) # 3代表的是半连接池,代表可以等待连接的客户端的数量
# accept:等待客户端发来的信息
"""
sock:代表的是当前客户端的连接对象
addr:代表的是客户端的信息(ip+port)
"""
while True:
    print('等待接收信息')
    sock,addr = server.accept() # 代码走到这会停住,直到客户端发来信息。
    while True:
       # 防止客户端终止连接或者或者再次建立新连接时,导致服务端报错
        try:
            # receive:真正的取到客户端发来的消息
            data = sock.recv(1024) # 表示接收的最大数据字节,数据类型必须是字节类型
            if len(data) == 0:
                break
            print('客户端发来的消息:%s' % data)
            # send: 服务端给客户端发送消息
            sock.send(data.upper()) # 数据类型必须是字节类型
        except ConnectionResetError as e:
            print(e)
            break

    # close:断开与客户端之间的连接
    sock.close()
# close:关掉服务端
server.close()

 

 

客户端:

import socket
"""
# SOCK_STREAM:使用的是TCP协议
# SOCK_DGRAM:使用的是UDP协议
"""
# 实例化socket,得到了socket对象
client = socket.socket()
# client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化得到socket对象
# client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 实例化得到socket对象

# connect:连接服务端
client.connect(('127.0.0.1',8001))
while True:
    data = input('请输入发送给服务端信息:')
    # send:发送信息给服务端
    client.send(data.encode('utf8'))
    # recevie:接收服务端发来的消息
    data = client.recv(1024) # 一次接收最大的数据量,字节类型
print('服务器发来的消息: %s' % data)
# close:断开与服务端的连接
client.close()

基于UDP协议的套接字编程

服务端:

import socket
# 使用UDP协议
server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
while True:
    print('接收消息')
    # 接收客户端发来的消息,设定接收的最大字节数为1024
    data,client_addr = server.recvfrom(1024)
    print('>>>>>:',data ,client_addr)
    # 给客户端发消息,将从客户端那接收过来的消息和地址号发送过去
    server.sendto(data.upper(),client_addr)
# 关闭客户端
server.close()

客户端:

import socket
# 使用UDP协议
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
    msg = input('输入给服务端发送的消息:').strip()
    client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
    # 接收来自服务端发来的消息和地址号
    data,addr = client.recvfrom(1024)
    print(data,addr)
client.close()

粘包现象

粘包现象:

  就是管道里的数据没有完全被取出来

粘包问题出现的原因:

  1. TCP协议是流式协议,数据像水流一样粘一起,没有任何边界区分

  2. 当客户端发送的数据量很小,并且时间间隔很小的时候,会把数据打包成一个包一次性发送。

解决粘包问题的思路:

  1. 拿到数据的总大小

  2. recv_size = 0 ,循环接收,每接收一次,recv_size += 接收的长度

  3.直到 recv_size = 数据的总大小,结束循环

思路:

  报头:不给所给的数据是多少,都可以打包成一个固定长度的数据

  客户端:client.send(4)

      client.send(真实数据)

  服务端:sock.recv(4) 

 

 

 

 

struct 模块

struct模块:用于处理二进制数据和python数据类型之间的互相转换

解决粘包问题的核心就是:

  为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据。

struct模块提供了一组函数,可用于不同数据类型之间的打包(pack)和解包(unpack)操作,使得处理二进制数据更加方便和灵活。

简单的使用: