程序设计十二:网络编程
1.Manager类
使用Manager
类实现服务器功能:
_recv()
内部方法接收消息,
_broadcast()
方法向所有用户广播,
_private_chat()
方法发送私信(定向转发),
chat()
方法实现收发消息的完整过程,并实现所有聊天记录的保存。
class Manager:
def __init__(self, conn, addr, username=''): # 实质是建立了一个客户端对象,addr从accept中获取
self.socket = conn
self.ip = addr[0]
self.port = addr[1]
self.identify = '{}-{}'.format(self.ip, self.port)
self.username = username
def _recv(self, conn): # 接收消息
while True:
data = conn.recv(BS).decode("utf-8")
if not data or data == '再见!':
break
else:
return data
def _broadcast(self, message, sender): # 发送广播
for identify in clients.keys():
if sender != usernames[identify]:
clients[identify].socket.send(('%s %s:%s' % (get_time(), sender, message)).encode("utf-8"))
def _private_chat(self, message, sender, receiver): # 发送私信
for identify in clients.keys():
if usernames[identify] == receiver:
clients[identify].socket.send(('%s %s[私信]:%s' % (get_time(), sender, message)).encode("utf-8"))
break
def chat(self): # 与客户端交互:收发消息
log = open('server_log.txt', 'a')
log.write('\n#### 服务器数据 ####\n')
print('{}尝试连接'.format(self.identify))
log.write('{}尝试连接\n'.format(self.identify))
name = self._recv(self.socket)
if not name:
return
self.username = name
clients[self.identify] = self # 客户列表中加入客户 { identify:client }
usernames[self.identify] = self.username
# 处理连接成功信息
print('用户 %s 已连接!' % self.username)
log.write('用户 %s 已连接!\n' % self.username)
self.socket.send("您已连接".encode("utf-8"))
user_connect_message = ('我已进入聊天室')
self._broadcast(user_connect_message, self.username)
# 开始聊天
while True:
message = self._recv(self.socket)
if not message:
break
elif message.split(' ')[0] == '@': # 私信
receiver = message.split(' ')[1]
self._private_chat(message, self.username, receiver)
print("%s (%s) %s private chat with %s: %s" %
(get_time(), self.identify, self.username, receiver, message))
log.write("%s (%s) %s private chat with %s: %s\n" %
(get_time(), self.identify, self.username, receiver, message))
else: # 广播
print("%s (%s) %s: %s" % (get_time(), self.identify, self.username, message))
log.write("%s (%s) %s: %s\n" % (get_time(), self.identify, self.username, message))
self._broadcast(message, self.username)
# 断开连接:信息发送、用户删除、连接终止
print("%s(%s) 断开连接" % (self.ip, self.port))
log.write("%s(%s) 断开连接\n" % (self.ip, self.port))
user_exit_message = ('再见!\n%s已退出聊天室' % self.username)
self._broadcast(user_exit_message, self.username)
clients.pop(self.identify)
usernames.pop(self.identify)
log.close()
self.socket.close()
2.Chatter类
Chatter
类实现用户端,send()
发送消息并写入日志,recv()
接收消息并写入日志。
class Chatter:
def __init__(self, conn):
self.socket = conn
def send(self):
username = input('用户名:')
self.socket.send(username.encode('utf-8'))
log.write('\n##### 用户 %s 聊天记录#####\n'%username)
while not exit_event.is_set():
message = input('')
log.write(message+'\n')
self.socket.send(message.encode('utf-8'))
if message == '再见!':
exit_event.set()
def recv(self):
while not exit_event.is_set():
message = self.socket.recv(BS).decode('utf-8')
print(message)
log.write(message+'\n')
3.服务器的实现
创建多个线程,实现用同一个端口服务多个用户,代码如下:
def main():
if len(sys.argv) != 3:
print('usage: python my_server.py ip port')
else:
ip = sys.argv[1]
port = int(sys.argv[2])
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((ip, port)) # 设置绑定监听的ip和port
server.listen(10)
print("服务器已开启,正在监听{}".format(server.getsockname()))
while True:
conn, addr = server.accept() # 接受客户端连接,获取其地址
client = Manager(conn, addr) # 创建客户
t = Thread(target=client.chat, args=())
t.start()
4.用户端的实现
每个用户端启用两个线程,分别处理接收和发送代码如下:
def main():
if len(sys.argv) != 3:
print('usage: python my_client.py ip port')
else:
ip = sys.argv[1]
port = int(sys.argv[2])
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client.connect((ip, port))
chat1 = Chatter(client)
chat2 = Chatter(client)
send_thread = Thread(target=chat1.send, args=())
recv_thread = Thread(target=chat2.recv, args=())
send_thread.start()
recv_thread.start()
send_thread.join()
recv_thread.join()
finally:
print("连接已被关闭")
client.close()
5.多机测试结果
启动多台电脑作为用户,连接服务器,构成聊天室,测试结果如下:
服务器窗口截图:
用户一窗口截图:
用户二窗口截图:
用户三窗口截图:
从上述结果可以看到:
- 每个人都可以向聊天室发送消息,消息会广播给除自己以外的其他人
- 用户之间可以发送私信,私信格式为
@ <receiver> <message>
且私信不会被其他人看到
- 用户发送
'再见!'
时会自动退出聊天室,此时聊天室会发送广播告知用户已经退出
6.聊天记录保存
用户退出聊天室之后,会自动生成'client_log.txt'
文件,保存该用户的聊天记录,上述跨机演示结束后,自动保存的聊天记录如下:
用户1
用户2
同时,服务器也会保存所用的聊天数据,并按照用户分类储存,截图如下: