一. 接着上一篇文章,原理:多个客户端在线,服务器端会产生多个连接客户端的线程,通过Queue使这些连接客户端线程相互发送消息
二 .新增三个protobuf数据文件,当然socket传输数据也可以用其他格式数据,如json等,我的前几篇文章都是用protobuf格式数据传输的。
FriendMessage.py
syntax = "proto3";
message FriendMessage {
string FriendId = 1; // ID号
string FriendNo = 2; // 别名
string FriendNick = 3; // 好友的昵称
string Avatar = 9; //头像
}
ContactsInfoNoticeMessage.py
syntax = "proto3";
import "FriendMessage.proto";
message ContactsInfoNoticeMessage {
string WeChatId = 1; // 全局唯一识别码
repeated FriendMessage Friends = 2; // 好友信息
}
FriendTalkNoticeMessage.py syntax = “proto3”;
message FriendTalkNoticeMessage {
string WeChatId = 1; // 唯一识别码
string FriendId = 3; // 好友唯一识别码
bytes Content = 6; // 内容 二进制流
}
编译生成的.py文件复制到项目中,如果不知道怎么编译请参考我的第二篇文章
三.服务器端代码,ServerProtoTest.py
import socket
import time
import google.protobuf
import google.protobuf.any_pb2
import TransportMessage_pb2
import WeChatOnlineNoticeMessage_pb2
import ContactsInfoNoticeMessage_pb2
import FriendMessage_pb2
import FriendTalkNoticeMessage_pb2
from queue import Queue
from threading import Thread
import threading
import gevent
from gevent import socket,monkey
monkey.patch_all()
import socketserver
allFriendList = list() #不包含重复的好友信息
List_Lock = threading.Lock() #线程锁
queue = Queue()#创建队列
"""class Myserver(socketserver.BaseRequestHandler):
def handle(self):
conn = self.request
Thread_recv(conn)"""
def Thread_send(socket_Server,queue,WeChatId):
global allFriendList
try:
while True:
msg = queue.get()
print('Thread_send',msg['FriendId'],WeChatId)
if msg['FriendId']==WeChatId:
SendMessage = TransportMessage_pb2.TransportMessage()
FriendTalkMessage = FriendTalkNoticeMessage_pb2.FriendTalkNoticeMessage()
FriendTalkMessage.Content = msg['Content']
FriendTalkMessage.WeChatId = WeChatId
SendMessage.MsgType = 1025
SendMessage.Id = 0
SendMessage.AccessToken ="ac897dss"
SendMessage.Content.Pack(FriendTalkMessage)
byte_data = SendMessage.SerializeToString()
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
socket_Server.send(byte_head)
socket_Server.send(byte_data)
else:
for friend in allFriendList:
if friend['WeChatId'] == msg['FriendId']:
queue.put(msg)
time.sleep(0.000001)#只要有个延时就行,不然会导致代码死循环
except Exception as ex:
print('Thread_send 异常...',ex)
def Thread_recv(socket_Server):
try:
global allFriendList
global queue
online = WeChatOnlineNoticeMessage_pb2.WeChatOnlineNoticeMessage()
while True:
Head_data = socket_Server.recv(4) #接收数据头 4个字节,
data_len = int.from_bytes(Head_data, byteorder='big')
print('data_len=',data_len)
protobufdata = socket_Server.recv(data_len)
tmessage = TransportMessage_pb2.TransportMessage()
tmessage.ParseFromString(protobufdata)
i_id = tmessage.Id
i_msgtype = tmessage.MsgType
now_time = time.strftime('%Y-%m-%d %H:%M:%S')
print(now_time,' id:',i_id,'msgType:',i_msgtype)
if i_msgtype==0:
print('异常')
for friend in allFriendList:
if friend['WeChatId'] == online.WeChatId:
print('下线WeChatId:',online.WeChatId)
if List_Lock.acquire():
allFriendList.remove(friend)
List_Lock.release()
break
socket_Server.close()
break
if i_msgtype==1010:
print(now_time,' 服务器接收到心跳包...好友在线个数:',len(allFriendList))
#print(now_time,' 服务器接收到心跳包...')
if i_msgtype == 1020:
print(now_time,' 服务器接收到上线通知...')
#有上线通知,将好友信息和socket_Server套接字存放在buf中,
#这样可以将好友A的信息转发给好友B,做成及时聊天工具
#有下线通知,将好友信息和socket_Server套接字从buf中去掉
#服务器端功能就是收集上线和下线通知,转发聊天消息
tmessage.Content.Unpack(online)
print(now_time,' WeChatNo:'+online.WeChatNo,'WeChatId:'+online.WeChatId,'WeChatNick:'+online.WeChatNick)
#没有添加就添加,列表中有的话就更新
f_dict = {"WeChatId":online.WeChatId,"socket_Server":socket_Server,"WeChatOnlineNoticeMessage":online}
isAddFriend = True
for friend in allFriendList:
if friend['WeChatId'] == online.WeChatId:
isAddFriend = False
print('已上线WeChatId:',online.WeChatId)
if isAddFriend:
if List_Lock.acquire():
allFriendList.append(f_dict)
List_Lock.release()
#新建转发好友消息的线程
t_send = threading.Thread(target=Thread_send,args=(socket_Server,queue,online.WeChatId))
t_send.start()
if i_msgtype == 1021:
print(now_time,'服务器接收到下线通知')
for friend in allFriendList:
if friend['WeChatId'] == online.WeChatId:
print('下线WeChatId:',online.WeChatId)
if List_Lock.acquire():
allFriendList.remove(friend)
List_Lock.release()
break
socket_Server.close()
break #退出while
if i_msgtype == 1022:
print(now_time,'服务器接收到客户端请求在线好友信息通知')
contacts = ContactsInfoNoticeMessage_pb2.ContactsInfoNoticeMessage()
for friend in allFriendList:
friendmessage = contacts.Friends.add()
friendmessage.FriendId = friend['WeChatId']
#contacts.add(friendmessage)
transportMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
transportMessage.MsgType = 1023
transportMessage.Id = 0
transportMessage.AccessToken ="ac897dss"
transportMessage.Content.Pack(contacts)
byte_data = transportMessage.SerializeToString()
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
socket_Server.send(byte_head)
socket_Server.send(byte_data)
if i_msgtype == 1024:
print(now_time,'转发好友聊天内容')
FriendTalkMessage = FriendTalkNoticeMessage_pb2.FriendTalkNoticeMessage()
tmessage.Content.Unpack(FriendTalkMessage)
print(now_time,online.WeChatId,FriendTalkMessage.FriendId,str(FriendTalkMessage.Content,'utf-8'))
#if FriendTalkMessage.FriendId == online.WeChatId:
msg ={'FriendId':FriendTalkMessage.FriendId,'Content':FriendTalkMessage.Content}
queue.put(msg)
"""SendMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
SendMessage.MsgType = 1025
SendMessage.Id = 0
SendMessage.AccessToken ="ac897dss"
SendMessage.Content.Pack(FriendTalkMessage)
byte_data = SendMessage.SerializeToString()
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
socket_Server.send(byte_head)
socket_Server.send(byte_data)"""
except Exception as ex:
print('Thread_recv 异常...',ex)
for friend in allFriendList:
if friend['WeChatId'] == online.WeChatId:
print('下线WeChatId:',online.WeChatId)
if List_Lock.acquire():
allFriendList.remove(friend)
List_Lock.release()
break
finally:
socket_Server.close()
if __name__ == "__main__":
#方法3
#server = socketserver.ThreadingTCPServer(('192.168.0.100',11087),Myserver)
#server.serve_forever()
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
HostPort = ('192.168.0.12',11087)
s.bind(HostPort) #绑定地址端口
s.listen(100) #监听最多100个连接请求
while True:
print('server socket waiting...')
obj,addr = s.accept() #阻塞等待链接
print('socket object:',obj)
print('client info:',addr)
#方法1
t_recv = threading.Thread(target=Thread_recv,args=(obj,))
t_recv.start()
#方法2
#gevent.spawn(Thread_recv,obj)
四.客户端代码,ClientProtoTest.py
from socket import *
import google.protobuf
import TransportMessage_pb2
import WeChatOnlineNoticeMessage_pb2
import ContactsInfoNoticeMessage_pb2
import FriendTalkNoticeMessage_pb2
import traceback
import google.protobuf.any_pb2
import threading
import time
Id = 0
IsConnect = False
onlineNotice_bytes=None
WeChatId = None
contacts = None
def HearBeatReq_bytes():
global Id
Id+=1
transportMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
transportMessage.MsgType = 1010
transportMessage.Id = Id
transportMessage.AccessToken ="ac897dss"
#print('心跳包数据...Id=',Id)
return transportMessage.SerializeToString()
def OfflineNotice_bytes():
global Id
Id+=1
transportMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
transportMessage.MsgType = 1021
transportMessage.Id = Id
transportMessage.AccessToken ="ac897dss"
print('下线通知...Id=',Id)
return transportMessage.SerializeToString()
def OnlineNotice_bytes():
global Id
global WeChatId
time_t = int(time.time())
WeChatId = "id_"+str(time_t) #随机生成一个WeChatId
print(WeChatId)
Id+=1
transportMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
transportMessage.MsgType = 1020
transportMessage.Id = Id
transportMessage.AccessToken ="ac897dss"
weChatOnlineNotice = WeChatOnlineNoticeMessage_pb2.WeChatOnlineNoticeMessage()
weChatOnlineNotice.WeChatId = WeChatId
weChatOnlineNotice.WeChatNo = "qdj_cancle"
weChatOnlineNotice.WeChatNick = "昵称001"
weChatOnlineNotice.Gender = 0
weChatOnlineNotice.Country = "中国"
print('上线通知...Id=',Id)
transportMessage.Content.Pack(weChatOnlineNotice)
return transportMessage.SerializeToString()
def GetOnlineFriendNotice_bytes():
global Id
Id+=1
transportMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
transportMessage.MsgType = 1022
transportMessage.Id = Id
transportMessage.AccessToken ="ac897dss"
print('获取在线好友通知...Id=',Id)
return transportMessage.SerializeToString()
def SendChatContentNotice_bytes(FriendId,content):
global Id
global WeChatId
Id+=1
#print(FriendId,content)
transportMessage = TransportMessage_pb2.TransportMessage()#注意括号不要掉了,
transportMessage.MsgType = 1024
transportMessage.Id = Id
transportMessage.AccessToken ="ac897dss"
FriendTalkNotice = FriendTalkNoticeMessage_pb2.FriendTalkNoticeMessage()
FriendTalkNotice.WeChatId = WeChatId #随机生成一个WeChatId
FriendTalkNotice.FriendId = FriendId
FriendTalkNotice.Content = bytes(content,'utf-8')
transportMessage.Content.Pack(FriendTalkNotice)
print(WeChatId,'给好友',FriendId,'发送消息',content)
return transportMessage.SerializeToString()
def thread_HearBeat(tcpCliSock):
global IsConnect
try:
t = 1
print('thread_HearBeat=',IsConnect)
while IsConnect:
if t > 10:
t = 1
hearBeat_data = HearBeatReq_bytes()
byte_data = hearBeat_data
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
tcpCliSock.send(byte_head)
tcpCliSock.send(byte_data)
#print('10秒定时发送心跳包...\r\n发送数据[0].退出;[1].心跳包;[2].上线通知>')
time.sleep(1)
t+=1
except Exception as ex:
print('tcpCliSock异常',ex)
finally:
tcpCliSock.close()
def thread_recv(tcpCliSock):
global IsConnect
global contacts
try:
while IsConnect:
Head_data = tcpCliSock.recv(4) #接收数据头 4个字节,
data_len = int.from_bytes(Head_data, byteorder='big')
print('data_len=',data_len)
protobufdata = tcpCliSock.recv(data_len)
tmessage = TransportMessage_pb2.TransportMessage()
tmessage.ParseFromString(protobufdata)
i_id = tmessage.Id
i_msgtype = tmessage.MsgType
now_time = time.strftime('%Y-%m-%d %H:%M:%S')
print(now_time,' id:',i_id,'msgType:',i_msgtype)
if i_msgtype==0:
print('异常')
break
if i_msgtype==1023:
print(now_time,' 接收到在线好友消息:')
print(tmessage.Content)
contactsMessage = ContactsInfoNoticeMessage_pb2.ContactsInfoNoticeMessage()
tmessage.Content.Unpack(contactsMessage)
contacts = contactsMessage.Friends
index = 0
for friend in contacts:
index+=1
print(index,'在线好友Id:'+friend.FriendId)
if i_msgtype == 1025:
print(now_time,'接收到好友消息:')
FriendTalkMessage = FriendTalkNoticeMessage_pb2.FriendTalkNoticeMessage()
tmessage.Content.Unpack(FriendTalkMessage)
print(now_time,FriendTalkMessage.FriendId,'发来消息:'+str(FriendTalkMessage.Content,'utf-8'))
except Exception as ex:
print('thread_recv异常',ex)
finally:
tcpCliSock.close()
def main():
global IsConnect
global onlineNotice_bytes
global WeChatId
HOST = '192.168.0.12'
PORT = 11087
BUFSIZ =1024
ADDR = (HOST,PORT)
tcpCliSock = socket(AF_INET,SOCK_STREAM)
tcpCliSock.connect(ADDR)
IsConnect = True
t_hearBeat = threading.Thread(target=thread_HearBeat,args=(tcpCliSock,))
t_hearBeat.start() #启动心跳线程
t_rev = threading.Thread(target=thread_recv,args=(tcpCliSock,))
t_rev.start() #启动心跳线程
#friendInfoList = list()
try:
while IsConnect:
data1 = input('[0].下线退出;\n[1].心跳包;\n[2].上线通知;\n[3].获取在线好友信息;\n[4].给所有在线好友发送消息;\n>')
print('输入指令:',data1)
if data1=='0':
Offline_data = OfflineNotice_bytes()
byte_data = Offline_data
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
tcpCliSock.send(byte_head)
tcpCliSock.send(byte_data)
IsConnect = False
break
if data1 =='1':
hearBeat_data = HearBeatReq_bytes()
byte_data = hearBeat_data
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
tcpCliSock.send(byte_head)
tcpCliSock.send(byte_data)
if data1 =='2':
if onlineNotice_bytes is None:
onlineNotice_bytes = OnlineNotice_bytes()
byte_data = onlineNotice_bytes
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
tcpCliSock.send(byte_head)
tcpCliSock.send(byte_data)
if data1 == '3':
byte_data = GetOnlineFriendNotice_bytes()
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
tcpCliSock.send(byte_head)
tcpCliSock.send(byte_data)
if data1 == '4':
if not contacts is None:
for friend in contacts:
if not WeChatId==friend.FriendId:
byte_data = SendChatContentNotice_bytes(friend.FriendId,'hello,我是'+WeChatId)
byte_head = (len(byte_data)).to_bytes(4, byteorder='big')
tcpCliSock.send(byte_head)
tcpCliSock.send(byte_data)
except Exception as identifier:
print('退出程序!',IsConnect)
finally:
tcpCliSock.close()
if __name__ == "__main__":
main()
五.运行结果,开启三个客户端,输入数字2,上线三个客户端
聊天运行结果如下:客户端输入数字3,获取在线好友信息
客户端输入数字4,发送消息给其他两个好友,结果如下:
总结:此项目到此为止,不再进行扩展了,1到7篇文章都是描述这个项目的,通过socket传输protobuf结构数据,实现客户端相互聊天功能。