Loading

socket套接字简介

cheems柴犬表情包

socket套接字简介

Socket是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。
作用:编写代码操作OSI七层,相当的复杂,由于操作OSI七层所有CS架构都需要经历的过程,所以有固定的模块
socket套接字是一门技术
socket模块>>>:提供了快捷方式,不需要自己处理每一层
'''
socket是最底层的原理,很多框架都封装了
'''

socket模块

cs架构的软件无论是在编写还是运行
	都应该考虑服务端
    有了店运营了才可以接待客人
 
# 服务器端:
'''
其过程是首先服务器方要先启动,并根据请求提供相应服务:

(1)打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口(如FTP的端口可能为21)接收客户请求;
(2)等待客户请求到达该端口;
(3)接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
(4)返回第(2)步,等待另一客户请求。
(5)关闭服务器
'''
# 客户端:

'''(1)打开一通信通道,并连接到服务器所在主机的特定端口;
(2)向服务器发服务请求报文,等待并接收应答;继续提出请求…
(3)请求结束后关闭通信通道并终止。
从上面所描述过程可知:

(1)客户与服务器进程的作用是非对称的,因此代码不同。
(2)服务器进程一般是先启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。
'''

在这里插入图片描述

socekt通信流程

在这里插入图片描述

socket基本使用

服务器端

import socket

server = socket.socket()  # 买手机
"""
通过查看源码得知 
括号内不写参数默认就是基于网络的遵循TCP协议的套接字
"""
server.bind(('127.0.0.1', 8080))  # 插电话卡
"""
服务端应该具备的特征
    固定的地址
127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问
"""
server.listen(5)  # 开机  # 半连接池
sock, addr = server.accept()  # 等待并接听电话  没有人来就原地等待(程序阻塞)
"""
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 sweetbaby')   # 根据服务端的地址发送
data = client.recv(1024)  # 接受服务端回复的消息
print(data.decode('utf8'))
client.close()  # 关闭客户端

注意事项

服务端与客户端首次交互
	一边是recv那么另一边必须是send  两边不能相同 否则双方都处于'听'的状态,不会进行下一步操作,就像连个人吵架,你不理我,我也不理你
    

cheems柴犬表情包

通信循环问题

# 思路:
    '''
    先解决消息固定的问题
        利用input获取用户输入
    在解决通信循环的问题
        将双方用于数据交互的代码循环起来
    '''
# 实现方法:
# 服务器端
import socket

server = socket.socket()  # 买手机
server.bind(('127.0.0.1', 8080))  # 插电话卡
server.listen(5)  # 开机  # 半连接池
sock, addr = server.accept()  # 等待并接听电话  没有人来就原地等待(程序阻塞)
print(addr)  # 客户端的地址
while True:
    data = sock.recv(1024)  # 听别人说话
    print(data.decode('utf8'))
    msg = input('请输入你想回复的消息>>>:')
    sock.send(msg.encode('utf8'))  # 回复别人说的话

    
# 客户端
import socket

client = socket.socket()   # 产生一个socket对象
client.connect(('127.0.0.1', 8080))   # 根据服务端的地址链接
while True:
    msg = input('请输入你想发送的消息>>>:')
    client.send(msg.encode('utf8'))   # 根据服务端的地址发送
    data = client.recv(1024)  # 接受服务端回复的消息
    print(data.decode('utf8'))

代码优化及链接循环

1.发送消息不能为空
	统计长度并判断即可
2.反复重启服务端可能会报错>>>:address in use
  这个错在苹果电脑报的频繁 windows频率较少
  from socket import SOL_SOCKET,SO_REUSEADDR
  server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加
3.链接循环
客户端如果异常断开 服务端代码应该重新回到accept等待新的客人
'''
  如果是windows 客户端异常退出之后服务端会直接报错
  	处理方式
  		异常处理
  如果是mac或linux 服务端会接收到一个空消息
  	处理方式
  		len判断
'''
目前的服务端只能实现一对一,不能够一对多,学了并发才可以

半连接池

listen(5)
当服务器在响应了客户端的第一次请求后会进入等待状态,会等客户端发送的ack信息,这时候这个连接就称之为半连接。
# 作用:
# 半连接池其实就是一个容器,系统会自动将半连接放入这个容器中,可以避免半连接过多而保证资源耗光。

黏包问题

'''
TCP为了保证可靠传输,尽量减少额外开销(每次发包都要验证),因此采用了流式传输,面向流的传输,相对于面向消息的传输,可以减少发送包的数量,从而减少了额外开销。但是,对于数据传输频繁的程序来讲,使用TCP可能会容易粘包。
'''
# 服务端代码演示:

import socket

server = socket.socket()  # 买手机 不传参数默认用的就是TCP协议
server.bind(('127.0.0.1',8080))  # bind((host,port))  插电话卡  绑定ip和端口
server.listen(5)  # 开机    半连接池
conn, addr = server.accept()  # 接听电话  等着别人给你打电话     阻塞
data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(5)  # 听别人说话 接收1024个字节数据          阻塞
print(data)
data = conn.recv(4)  # 听别人说话 接收1024个字节数据          阻塞
print(data)


# 客户端代码演示:

import socket

client = socket.socket()  # 拿电话
client.connect(('127.0.0.1',8080))  # 拨号   写的是对方的ip和port
client.send(b'hello')
client.send(b'world')
client.send(b'baby')

解决黏包问题

struct模块

struct 模块可以将任意大小的数字转换成一个固定长度(可选择)的 bytes,而且传入的原材料可以是文本、字符串等许多数据类型, 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左右)
  可以携带更多的描述信息

代码演示:

服务器端

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', 8888))  # 给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'1.txt.py') # 可以改为自己的文件地址
  }
  # 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'1.txt.py', '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('kehu' + 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)
posted @ 2022-04-16 22:04  香菜根  阅读(104)  评论(0编辑  收藏  举报