# Python 作业 13 ```java 实现 一个 简单的 客服聊天系统。 客服中心是tcp服务端程序 客户使用tcp客户端程序。 服务端程序 先运行, 绑定本机一个ip地址, 等待客户端系统连接上来 。 客户端程序有一个命令行参数, 表示客户的名字 连接成功后, 客户端发送给服务端第一个消息 必须告诉客服中心,用户的名字 一个客户连接后,别的客户不能连接, 等到前面的客户断开连接后,才能连上。 客户端和服务端都是手动在终端输入信息,发送消息后,必须等待接受到对方的消息才能发送下一个消息。 我们定义消息的格式如下: 0008|1|nickname 用竖线隔开3部分的字段,分别表示 消息长度、 消息类型 、消息内容 前面字段是4个字节的字符串,比如'0008',其内容是数字,表示消息的长度, 不足4个字节前面补零。 注意长度是整个消息内容的长度,包括 消息头部和消息体 后面用竖线隔开的字段,是1个字节的字符串,是消息类型,其内容是数字,1表示客户昵称, 2 表示 普通消息 前面两个字段合起来 0008|1| , 可以看成是一个消息的头部, nickname 是消息体 再后面用竖线隔开的字段是 消息内容,其长度等于 前面消息长度字段指明的长度 减去 消息头部长度 (也就是7个字节) 服务端程序在下面, 大家参考服务端程序的实现,开发客户端程序和服务端进行通讯 ``` ```python # coding=utf8 import sys from socket import socket,AF_INET,SOCK_STREAM HOST = '' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) class CloseSocketError(Exception): pass # 一个 ConnectionHandler 处理和一个客户端的连接 class ConnectionHandler: # 0008|1|nickname LEN_MSG_LEN_FIELD = 4 LEN_MSG_LEN_TYPE_FIELD = 7 def __init__(self,sock): # 消息缓存区 self._readbuffer = b'' self.sock = sock self.customername = '' # msgBody 是 unicode @staticmethod def encode(msgType,msgBody): rawMsgBody = msgBody.encode('utf8') msgLenth = '{:04}' \ .format(len(rawMsgBody)+ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD) \ .encode() msgType = f'{msgType}'.encode() return b'|'.join([msgLenth,msgType,rawMsgBody]) @staticmethod def decode(rawmsg): msgType = int(rawmsg[5:6]) # 这样写rawmsg[5] 返回的是字节对应的数字 msgbody = rawmsg[ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD:].decode('utf8') return [msgType,msgbody] def readMsg(self): bytes = self.sock.recv(BUFSIZ) # ** 用不同的返回值表示不同的含义 # 当对方关闭连接的时候,抛出异常 if not bytes: self.sock.close() raise CloseSocketError() # 应用程序的读取缓冲,和前面讲的系统的读取缓冲是两个不同的缓冲 self._readbuffer += bytes buffLen = len(self._readbuffer) # 如果已经获取了消息头部 (包括 消息长度,消息类型) if buffLen >= self.LEN_MSG_LEN_TYPE_FIELD: msgLen = int(self._readbuffer[:self.LEN_MSG_LEN_FIELD]) # 缓存区消息 已经包含了一个整体的消息(包括 消息长度,消息类型,消息体) if buffLen >= msgLen: # 从缓存区,截取整个消息 msg = self._readbuffer[0:msgLen] # 缓存区变成剩余的消息部分 self._readbuffer = self._readbuffer[msgLen:] return self.decode(msg) # 如果已经获取的消息还不包括一个完整的消息头部, 不做处理等待下面继续接受消息 else: return None print('get:%s' % bytes) # msgBody 是 unicode def sendMsg(self,msgType,msgBody): self.sock.sendall(self.encode(msgType,msgBody)) def handleMsg(self,msgType,msgBody): # 客户名称 if msgType == 1: self.customername = msgBody print('客户名称设置:%s' % self.customername) # 普通消息 elif msgType == 2: print(msgBody) print('---------------') # 客服输入消息内容 msgSend = input('>>') self.sendMsg(2,msgSend) # 主循环,不断的接受消息发送消息 def mainloop(self): while True: try: msg = self.readMsg() # msg 里面包含了 type 和body if msg: msgType,msgBody= msg self.handleMsg(msgType,msgBody) except CloseSocketError: print('对方断开了连接,等待下一个客户') break except IOError: print('对方断开了连接,等待下一个客户') break #创建socket,指明协议 tcpSerSock = socket(AF_INET, SOCK_STREAM) #绑定地址和端口 tcpSerSock.bind(ADDR) tcpSerSock.listen(5) print('等待客户端连接...') while True: #阻塞式等待连接请求 tcpCliSock, addr = tcpSerSock.accept() print('有客户连接上来', addr) handler = ConnectionHandler(tcpCliSock) handler.mainloop() tcpSerSock.close() ``` ## 参考答案,往下翻



























































