C/S架构的网络聊天工具OC

1 分钟读完

网络编程的一个大作业《编写网络聊天工具》,拿出来和大家分享一下实现思路。

1.简介

模仿QQ来写的一个工具,使用Python+PyQt开发,为快速实现网络聊天工具的基本逻辑,服务器端没有实用数据库,客户端有登录窗口、注册窗口、好友列表窗口和对话框。主要功能包括:

  • 注册、登录;
  • 添加好友;
  • 支持多会话;
  • 支持好友上线、下线通知;
  • 一对一聊天,支持离线消息;
  • 文件传输,支持离线文件;

2.服务器设计

2.1. 用户管理模块

为实现一个网络聊天工具的基本业务逻辑,在基本要求和扩展要求的基础上,添加用户注册和登录验证功能。服务器需要保存用户注册信息,一般会采用数据 库,本实验为确保程序运行环境的灵活性(作为一实验,简洁为上,实际上用数据库更方便),将用户注册信息保存在文件server\user \user.pk中。密码在传输过程中和在服务器保存的形式均为MD5密文,提高了安全性。User.pk文件的内容其实一个字典,如下所示:

users={user_no_1: [email, nickname, password_md5], user_no_2: [email, nickname, password_md5]….}

user_no_n就是在注册时随机生成的一个6位的整数作为唯一标识OC号(类似QQ号,本实验中用于标识唯一用户的数字串),此处没有添加多余的字段,只有用户邮箱、昵称和密码。

另外一个重要的文件server\user\friends.pk用于保存好友关系,用于字典和列表实现,格式如下:

friends={user_no_0: [user_no_0,user_no_1, user_no_2,…], user_no_1: [user_no_0,user_no_1, user_no_3,…]…}

列表friends[user_no_n]保存的是用户user_no_n的所有好友的OC号。

至于在线用户,服务器会在内存中存储一个onlineUser列表,保存在线用户的OC号。

2.2. 聊天消息存储模块

服务器在转发聊天消息的同时将聊天记录保存到磁盘上,由于实验中数据比较少,也不采用数据库存储,并且所有的聊天记录保存在一个文件server\msg\history.pk中,每一条聊天记录的格式如下所示:

send_user_no \t recv_user_no \t cur_time \ t msg_content \n

如果接收方不在线,那么将消息保存到离线消息文件server\msg\offline.pk中,格式如下:

send_user_no \t recv_user_no \t cur_time \ t msg_content \n

待接收方上线后,将离线消息发送给他,并且将该离线消息从offline.pk文件移动到history.pk文件中。

2.3. 文件存储模块

所有的上传文件保存的server\file\文件夹中,在该文件夹下有一个目录文件file_info.txt,用于保存所有的文件信息,格式如下:

send_user_no \t recv_user_no \t upload_time \ t file_name\n

在上传成功后,如果接收方在线,服务器向接收方发送提示消息,接收方随时可以下载,如果接收主不在线,等接收方上线后会进行提醒。此外,在两个好友A和B会话期间,对话框右侧的文件列表中会显示所有可下载的文件(准确的说是两人进行传送过的所有历史文件),可以随时下载。

2.4. 服务器消息转发

为实现消息的快速转发,服务器动态维护一个字典sessions,建立会话和关闭会话时,会动态增删。格式如下:

sessions={user_no_1: {user_no_2: socket,.. }, user_no_2: {user_no_1: socket,.. }…}

sessions[user_no_x][user_no_y]表示user_no_x和服务器建立的一个socket会话,而这个会话的消息是发 送给用户user_no_y的,服务器从sessions[user_no_x][user_no_y]获取消息时,转发给 sessions[user_no_y]user_no_x

2.5.服务器向客户端推送消息

服务器大多是在响应客户端的请求,实验中所有的socket都是由客户端发起连接,但是有时候服务器也需要向客户端主动发送消息,所以服务器需要为每个客 户端保存一个socket,用于推送消息(包括新消息提醒、离线,以及上线/下线提醒)。为实现该功能,服务器实时维护一个socket列表 cmd_sock,保存在线用户的接收信息的socket.

