当前位置 : 主页 > 编程语言 > 其它开发 >

socket,UDP协议

来源:互联网 收集:自由互联 发布时间:2022-05-30
目录 socket套接字 通信循环 链接循环 半连接池 粘包问题 UDP协议 内容socket套接字 需求:编写一个cs架构的程序 实现数据交互 操作OSI七层是所有cs架构的程序都需要经历的过程 所以有固定
目录
  • socket套接字
  • 通信循环
  • 链接循环
  • 半连接池
  • 粘包问题
  • UDP协议
内容 socket套接字

需求:编写一个cs架构的程序 实现数据交互

操作OSI七层是所有cs架构的程序都需要经历的过程 所以有固定的模块

socket套接字是一门技术;socket模块>>>:提供了快捷方式 不需要自己处理每一层

以后我们写软件连socket的影子都看不到,因为被封装起来

socket是最底层的原理,很多框架都封装了,其实我们不需要深入研究

socket模块

cs架构的软件无论是在编写还是运行,都应该先考虑服务端 

服务端:

import socket
server = socket.socket() # 买手机
"""
通过查看源码,括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.bind(('192.168.199.229',8888)) # 查电话卡
server.listen(5) # 开机
# 半连接池(先写,后面讲)
sock,addr = server.accept() # 等待接听电话,没人来就原地等待(程序阻塞)
print(addr) # 客户端的地址
data = sock.recv(1024) # 接受1024个字节,听别人说话
print(data.decode('utf8')) # 二进制格式需要解码
sock.send('hello'.encode('utf8'))
"""基于网络传输必须是bytes类型,需要编码,后面加encode"""
sock.close() # 挂电话
server.close() # 关机
View Code

客户端:

import socket

client = socket.socket() # 买手机 产生一个socket对象
client.connect(('192.168.199.229',8888)) # 直接拨号 根据服务端的地址链接
# 数据交互
client.send(b'hello') # 给服务端发送消息
data = client.recv(1024) # 接受服务端回复的消息
print(data.decode('utf8')) # 解码是因为是二进制格式

client.close() # 关闭客户端
View Code

服务端与客户端首次交互:一边是recv那么另一边必须是send,两边不能相同,否则就'冷战'了!!!

通信循环

基于上面的代码发现的问题:

只能发送一次消息就交互结束了

1.先解决消息固定的问题:input

服务端:

import socket
server = socket.socket() # 买手机
"""
通过查看源码,括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.bind(('192.168.199.229',8888)) # 查电话卡
server.listen(5) # 开机
# 半连接池(先写,后面讲)
sock,addr = server.accept() # 等待接听电话,没人来就原地等待(程序阻塞)
print(addr) # 客户端的地址
data = sock.recv(1024) # 接受1024个字节,听别人说话
print(data.decode('utf8')) # 二进制格式需要解码
msg = input('请回复消息>>>:').strip()
sock.send(msg.encode('utf8'))
"""基于网络传输必须是bytes类型,需要编码,后面加encode"""
sock.close() # 挂电话
server.close() # 关机
View Code

客户端:

import socket

client = socket.socket() # 买手机 产生一个socket对象
client.connect(('192.168.199.229',8888)) # 直接拨号 根据服务端的地址链接
# 数据交互
msg = input('请输入消息>>>:').strip()
client.send(msg.encode('utf8')) # 给服务端发送消息
data = client.recv(1024) # 接受服务端回复的消息
print(data.decode('utf8')) # 解码是因为是二进制格式

client.close() # 关闭客户端
View Code

2.再解决通信循环的问题:循环

服务端:

import socket
server = socket.socket() # 买手机
"""
通过查看源码,括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.bind(('192.168.199.229',8888)) # 查电话卡
server.listen(5) # 开机
# 半连接池(先写,后面讲)
sock,addr = server.accept() # 等待接听电话,没人来就原地等待(程序阻塞)
print(addr) # 客户端的地址

while True:
    data = sock.recv(1024) # 接受1024个字节,听别人说话
    print(data.decode('utf8')) # 二进制格式需要解码
    msg = input('请回复消息>>>:').strip()
    sock.send(msg.encode('utf8'))
    """基于网络传输必须是bytes类型,需要编码,后面加encode"""
sock.close() # 挂电话
server.close() # 关机
View Code

客户端:

import socket

client = socket.socket() # 买手机 产生一个socket对象
client.connect(('192.168.199.229',8888)) # 直接拨号 根据服务端的地址链接
# 数据交互
while True:

    msg = input('请输入消息>>>:').strip()
    client.send(msg.encode('utf8')) # 给服务端发送消息
    data = client.recv(1024) # 接受服务端回复的消息
    print(data.decode('utf8')) # 解码是因为是二进制格式

client.close() # 关闭客户端
View Code 链接循环

基于上面的代码发现的问题:

1.发送消息为空会‘冷战’

解决:在客户端统计长度判断即可

客户端:

import socket

client = socket.socket() # 买手机 产生一个socket对象
client.connect(('192.168.199.229',8888)) # 直接拨号 根据服务端的地址链接
# 数据交互
while True:
    msg = input('请输入消息>>>:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf8')) # 给服务端发送消息
    data = client.recv(1024) # 接受服务端回复的消息
    print(data.decode('utf8')) # 解码是因为是二进制格式

client.close() # 关闭客户端
View Code 2.反复重启服务器可能会报错>>>: address in use

解决:在服务端加入两行代码

服务端:

import socket
server = socket.socket() # 买手机
from socket import SOL_SOCKET,SO_REUSEADDR
"""
通过查看源码,括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(('192.168.199.229',8888)) # 查电话卡
server.listen(5) # 开机
# 半连接池(先写,后面讲)
sock,addr = server.accept() # 等待接听电话,没人来就原地等待(程序阻塞)
print(addr) # 客户端的地址

