对前边的工作Python聊天室程序和Python聊天室程序---客户端的改进的进一步更新。
客户端和服务器之间的消息传递格式为
struct.Struct('I 50s 100s')
‘I’带表消息类型,在Struct中表示整型数据;‘50s’代表发送消息的用户名,'s'代表字符,用户名最多为50个字符;‘100s’代表所发送的消息,最多为100个字符。
如果需要对发送信息的字体进行一些设置,或者想发送一些表情,可以在消息格式中添加一些新的内容即可。
struct的更详细的信息可以查询《Python标准库》一书。另外json据说也能做相关工作,这个以后再进行尝试。
在客户端里,从接收到的内容中提取具体信息方法为:
data = self.sock.recv(200)
s = struct.Struct('I 50s 100s')
unpacked_data = s.unpack(data)
extra = b'\x00'
infoType = unpacked_data[0]
username = (unpacked_data[1].decode()).strip(extra.decode())
info = (unpacked_data[2].decode()).strip(extra.decode())
extra是客户端接收到的空字符,比如用户名有50个字符,我们只占用了30个,剩下的20个空的。
unpacked_data是二进制数据,所以转换为字符串时需要使用decode()。strip(rm)是将字符串开头和结尾的rm字符都去除掉。
infoType,username,info就是从提取出的消息类型,用户名,信息。
下面是客户端的示意图,略丑,勿拍。
客户端代码:
# Filename: socket_client.py
from tkinter import *
import Pmw
import threading
import socket
import struct
MESSAGE = 1
NEWPARTICIPANT = 2
PARTICIPANTLEFT = 3
REFUSE = 4
class ChatFrame:
def __init__(self, master=None):
# Create a TCP/IP socket And connect the socket to the port where
# the server is listening
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_address = ('localhost', 10000)
print (sys.stderr, 'connecting to %s port %s' % self.server_address)
self.sock.connect(self.server_address)
# 将主机hostname当做用户名
self.username = socket.gethostname()
# Client GUI
self.master = master
## Base frame for the widgets
self.frame = Frame(master)
# 界面左区域的Frame
self.leftFrame = Frame(self.frame)
## Message Display widget
self.textDisplay = Text(self.leftFrame,height=20,width=40)
## Text Input widget
self.textInput = Text(self.leftFrame,height=10,width=40)
## 发送消息按钮
self.sendBtn = Button(self.leftFrame, text='发送')
self.sendBtn.bind('<Button-1>', self.sendMsg)
## 界面右区域的Frame
self.rightFrame = Frame(self.frame)
## 在线人数标签
self.onlineNumLabel = Label(self.rightFrame,text='在线用户:x人',bg='gray')
## 在线用户列表
self.userList = Listbox(self.rightFrame)
## 退出按钮
self.exitBtn = Button(self.rightFrame, text='退出')
# 接受消息
receiveThread = threading.Thread(name='waitForMSG', target=self.receiveMsg)
receiveThread.start()
# 上线通知
self.sendInfo(NEWPARTICIPANT)
# 发送消息
def sendInfo(self,infoType):
# 消息类型为新用户上线
if infoType == NEWPARTICIPANT:
info = 'online\n'
## 信息格式为(消息类型,用户名,信息)
values = (NEWPARTICIPANT,self.username.encode(),info.encode())
## 用户名最多为50个字符,信息最多为100个字符
packer = struct.Struct('I 50s 100s')
packed_data = packer.pack(*values)
# 消息类型为文本输入信息
elif infoType == MESSAGE:
info = self.textInput.get(1.0,END)
print('sending message is %s' % info)
## 信息格式为(消息类型,用户名,信息)
values = (MESSAGE,self.username.encode(),info.encode())
packer = struct.Struct('I 50s 100s')
packed_data = packer.pack(*values)
self.textInput.delete(1.0,END)
elif infoType == PARTICIPANTLEFT:
pass
self.sock.sendall(packed_data)
# 接受消息并处理
def receiveMsg(self):
while True:
data = self.sock.recv(200)
s = struct.Struct('I 50s 100s')
unpacked_data = s.unpack(data)
extra = b'\x00'
## 从接受到的信息中提取具体的信息
infoType = unpacked_data[0]
username = (unpacked_data[1].decode()).strip(extra.decode())
info = (unpacked_data[2].decode()).strip(extra.decode())
print('Infomation Type is')
print(infoType)
print('client received "%s"' % info)
if infoType == MESSAGE:
message = username + ':' + info
self.textDisplay.insert(END, message)
elif infoType == NEWPARTICIPANT:
message = username + 'online\n'
self.textDisplay.insert(END, message)
self.newParticipant(username)
# 发送消息按钮事件处理
def sendMsg(self,event):
self.sendInfo(MESSAGE)
# 处理新用户加入
def newParticipant(self,username):
self.userList.insert(END,username)
if __name__ == '__main__':
root = Tk()
root.title('Chat Room')
tt = ChatFrame(root)
# left frame
tt.textDisplay.pack(fill=X,expand=1,padx=3,pady=3)
tt.textInput.pack(fill=X,expand=1,padx=3,pady=3)
tt.sendBtn.pack(side=LEFT)
tt.leftFrame.pack(side=LEFT,fill=BOTH,padx=3,pady=3,expand=1)
# right frame
tt.onlineNumLabel.pack(fill=X)
tt.userList.pack(fill=Y,expand=1)
tt.exitBtn.pack(side=RIGHT,anchor=CENTER)
tt.rightFrame.pack(side=LEFT,fill=BOTH,pady=3)
tt.frame.pack(fill=BOTH, padx=3, pady=3,expand=1)
root.mainloop()
服务器代码:
# Filename: socketServer.py
import socket
import sys
import struct
# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the port
server_address = ('localhost', 10000)
print (sys.stderr, 'starting up on %s port %s' % server_address)
sock.bind(server_address)
# Listen for incoming connections
sock.listen(1)
# 消息格式
unpacker = struct.Struct('I 50s 100s')
while True:
# Wait for a connection
print (sys.stderr, 'waiting for a connection')
connection, client_address = sock.accept()
try:
print (sys.stderr, 'connection from', client_address)
# Receive the data in small chunks and retransmit it
while True:
data = connection.recv(1000)
print (sys.stderr, 'received "%s"' % data)
if data:
print (sys.stderr, 'sending data back to the client')
connection.sendall(data)
else:
print (sys.stderr, 'no data from', client_address)
break
finally:
# Clean up the connection
connection.close()