涂笑语, 王美玉, 李 毅, 朱友华
(南通大学信息科学技术学院,江苏南通226019)
Linux系统作为自由、开源的类Unix 操作系统,广泛应用于个人计算机、服务器以及各种嵌入式终端设备。它的优势在于具有开放性、多用户、多任务、丰富的网络功能、可靠的系统安全等。用户可以对加入革奴计划(GNU’s Not Unix,GNU)的软件进行任意使用或者修改源代码。目前,国内外有各式各样界面友好、功能齐全的即时通信软件,但是它们都是基于Windows、Android和iOS系统,或者只是功能较局限的Linux版本,且不是开源的。另外,不同商业软件使用各自自有的即时通信协议,互相之间无法通信,这也不利于即时通信软件的发展。设计一个基于Linux 的开源即时通信系统十分有必要。
传输控制协议(Transmission Control Protocol,TCP)的特点是面向连接且相对可靠[1]。通过该协议传输数据时,首先要进行3 次握手来建立服务器与客户端之间的连接;为保证数据完整性和准确性,如数据验证结果与原来不符,则需要重传数据;若数据传输顺利完成,则需通过4 次挥手来关闭连接[2]。TCP 适用于传输大量数据且对可靠性要求较高的情况,其缺点是开销较大、速度较慢[3]。
本设计采用基于TCP 的C / S 架构[4]。C / S 架构模式是非对称的,核心是数据库服务[5]。它将连接在网络中的多台终端组成一个整体,客户端和服务器分别负责完成不同的功能。服务器用来响应客户端的请求,针对不同的请求类型为其提供对应的服务;客户端根据自己的需求,向服务器发出不同的请求[6]。用户A、B之间的信息传输通过服务器进行中转。C / S架构的基本工作原理如图1 所示。
图1 C/ S架构基本工作原理
目前市面上常见的即时通信软件设计思路大致如图2 所示[7]。首先打开应用程序,显示主界面,一般都会有注册账户、登录和退出这3 个基本功能。注册时,无论成功与否,系统均会有提示。登录时,如果用户名和密码匹配且正确,则可以成功登录。登录成功后,系统会根据不同的用户身份显示对应的功能界面。在本系统设计中,管理员用户有独享功能。
图2 设计思路
在服务器模块中需要使用数据库来存放用户注册的用户名、密码以及聊天记录。系统设计过程中,使用SQLite的编程接口函数来实现创建表、添加数据、更新数据和查询数据这4 个操作[8]。数据库打开函数要和关闭函数成对使用,因为如果数据库没有关闭,则其他程序无法使用该数据库。而且数据库的连接数有上限,如果有足够数量的程序在关闭之前没有关闭数据库,则可能导致数据库系统崩溃。
本次设计需要创建的表名为user,有4 个字段,存放的数据均为TEXT类型。第1 个字段username存放用户名,第2 个字段password 存放密码,第3 个字段to_name存放发送信息时接收方的用户名,第4 个字段record存放具体的聊天内容。user 表的结构如表1所示。
表1 user表的结构
在传输数据时,客户端与服务器之间需要同时遵守一个协议来规范各个模块之间的通信。本系统的设计中,定义了一个结构体来规范通信时传输的所有数据类型。具体定义如下。
struct msg
{
int action;
char name[30];
char to_name[30];
char from_name[30];
char password[30];
char message[1024];
char online_name[30][30];
int row;
int column;
char record[100][100];
};
对于上面的结构体,action 中存放的是命令号,不同的命令号对应不同的指令;char类型的name数组存放用户名,限定长度为30;接下来分别是接收方用户名、发送方用户名、密码和消息内容;online_name 是一个二维数组,存放在线用户名;最后3 个参数是消息记录的行数、列数、具体消息记录,这里限定最多查询100 条消息记录。客户端与服务器之间进行通信时,收发的信息均以结构体的形式进行传输。客户端或者服务器收到该结构体后按照具体功能需求获取所需的数据。
命令号action也需要客户端和服务器采用同一种规范,双方可以正确识别命令,然后去执行对应的函数,实现相应的功能。具体的命令号协议见表2。
表2 命令号协议
Linux系统中的网络编程是通过socket 编程接口来实现的[9]。设计中采用了基于TCP 的流式套接字类型,使用socket库中的sockaddr_in 数据结构来存储IP地址和端口号。编程过程中考虑到不同终端存储数据时有大端模式和小端模式之分,还涉及到对网络地址的字节序转换。基于TCP 的C / S 架构中服务器的搭建流程如图3 所示[10-11]。
图3 服务器端的搭建流程
服务器端需要先打开数据库,然后按照流程搭建服务器。服务器端搭建流程中用到了两种套接字,第一种是socket()函数新建的套接字,listen()函数用来监听该套接字;如果有客户端请求连接,则新建一个通信套接字来专门用来处理与该客户端的连接。需要注意的是,在执行listen()函数之后要创建多线程,主线程负责监听客户端的请求,每当有一个客户端请求连接就需要新建一个线程来专门处理与该客户端的通信[12-13]。accept()函数是一个阻塞型函数,如果没有客户端请求连接,则一直阻塞。客户端与服务器连接成功后,负责通信的线程会执行receive_msg()函数。该函数内部会用read()函数读取命令代号,根据命令号分别执行不同的函数,实现不同的功能,将对应的信息返回给目标客户端。服务器端的执行流程如图4所示。
图4 服务器端的执行流程
(1)注册reg()。收到注册请求后,先在数据库中查找想要注册的用户名,如果该用户名未被注册,则将用户名和密码插入数据库中,返回注册成功代号;如果用户名已被注册,则返回错误号。
(2)登录log_in()。收到登录请求后,检查该用户是否已登录、是否已在线、用户名和密码是否匹配,检查用户身份,向客户端返回身份代号,将该用户插入在线用户列表。
(3)群聊chat-all()。将用户想要发送的信息保存到数据库的聊天记录字段,给当前所有在线用户发送该信息。
(4)私聊chat_ private()。检查目标用户是否存在、是否在线,如果在线,保存聊天记录,向该用户发送信息。
(5)修改密码modify_password()。使用update命令在数据库中将当前用户的password字段更新为想要修改的密码。
(6)查看聊天记录chat_record()。使用select命令去数据库中查询记录,只要username 或者to_name中有任意一个字段与当前用户名匹配,即为当前用户的聊天记录,将其返回客户端即可。
(7)踢人kick_out()。检查目标用户是否存在、是否在线,如果在线,将其通信套接字置零并移出在线列表。
(8)禁言与解禁banned()/ unbanned()。该功能主要在客户端实现,服务器只需返回标志位。
此外,C / S架构中客户端的搭建流程如图5 所示。
图5 客户端的搭建流程
客户端运行之后会显示主页面,可以选择注册、登录或者退出,系统会根据输入的命令号的不同对应执行不同的函数。如果注册成功,则系统会在服务器端的数据库中添加用户名、密码等相关信息。如果用户登录时输入的用户名和密码均存在且与服务器端的数据库匹配,则可以登录成功。登录成功之后,客户端会利用多线程技术进行读写分离,即主线程函数根据用户身份显示对应功能界面,根据用户输入的命令专门负责向服务器发送消息,新建线程专门负责从服务器接收消息,根据服务器返回的命令号去解析消息结构体,从中获取所需的数据,显示该命令对应功能所需的信息。客户端的执行流程如图6 所示。
(1)注册reg()。从键盘读取用户输入的用户名和密码,如果符合要求,则发送给服务器,等待服务器返回注册状态。
(2)登录log_in()。输入用户名和密码,发送给服务器,等待服务器返回登录状态。
(3)向服务器发消息write_to_server()。主线程负责执行该函数,根据不同用户身份,系统显示不同的功能界面。对于普通用户,主要功能有:查看在线用户、群聊、私聊、修改密码、查看聊天记录和退出登录。对于管理员用户,新增了踢人、禁言和解禁的功能。用户输入不同的命令号,执行相应的函数。
图6 客户端的执行流程
(4)从服务器读消息read_from_server()。新建的线程负责执行该函数,根据从服务器端返回的命令号的不同,解析消息结构体中的数据并显示出对应的提示信息。如客户端收到的是群聊或者私聊命令,显示消息结构体中的文本信息并提示用户消息类型。如客户端收到的是踢人命令,表示当前登录的用户被管理员踢出聊天室,调用函数退出程序。禁言和解禁功能主要在客户端的代码中实现,首先定义一个全局变量flag作为标志位,默认为“0”,表示未被禁言。当客户端收到禁言命令时,将flag 置为“1”,表示当前用户被禁言。此外,还要在write_to_server()函数中设置条件,发送群聊和私聊请求时如遇到flag 为“1”就不会向服务器发消息,在用户界面提示已被禁言,这样就实现了禁言功能。解禁功能编程思路与禁言一致,只需要将标志位flag重新置为“0”[14]。
本实验设计了一个基于Linux 平台的即时通信系统,实现了注册、登录、查看聊天记录、群聊、私聊、修改密码、查看聊天记录等基本功能,新增管理员用户独享踢人、禁言和解禁功能。经过测试,系统实现了预期的功能,满足通信需求,而且系统运行稳定,具有一定的实用性。同时还可对系统进行图形化界面设计、引入数据传输的加密算法和实现与外部网络的连接等进一步优化操作[15-16]。