while True:
    data = sock.recv(1024) # 接受1024个字节,听别人说话
    print(data.decode('utf8')) # 二进制格式需要解码
    msg = input('请回复消息>>>:').strip()
    sock.send(msg.encode('utf8'))
    """基于网络传输必须是bytes类型,需要编码,后面加encode"""
sock.close() # 挂电话
server.close() # 关机
View Code 3.链接循环

如果是windows,客户端异常退出之后服务端会直接报错

解决:异常处理

服务端:

import socket
server = socket.socket() # 买手机
from socket import SOL_SOCKET,SO_REUSEADDR
"""
通过查看源码,括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(('192.168.199.229',8888)) # 查电话卡
server.listen(5) # 开机
# 半连接池(先写,后面讲)
sock,addr = server.accept() # 等待接听电话,没人来就原地等待(程序阻塞)
print(addr) # 客户端的地址

while True:
    try:
        data = sock.recv(1024) # 接受1024个字节,听别人说话
        if len(data) == 0:
            break
        print(data.decode('utf8')) # 二进制格式需要解码
        msg = input('请回复消息>>>:').strip()
        if len(msg) == 0:
            msg = '太忙'
        sock.send(msg.encode('utf8'))
        """基于网络传输必须是bytes类型,需要编码,后面加encode"""
    except Exception:
        break
sock.close() # 挂电话
server.close() # 关机
View Code

如果是mac或者linux,服务端会接收到一个空消息

解决:len 判断

客户端如果异常断开 服务端代码应该重新回到accept等待

目前我们的服务端只能实现一次服务一个人,不能做到同时服务多个 学了并发才可以实现

半连接池

listen(5)

py文件默认同一时间只能运行一次 如果想单独分开运行多次:

Edit Configurations >>> Allow parallel run 允许并行

这个‘5’就是最大的等待人数

把一开始的停掉之后,后面等待的就会陆陆续续的可以进来

粘包问题

服务端:

import socket
server = socket.socket()
server.bind(('127.0.0.1',9999))
server.listen(5)

coon,addr = server.accept()
data = coon.recv(1024)
print(data)
data1 = coon.recv(1024)
print(data1)
data2 = coon.recv(1024)
print(data2)
View Code

客户端:

import socket

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

client.send(b'hello')
client.send(b'big')
client.send(b'pig')
View Code

这里服务端的打印结果为:

b'hellobigpig'
b''
b''
View Code

得出结论:TCP协议的特点

会将数据量比较小并且时间间隔比较短的数据整合到一起发送

并且还会受制于recv括号内的数字大小(核心问题!!!)

流式协议:跟水流一样不间断

问题产生的原因:因为recv括号内我们不知道即将要接收的数据到底多大

如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现黏包

解决粘包问题

方向:精准获取数据的大小 struct模块

先看代码演示:

import struct

data1 = 'hello world!'
print(len(data1))  # 12
res1 = struct.pack('i', len(data1))  # 第一个参数是格式 写i就可以了
print(len(res1))  # 4
ret1 = struct.unpack('i', res1)
print(ret1)  # (12,)

data2 = 'hello baby baby baby baby baby baby baby baby'
print(len(data2))  # 45
res2 = struct.pack('i', len(data2))
print(len(res2))  # 4
ret2 = struct.unpack('i', res2)
print(ret2)  # (45,)
View Code

pack可以将任意长度的数字打包成固定长度
unpack可以将固定长度的数字解包成打包之前数据真实的长度

举例子:

import struct

data_dict = {
    'name':'动漫.zip',
    'pwd':'2222',
    'size':'00000000000000'
}
print(len(data_dict)) # 3
res = struct.pack('i',len(data_dict))
print(len(res)) # 4
View Code 解决粘包问题的代码演示:

解决粘包问题的思路:

  1. 先将真实数据打包成固定长度的包
  2. 将固定长度的包先发给对方
  3. 对方接收到包之后再解包获取真实数据长度
  4. 接收真实数据长度

服务端:

import os
import struct
import socket
import json

server = socket.socket()
server.bind(('127.0.0.1',9999))
server.listen(5)

coon,addr = server.accept()
while True:
    data_dict = {
        'file_name':'aaa.txt',
        'file_desc':'封了一个月',
        'file_size':os.path.getsize(r'1.py')
    }
    # 先打包字典
    dict_json_str = json.dumps(data_dict)
    dict_bytes = dict_json_str.encode('utf8')
    dict_package_header = struct.pack('i',len(dict_bytes))
    # 发送报头
    coon.send(dict_package_header)
    # 发送字典数据
    coon.send(dict_bytes)
    # 发送真实数据
    with open(r'1.py','rb')as f:
        for line in f :
            coon.send(line)
View Code

客户端:

import socket
import struct
import json

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

# 先接收固定长度的字典的报头
dict_header_len = client.recv(4)
# 解析出字典的真是长度
dict_real_len = struct.unpack('i',dict_header_len)[0]
# 接受字典数据
dict_data_bytes = client.recv(dict_real_len)
dict_data = json.loads(dict_data_bytes)
print(dict_data)
# 循环接受文件数据 不要一次性接受
recv_size = 0
with open(dict_data.get('file_name'),'wb')as f:
    while recv_size < dict_data.get('file_size'):
        data = client.recv(1024)
        recv_size += len(data)
        f.write(data)
View Code

打印结果为:

{'file_name': 'aaa.txt', 'file_desc': '封了一个月', 'file_size': 715}
View Code

recv括号内的数字尽量不要写太大 1024 2048 4096足够了
字典数据很难突破上面的数值

所以针对大文件的接收应该采用循环的形式一次接受一点点

UDP协议

服务端:

import socket

server = socket.socket(type=socket.SOCK_DGRAM) # 手动自己指定UDP协议,默认是TCP协议
server.bind(('127.0.0.1',8888))
"""UDP协议不需要半连接池,不需要等待"""
msg,add = server.recvfrom(1024)
"""一般情况下UDP协议很少用来发大的数据"""
"""也不需要考虑黏包问题"""
print('msg>>>:',msg.decode('utf8')) # msg是内容,需要解码
print('add>>>:',add)
server.sendto(b'hello',add) # sendto 给谁发
View Code

客户端:

import socket

client = socket.socket(type = socket.SOCK_DGRAM)
# 直接发送即可
server_add = ('127.0.0.1',8888) # 查找通讯录跟谁进行交互
client.sendto(b'hello',server_add)
msg,add = client.recvfrom(1024)
print('msg>>>:',msg.decode('utf8')) # msg是内容,需要解码
print('add>>>:',add)
View Code

执行之后的结果:

服务端:

msg>>>: hello
add>>>: ('127.0.0.1', 64341)

客户端:

msg>>>: hello
add>>>: ('127.0.0.1', 8888)

基于UDP收发消息,消息都会带着给你发的人的地址

UDP协议跟TCP协议不同在于需要自己携带一个地址,一个是recvfrom,一个是recv.

并且没有所谓的粘包问题

服务端:

import socket

server = socket.socket(type=socket.SOCK_DGRAM) # 手动自己指定UDP协议,默认是TCP协议
server.bind(('127.0.0.1',8888))
"""UDP协议不需要半连接池,不需要等待"""
msg,add = server.recvfrom(1024)
msg1,add1 = server.recvfrom(1024)
msg2,add2 = server.recvfrom(1024)
"""一般情况下UDP协议很少用来发大的数据"""
"""也不需要考虑黏包问题"""
print(msg)
print(msg1)
print(msg2)
View Code

客户端:

import socket

client = socket.socket(type = socket.SOCK_DGRAM)
# 直接发送即可
server_add = ('127.0.0.1',8888) # 查找通讯录跟谁进行交互
client.sendto(b'hello',server_add)
client.sendto(b'hello1',server_add)
client.sendto(b'hello2',server_add)
View Code

服务端打印结果:发一次收一次

b'hello'
b'hello1'
b'hello2'

UDP协议一般不会用来做大数据的数据交互,QQ微信就是基于UDP协议

基于UDP协议实现简易版本的QQ

服务端:

import socket

server = socket.socket(type=socket.SOCK_DGRAM) # 手动自己指定UDP协议,默认是TCP协议
server.bind(('127.0.0.1',8888))
while True:
    msg,add = server.recvfrom(1024)
    print(add)
    print(msg.decode('utf8'))
    back_msg = input('回复消息>>>:').strip()
    server.sendto(back_msg.encode('utf8'),add)
View Code

客户端:

import socket

client = socket.socket(type = socket.SOCK_DGRAM)
# 直接发送即可
server_add = ('127.0.0.1',8888) # 查找通讯录跟谁进行交互
while True:
    msg = input('请输入聊天内容>>>:').strip()
    msg = '来自客户端1的消息:%s'%msg
    client.sendto(msg.encode('utf8'),server_add)
    msg,add = client.recvfrom(1024)
    print(msg.decode('utf8'),add)
View Code

也可以支持多个客户端

 

上一篇:python爬虫中Response [503]问题
下一篇:没有了
网友评论