2.6.请求报文的格式

这是服务器设计中最重要一环,为实现客户端和服务器的通信,双方必须约定一个格式(也就是协议),为了实现简单,容易理解,使用python中的 struct.pack和struct.unpack,每个报文TCP传输的数据(也就是应用层部分)包括四个字段:

FIELD-1 FIELD-2 FIELD-3 FIELD-4
FIELD-1标识报文类型,FIELD-2~4根据FIELD-1有不同的含义。
FIELD-1 FIELD-2 FIELD-3 FIELD-4 含义
SIGN_IN email Nickname Pwd_md5 注册新用户
LOGIN User_no Pwd_md5 登录
DOWN User_no 下线通知
CMD_SOCKET User_no 标识此socket用于接收推送消息
GET_FRIENDS User_no 获取自己的好友列表
GET_ONLINE User_no 获取目前在线的用户
FIND_FRIEND User_no 根据oc_no查询一个用户的信息
ADD_FRIEND User_no Friend_no 添加好友
OFFLINE_MSG User_no 查询有没有自己的离线消息
NEW_SESSION User_no Friend_no 建立会话请求
MESG User_no Friend_no Msg 发送消息
UPLOAD User_no Friend_no File_name 上传文件请求
UPLOAD Data_length File_name File_content 上传文件内容
UPLOAD_FINISH User_no Friend_no File_name 上传结束
DOWN_FILE_NAME User_no Friend_no 获取可下载的文件列表
DOWNLOAD File_name Recv_state Recvd_bytes 下载文件

2.7.服务器的I/O模型

采用select模型实现I/O多路复用,使用队列Queue实现消息发送,具体参见server\server.py,主体框架代码并不多。

3.客户端设计

上一节已经详细介绍了请求的各种类型,客户端如果想得到某些信息,只需要按相应的数据报文类型格式进行请求,并解析服务器响应即可。这时客户端只剩下界面设计部分。

3.1. 流程图

3.2. 界面设计

使用QT Designer设计了四个界面login.ui, register.ui, main.ui, chat.ui ,使用pyuic4命令生成对应的py文件,稍微修改。Client\main.py是主要的界面和逻辑实现部分,client\net.py是 main.py用到的一些辅助的函数。




3.3. 聊天对话框

启动聊天对话框后,首先从服务器获取可下载的文件列表,如果有离线消息的话,也是从主程序传过来的,在此不用查询。

3.4.文件上传与下载

服务器设计的相应报文格式:

1 UPLOAD User_no Friend_no File_name 上传文件请求
2 UPLOAD Data_length File_name File_content 上传文件内容
3 UPLOAD_FINISH User_no Friend_no File_name 上传结束
4 DOWN_FILE_NAME User_no Friend_no 获取可下载的文件列表
5 DOWNLOAD File_name Recv_state Recvd_bytes 下载文件

报文1是通知服务器有文件即将上传,服务器如果收到就返回一个“RECV_NOTICE”,客户端收到之后才开始传送文件内容;

报文2是用来传送文件内容的,它告诉服务器传送的文件名称和传送的字节数,服务器正确收到后响应一个“RECV_OK”;

报文3是在客户端上传结束时,发送该报文通知服务器结束;

报文4是客户端告诉服务器要下载的文件名;

报文5是从服务器下载文件时,服务器每发送一次文件内容,客户端响应一个该报文,它标识了下载的文件名,已经接收的字节数,服务器根据已经接收的字节数 ,可以直接定位到文件的准确位置,避免了在传送过程中一直打开文件,或者一次性将文件内容读入内存。

4.总结

实现了一个简易逻辑完整的网络聊天工具,聊天室部分由于时间关系没有实现,不过实现思路和两人聊天相同,界面需要重新设计一个,还有共享文件等。本工具的消息提示方式不够人性化,是弹窗方式,不知道为什么,PyQt的系统托盘消息提示在win7下不好使。

附:

完整代码见: https://github.com/onestraw/OC

留下评论