ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

python学习-Day33

2022-04-16 01:04:29  阅读:237  来源: 互联网

标签:socket python recv Day33 server 学习 data 服务端 客户端


目录

image

今日内容详细

socket

socket套接字简介

编写一个cs架构的程序来实现数据交互时,需要编写代码操作OSI七层,但过程相当的复杂。由于操作OSI七层是所有cs架构的程序都需要经历的过程,因此有了固定的模块,socket模块。
# socket套接字是一门技术
# socket模块>>>:提供了快捷方式,不需要自己处理每一层

"""
socket是最底层的原理,很多框架都封装了
"""

Socket并不属于TCP/IP协议簇,它只是一个编程接口,即对TCP/IP的封装和应用,简单理解TCP/IP看看作一个函数,而Socket用来进行调用,Socket可在网络中对两个程序建立通信通道,Socket可分为两个基本模块,一个服务端一个客户端,链接后进行通信。

socket模块

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

服务端

import socket


server = socket.socket()  # 第一步,创建一个socket对象(server),准备好联入网络的协议
"""
通过查看源码得知 
括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.bind(('127.0.0.1', 8080))   # 第二步,选择地址与端口号(以元组的形式)
"""
服务端应该具备的特征
    固定的地址
    ...  
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
"""
server.listen(5)  # 第三步,设置半连接池,最低为0,成功链接入网络
"""
半连接池(暂且忽略 先直接写 后面讲)
"""
sock, addr = server.accept()  # 等待用户来发起会话,若无用户便会造成程序阻塞(接收客户端连接,获得sock连接 和 addr客户端地址。)
"""
listen和accept对应TCP三次握手服务端的两个状态
"""
print(addr)  
data = sock.recv(1024) # 服务端接受客户端信息 
print(data.decode('utf8'))   # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
sock.send('你好啊'.encode('utf8'))  # 服务端回复客户端的信息
"""
recv和send接收和发送的都是bytes类型的数据
"""
sock.close()  # 终止与当前客户端的会话
server.close()  # 终止所有链接,关闭服务端

客户端

import socket


client = socket.socket()  # 产生一个socket对象
client.connect(('127.0.0.1', 8080))  # 根据服务端的地址链接

client.send(b'hello sweet heart!!!')  # 给服务端发送消息
data = client.recv(1024)  # 接收服务端回复的消息
print(data.decode('utf8'))  # 解码后打印出服务端的信息

client.close()  # 关闭客户端


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

通信循环

1.先解决消息固定的问题
	利用input获取用户输入
2.再解决通信循环的问题
	将双方用于数据交互的代码循环起来

服务端

import socket
from socket import SOL_SOCKET,SO_REUSEADDR
server = socket.socket()  # 创建一个socket对象(server)
  
    # -------------------------------------
    #在重启服务器的时候可能会遇到的BUG(mac居多),windows频率较少
    # 加上他就可以防止重启报错了(注意位置)
    # -------------------------------------
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

server.bind(('192.168.1.8', 8080))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
server.listen(5)  # 设置半连接池,最低为0
sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。
while True:
	data = sock.recv(1024)  # 获取客户端发送的消息
	print(data.decode('utf8')) # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
	msg = input('要发送的消息: ').strip()
	sock.send(msg.encode('utf8'))  # 服务端发送消息给客户端

    # sock.close()  # 断开服务端到客户端的连接
    # server.close()  # 关闭服务端

客户端

import socket

client = socket.socket()  # 创建一个socket对象(client)
client.connect(('192.168.1.8', 8080))  # 根据ip和端口连接服务端(元组的形式),主动发起连接
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()  # 断开连接,关闭客户端

image

链接循环

"""
  如果是windows 客户端异常退出之后服务端会直接报错
  	处理方式
  		异常处理
  如果是mac或linux 服务端会接收到一个空消息
  	处理方式
  		len判断
"""
# linux、mac断开链接时不会报错,会一直返回空(b‘’)
# 客户端如果异常断开,服务端代码应该重新回到accept等待新的客人

# 目前我们的服务端只能实现一次服务一个人,不能做到同事服务多个 
import socket
from socket import SOL_SOCKET, SO_REUSEADDR

server = socket.socket()  # 创建一个socket对象(server)

  # -------------------------------------
  # 加上他就可以防止重启报错了(注意位置)
  # -------------------------------------
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

server.bind(('192.168.1.8', 8080))  # 给socket对(server)绑定ip和端口号(以元组的形式)
server.listen(5)  # 设置半连接池,最低为0
while True:
	sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。

	while True:
		try:
			data = sock.recv(1024)  # 获取客户端发送的消息
			print(data.decode('utf8'))  # 打印接收到的数据,由于传输的时候是二进制所以需要进行解码
			msg = input('要发送的消息: ').strip()
			sock.send(msg.encode('utf8'))  # 服务端发送消息给客户端
		except BaseException:
			break # 客户端如果异常断开 服务端代码应该重新回到accept等待新的客人

# sock.close()  # 断开服务端到客户端的连接
# server.close()  # 关闭服务端

半连接池

概念

当服务器在响应了客户端的第一次请求后会进入等待状态,会等客户端发送的ack信息,这时候这个连接就称之为半连接。

半连接池其实就是一个容器,系统会自动将半连接放入这个容器中,可以避免半连接过多而保证资源耗光。

产生半连接的两种情况

1.客户端无法返回ACK信息
2.服务器来不及处理客户端的连接请求


设置的最大等待人数  >>>:  节省资源 提高效率
server.listen(5)  # 指定5个等待席位

'''限制的是同一时刻的请求数,而非连接数'''

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

image

黏包问题

多次发送被并为一次

data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)
data3 = conn.recv(1024)
print(data3)

client.send(b'hello')
client.send(b'jason')
client.send(b'kevin')

"""
三次打印的结果
  b'hellojasonkevin'
  b''
  b''
