在windows下,没有AF_UNIX,只有在linux下才有。
windows下,可使用(AF_INET,SOCK_STREAM)来实现进程间通信,当然也可以使用(SOCK_DGRAM),根据自己的需要来选择。两个进程不需在一个工程中,且各自可位于虚拟环境中,因为用的是localhost通信,也就是ip。
先介绍一下我的练习项目架构设计:
(1)学校数据处理进程:随机产生多个学校信息,每个学校包含多个学院、每个学院包含多个专业、每个专业包含4个年级、每个年级包含多个班级、每个班级包含多个学生、每个学生有很多属性,这样产生的数据量可以自己控制达到足够大。
其中学校、学院、专业等使用类属性定义,保留后期爬虫接口用于自动爬取数据;数据量大一些,也可以练习很多算法,本工程算是自己的一个学习平台。
(2)学生信息处理进程:为后期保留,用于分析学生个体的一些特性。
(3)Django工程:前端呈现学校的各种查询入口,后台与上述进程交互数据,再将数据返回前端。
下面介绍socket通信的设计:
(1)首先,需要在服务器/客户端之间约定消息格式,如可以自定义IPC_Msg类来实现消息的收发处理。
可约定ModuleId(模块id,用于标识收、发进程)、MSG_Type(消息类型,根据自己的需要约定)、MSG_SubType(消息字类型,根据自己的需要约定)、IPC_Opcode(IPC操作动作)等。
数据data使用字典进行存放,使用key来取value。
from enum import Enum, unique
@unique
class ModuleId(Enum):
Django = 1
College = 2
Student = 3
@unique
class MSG_Type(Enum):
All_Colleges = 1
College = 2
Academy = 3
Major = 4
Grade = 5
Classs = 6
Student = 7
Teacher = 8
@unique
class MSG_SubType(Enum): # 待扩展
Invalid = 1
@unique
class IPC_Opcode(Enum):
Add = 1 # 增加一项数据
Del = 2 # 删除一项数据
Update = 3 # 更新一项数据
Get = 4 # 获取
Getbulk = 5 # 批量获取
Clear = 6 # 全部清空
Enable = 7 # 使能
Disable = 8 # 禁能
Register = 9 # 注册
UnRegister = 10 # 解注册
Event = 11 # 事件
Reply = 12 # 应答
NoReply = 13 # 应答
@unique
class TLV_TAG(Enum):
STR = 1
INT = 2
class TLV():
"""tag、length、value数据结构"""
def __init__(self, tag, length, value):
"""
:param tag: 从枚举类TLV_TAG中选择
:param length: >1
:param value: 列表或字符串
"""
if tag not in TLV_TAG.__members__.values():
raise TypeError('tag must be a member of TLV_TAG')
if length < 1:
raise ValueError('length must be > 1')
if not isinstance(value, str) or not isinstance(value, list):
raise TypeError("value's type must be str or list")
self.tag = tag
self.length = length
self.value = value
IPC_HDR_LEN = 5
IPC_MSG_LEN = 7
class IpcMsg:
"""添加类说明"""
def __init__(self):
self._module_id = 0
self._sender_id = 0
self._msg_type = 0
self._msg_subtype = 0
self._opcode = 0
self._data_num = 0
self._data = {}
def __str__(self):
return 'This is a ipc message'
def data_add(self, key, value):
"""
向IPC消息数据结构中增加一项数据
:param key:
:param value:
:return:
"""
if not isinstance(key, str):
raise TypeError("key must be a str")
self._data[key] = value
self._data_num += 1
def data_del(self, key, value):
"""
从IPC消息数据结构中删除一项数据
:param key:
:param value:
:return:
"""
if not isinstance(key, str):
raise TypeError("key must be a str")
del self._data[key]
def fill_hdr(self, hdr):
"""
填充消息头
:param hdr:长度为5的列表,且各项数据必须从上述枚举中取
:return:
"""
if not isinstance(hdr, list) or IPC_HDR_LEN != len(hdr):
raise TypeError('hdr must be a list and length is %d' % IPC_HDR_LEN)
self.module_id = hdr[0]
self.sender_id = hdr[1]
self.msg_type = hdr[2]
self.msg_subtype = hdr[3]
self.opcode = hdr[4]
def fill_all(self, msg):
"""
填充全部消息,包括消息头和数据
:param msg: 列表,长度为7
:return:
"""
if not isinstance(msg, list) or IPC_MSG_LEN != len(msg):
raise TypeError('msg must be a list and length is %d' % IPC_MSG_LEN)
self.module_id = ModuleId(msg[0])
self.sender_id = ModuleId(msg[1])
self.msg_type = MSG_Type(msg[2])
self.msg_subtype = MSG_SubType(msg[3])
self.opcode = IPC_Opcode(msg[4])
self.data_num = msg[5]
self.data = msg[6]
def to_list(self):
"""
将消息的每个属性按顺序放置在列表中
:return:
"""
attrs_list = []
attrs_list.append(self.module_id.value)
attrs_list.append(self.sender_id.value)
attrs_list.append(self.msg_type.value)
attrs_list.append(self.msg_subtype.value)
attrs_list.append(self.opcode.value)
attrs_list.append(self.data_num)
attrs_list.append(self.data)
return attrs_list
def from_list(self, data_list):
"""
从list中获取各项数据
:param data_list:
:return:
"""
if not isinstance(data_list, list):
raise TypeError('data_list')
if 7 != len(data_list):
raise ValueError('data_list')
self.module_id = ModuleId(data_list[0])
self.sender_id = ModuleId(data_list[1])
self.msg_type = MSG_Type(data_list[2])
self.msg_subtype = MSG_SubType(data_list[3])
self.opcode = IPC_Opcode(data_list[4])
self.data_num = data_list[5]
self.data = data_list[6]
@property
def module_id(self):
return self._module_id
@module_id.setter
def module_id(self, module_id):
if module_id not in ModuleId.__members__.values():
raise TypeError('module id must be member of ModuleId')
self._module_id = module_id
@property
def sender_id(self):
return self._sender_id
@sender_id.setter
def sender_id(self, sender_id):
if sender_id not in ModuleId.__members__.values():
raise TypeError('sender id must be member of ModuleId')
self._sender_id = sender_id
@property
def msg_type(self):
return self._msg_type
@msg_type.setter
def msg_type(self, msg_type):
if msg_type not in MSG_Type.__members__.values():
raise TypeError('message type must be member of MSG_Type')
self._msg_type = msg_type
@property
def msg_subtype(self):
return self._msg_subtype
@msg_subtype.setter
def msg_subtype(self, msg_subtype):
if msg_subtype not in MSG_SubType.__members__.values():
raise TypeError('message subtype must be member of MSG_SubType')
self._msg_subtype = msg_subtype
@property
def opcode(self):
return self._opcode
@opcode.setter
def opcode(self, opcode):
if opcode not in IPC_Opcode.__members__.values():
raise TypeError('opcode subtype must be member of IPC_Opcode')
self._opcode = opcode
@property
def data_num(self):
return self._data_num
@data_num.setter
def data_num(self, data_num):
if not isinstance(data_num, int):
raise TypeError('data_num must be type of int')
self._data_num = data_num
@property
def data(self):
return self._data
@data.setter
def data(self, data):
if not isinstance(data, dict):
raise TypeError('data must be type of dict')
self._data = data
self.data_num = len(self.data)
(2)数据收发的编码与解码。
直接发送消息队列对象是不行的,可将队列的7个属性放在一个列表里发送,对端收到后再依次取出来即可。
由于通过socket发送数据时,只能发送字节流,所以需要将ipc消息进行编解码处理。由于完全自学,该过程花费了一些时间,试过使用TLV格式进行数据发送,但数据处理感觉较复杂。
发送时,通过 json.dumps(data_send).encode(),将数据转换json再转换为字节流;
接收时,通过 json.loads(data_send.decode()),将字节流数据还原。
由于代码较多,就不再放收发处理代码了。