```python # coding=utf-8 from socket import * import traceback,sys HOST = 'localhost' PORT = 21567 BUFSIZ = 1024 ADDR = (HOST, PORT) class CloseSocketError(Exception): pass class ConnectionHandler: # 0008|1|nickname LEN_MSG_LEN_FIELD = 4 LEN_MSG_LEN_TYPE_FIELD = 7 def __init__(self,sock): # 消息缓存区 self._readbuffer = b'' self.sock = sock self.customername = '' # msgBody 是 unicode @staticmethod def encode(msgType,msgBody): rawMsgBody = msgBody.encode('utf8') msgLenth = '{:04}' \ .format(len(rawMsgBody)+ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD) \ .encode() msgType = f'{msgType}'.encode() return b'|'.join([msgLenth,msgType,rawMsgBody]) @staticmethod def decode(rawmsg): msgType = int(rawmsg[5:6]) msgbody = rawmsg[ConnectionHandler.LEN_MSG_LEN_TYPE_FIELD:].decode('utf8') return [msgType,msgbody] def readMsg(self): bytes = self.sock.recv(BUFSIZ) # ** 用不同的返回值表示不同的含义 # 当对方关闭连接的时候,抛出异常 if not bytes: self.sock.close() raise CloseSocketError() self._readbuffer += bytes buffLen = len(self._readbuffer) # 如果已经获取了消息头部 (包括 消息长度,消息类型) if buffLen >= self.LEN_MSG_LEN_TYPE_FIELD: msgLen = int(self._readbuffer[:self.LEN_MSG_LEN_FIELD]) # 缓存区消息 已经包含了一个整体的消息(包括 消息长度,消息类型,消息体) if buffLen >= msgLen: # 从缓存区,截取整个消息 msg = self._readbuffer[0:msgLen] # 缓存区变成剩余的消息部分 self._readbuffer = self._readbuffer[msgLen:] return self.decode(msg) # 如果已经获取的消息还不包括一个完整的消息头部, 不做处理等待下面继续接受消息 else: return None print('--> %s' % bytes) # msgBody 是 unicode def sendMsg(self,msgType,msgBody): self.sock.sendall(self.encode(msgType,msgBody)) def handleMsg(self,msgType,msgBody): # 客户名称 if msgType == 2: print(msgBody) print('---------------') def userinputAndSend(self): msgSend = input('>>') self.sendMsg(2, msgSend) # 主循环,不断的接受消息发送消息 def mainloop(self): # 先发送客户名称 userName = '***' if len(sys.argv) > 1: userName = sys.argv[1].decode(sys.stdin.encoding) self.sendMsg(1,userName) while True: try: self.userinputAndSend() # print('reading...') msg = self.readMsg() if msg: msgType,msgBody= msg self.handleMsg(msgType,msgBody) except CloseSocketError: print('对方断开了连接,程序退出') return except IOError: print('对方断开了连接,程序退出') return #创建socket,指明协议 tcpCliSock = socket(AF_INET, SOCK_STREAM) #连接远程地址和端口 tcpCliSock.connect(ADDR) handler = ConnectionHandler(tcpCliSock) handler.mainloop() ```