"""

黏包现象只发生在tcp协议中

TCP协议的特点

# 会将数据量比较小并且时间间隔比较短的数据整合到一起发送
# 并且还会受制于recv括号内的数字大小(核心问题)
  流式协议:跟水流一样不间断

黏包问题原因

1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点
2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

# 粘包是接收长度没对上导致的
	控制recv接收的字节数与之对应(你发多少字节我收多少字节)
	在很多情况下并不知道数据的长度,服务端不能写死

"""
问题产生的原因其实是因为recv括号内我们不知道即将要接收的数据到底多大
如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现黏包
"""

解决黏包问题

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

struct模块

struct 模块可以将任意大小的数字转换成一个固定长度(可选择)的 bytes, 这个原理类似于 hash 算法, 不论内容多大, 最终的 hash 值长度不变, 不同的是 hash 算法是不可逆的, 而且传入的原材料可以是文本、字符串等许多数据类型, struct 可以反解出原来的数据

ps : 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,)

"""
  pack可以将任意长度的数字打包成固定长度
  unpack可以将固定长度的数字解包成打包之前数据真实的长度
"""
这里利用struct模块的 struct.pack() struct.unpack() 方法来实现打包(将真实数据长度变为固定长度的数字)解包(将该数字解压出打包前真实数据的长度)

    pack unpack模式参数对照表(standard size 转换后的长度)

image

思路

# 服务器端
    先制作一个发送给客户端的字典
    制作字典的报头
    发送字典的报头
    发送字典
    再发真实数据
    
# 客户端
    先接收字典的报头
    解析拿到字典的数据长度
    接收字典
    从字典中获取真实数据的长度
    循环获取真实数据

ps:为什么要多加一个字典
  pack打包的数据长度(的长度)有限,字典再打包会很小(长度值也会变很小)(120左右)
  可以携带更多的描述信息

image

代码演示

服务端

import socket
from socket import SOL_SOCKET, SO_REUSEADDR
import os
import json
import struct

server = socket.socket()  # 创建一个socket对象(server)

server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 防止重启报错了

server.bind(('127.0.0.1', 9000))  # 给socket对象(server)绑定ip和端口号(以元组的形式)
server.listen(5)  # 设置半连接池,最低为0
sock, addr = server.accept()  # 接收客户端连接,获得sock连接 和 addr客户端地址。

# 1.先制作一个字典(可以放入一些描述性的信息)
data_dict = {
      'file_name': '11.jpg',
      'file_title': '壁纸',
      'file_size': os.path.getsize(r'D:\pythonproject\test\11.jpg') # 可以改为自己的文件地址
  }
  # 2.制作字典报头
data_dict_json_str = json.dumps(data_dict)  # 转化成字符串
data_dict_json_str_bytes = data_dict_json_str.encode('utf8')  # 将字符串编码
data_dict_package = struct.pack('i', len(data_dict_json_str_bytes))  # 以i模式打包,打包成4个字节
  # 3.发送报头
sock.send(data_dict_package)  # 服务端将字典发送给客户端
  # 4.发送字典
sock.send(data_dict_json_str_bytes)
  # 5.发送真实数据
with open(r'D:\pythonproject\test\11.jpg', 'rb') as f:
	for i in f:
		sock.send(i)
print('发送完成')

客户端

import socket
import struct
import json

client = socket.socket()  # 创建一个socket对象(client)
client.connect(('127.0.0.1', 9000))  # 根据ip和端口连接服务端(元组的形式),主动发起连接

  # 1.先接收字典的报头
data_dict_package = client.recv(4)
  # 2.解析拿到字典的数据长度
data_dict_len = struct.unpack('i', data_dict_package)[0]
  # 3.接收字典数据
data_dict = client.recv(data_dict_len)
data_dict = data_dict.decode('utf8')
data = json.loads(data_dict)
  # 4.循环接收文件数据 不要一次性接收
recv_size = 0
with open('waa.jpg', 'wb') as f:
	while recv_size < data.get('file_size'):
		write_data = client.recv(1024)
		recv_size += len(write_data)
		f.write(write_data)

recv括号内的数字尽量不要写太大 ,1024 \2048\ 4096足够了,字典数据很难突破上面的数值,所以针对大文件的接收应该采用循环的形式一次接受一点点

image

标签:socket,python,recv,Day33,server,学习,data,服务端,客户端
来源: https://www.cnblogs.com/GuoQQa/p/16151665.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有