APP下载

基于Linux系统的即时通信软件开发

2015-03-15史黎黎

无线电工程 2015年8期

史黎黎,牛 宾

(河北远东通信系统工程有限公司,河北 石家庄 050200)

基于Linux系统的即时通信软件开发

史黎黎,牛宾

(河北远东通信系统工程有限公司,河北 石家庄 050200)

摘要基于C语言开发环境,在Linux操作系统的基础上,对即时通信系统的实现流程进行了系统的研究与介绍。介绍了软件编程所用的模式,设计了即时通信软件所用的协议,通过有效状态图使服务器端和客户端的各个状态之间的关系清晰,主要对各个模块的实现方式进行了详细介绍,使软件实现了基本的注册、登陆、消息、列表、更新和下线等功能,初步满足了基本的聊天需求。通过运行、测试与分析,该软件运行稳定、可靠。

关键词LINUX系统;即时通信;C/S模型;网络编程

Development of Instant Communication Software Based on Linux System

SHI Li-li,NIU Bin

(HebeiFar-eastCommunicationSystemEngineeringCo.,Ltd,ShijiazhuangHebei050200,China)

AbstractThe implementation flow of instant communication system is systematically studied and introduced based on C language development environment and Linux-based operating system.The programming model of this software is introduced and the protocol used by instant communication software is designed.The relationship between all kinds of status of server and client is clearly displayed through the effective state diagram.The implement method of each model is described in detail in order to achieve such basic functions as registration,log on,message,listing,update and log off,and can meet preliminarily the requirement of chat.The results of operation,test and analysis show that the instant communication software is stable,reliable.

Key wordsLINUX system;instant communication;C/S model;network programming

0引言

随着网络技术的不断发展,即时通信在近几年取得突飞猛进的发展,越来越多的人将拥有更多功能和人性化界面的网络聊天软件,作为他们日常生活交流和通信的重要工具。即时通信软件具有互动性好、实时性及低成本等特点,成为了越来越普及的一个通信工具,它给人们的日常工作及生活带来了极大便利。

与此同时,Linux系统在嵌入式行业的潜力逐渐被发掘出来。目前在嵌入式行业,Linux系统[1,2]作为一个开源的操作系统被越来越多的人所应用,它的源码公开,便于学习与交流,可以运行于多种硬件平台之上,现已成为全球使用最多的一种UNIX类操作系统。但是对于大多数习惯Windows操作系统的人来说,Linux操作起来不够人性化、交互界面不够美观,这给Linux 操作系统的普及带来了很大的障碍。目前基于linux系统的聊天工具还很少,因此制作一个Linux 操作系统下的拥有人性化界面的实时通讯工具,将给那些Linux 操作系统下的用户带来极大方便。

1总体设计

1.1 即时通信体系结构

常用的即时通信体系结构有2种模式:客户机/服务器模式和P2P模式[3]。本文采用的是客户机/服务器模式进行开发。

即时通信客户机/服务器模型,简称C/S模型[4]。C/S模型是一种非对称式编程模式,它的基本思想是把集中在一起的应用从功能上划分成为2部分,分别在不同的计算机上运行,通过它们之间的分工合作来实现一个完整的应用功能。这就需要其中一部分作为即时通信服务器,用来响应即时通信客户机的服务请求,为即时通信客户机提供各种服务;另一部分则作为即时通信客户机程序,用来向即时通信服务器提出服务请求。即时通信C/S模式体系架构如图1所示。

图1 即时通信C/S模式体系架构

1.2 协议设计

为了规范在编程中服务器端和客户端各模块之间的多函数通信,现将各模块设计如下协议,在服务器端和客户端同时遵守。具体协议如表1所示。

表1 协议中的命令

本文采用的是以用户数据包(UDP)的方式传输登陆、下线、消息、列表、更新及注册等请求,所以将报文中可能包含的信息以协议的形式列表。在登录时,由客户端向服务器端发送请求登陆报文。报文的前2个字节中存储的是10,标志该报文是向服务器端请求登陆的。若后2个字节minor =10,说明紧随其后的字节是用来登陆服务器的ID号,且该报文由客户端向服务器端登陆;若minor =20,说明服务器的数据库中存在该ID号,服务器向客户端发送一个新的端口号和salt,新端口号的用途是处理登陆过程,salt是客户端用来进行MD5加密的,以便随后向服务器端发送密钥密文;若minor = 30,说明客户端正在向服务器端发送密码。服务器端校验正确后向客户端发送minor = 50的报文,否则发送minor = 60的报文。在客户端下线时,向服务器端发送major = 20,minor = 10 的报文,不必等待服务器的响应。服务器接到下线通知后,将该用户的状态更改为离线。当客户端向其他客户端发送消息时,请求端向服务器发送major = 40,minor = 10的报文,并且随后指定目的ID,源ID,和消息文本的数据包。注册新ID的过程与登录过程类似。

1.3 报文结构

根据表1可以设计出表示数据报结构体中的各个数据成员[5]。

∥程序后结构体中用到的宏

define PKT_LEN 1 500

#define CMD_LEN4

#define FILL_LEN(PKT_LEN- CMD_LEN)

#define SALT_LEN 9

#define ENCRYPE_LEN20

#define TEXT_LEN

∥数据报结构体

Struct st_packet{

∥主命令和副命令

uint16_t major;

uint16_t minor;

∥填充和数据部分

union {

∥起填充作用

uint8_t fill[FILL_LEN];

∥客户端登录时向服务器发送的用户ID

uint16_t id;

∥服务器回复客户端登陆,向其发送新端口号和salt

Struct_attribute_((_packed_)){

uint16_t port;

char salt[SALT_LEN];

}port_salt;

∥客户端向服务器端发送经MD5加密后的密文密码

char encrype[ENCRYPE_LEN];

∥登陆成功

uint8_t success;

∥登陆失败

uint8_t fail;

∥客户端向其他客户端发送消息,分别表示目的ID ,源ID,消息文本

struct_attribute_((__packed__)) {

uint16_t dest;

uint16_t src;

char text[TEXT_LEN];

}rcv_snd;

∥用来请求列表

uint16_t dest;

uint16_t src;

∥变长数组,用来响应客户端的列表请求,包含的所有在线用户的ID

uint16_t id_arr[0];

};

}__attribute__ ((__packed__));

将程序中用到的主命令以及各种状态列举在枚举中,以增加程序的可读性。

enum CMD_MAJOR {

CM_LOGIN=10, /*登陆请求*/

CM_DOWN=20,/*下线通知*/

CM_MESSAGE=40,/*发送消息请求*/

CM_LIST=50,/*列表请求*/

CM_UPDATE=60,/*更新*/

CM_REGIS=70,/*注册新用户请求*/

CM_ERROR=200,/*错误请求*/

};

相对于服务器来说的响应各个请求时,服务器所对应的状态。

enum STATUS {

ST_QUIT, /*退出状态 */

ST_ERROR,/* 错误状态 */

ST_RECC,/* 接受状态 */

ST_LOGIN,/* 登陆状态 */

ST_DOWN, /* 下线状态 */

ST_MESSAGE,/*收发消息状态 */

ST_LIST, /* 列表状态 */

ST_UPDATE,/* 更新状态 */

ST_REGIS /* 注册状态 */

};

1.4 服务器端状态

服务器端的每个状态之间的关系清晰明了,在服务器端模块之间不存在并发的关系。在main函数中初始状态设为接收状态,因为客户端不论发送何种请求,服务器端都要先接收然后才能在接收中解析。解析出客户端的请求后,再由接收状态返回到相应的处理状态,处理该请求完毕后又转到接收状态,继续等待接收客户端的下一次请求。

在服务器端的登陆模块中需要处理异步操作。在该模块中服务器端与客户端需要进行3次连续的往返通信,这一过程不能被其他客户端的请求打断。因此在服务器的登陆模块中需要创建子进程,在子进程中定义新的套接字描述符,并且将该套接字描述符与新的端口绑定。在子进程中使用新的端口与客户端进行独立的通信,处理登录过程。

1.5 客户端状态

客户端的状态分为4个异步的模块:① 更新模块,负责定时向客户端发送更新数据报,服务器端定时的接收该数据报,若超过一定时间后服务器还没有收到某个在线客户端的更新报文,则服务器端将其标记为离线状态;② 输入-发送模块,该模块负责实时等待客户输入信息,客户输入完毕后,该模块将数据包发送出去,然后继续等待客户的输入消息;③ 接收-输出模块,该模块负责异步的接收来自服务器端的数据报(主要为转发自其他客户端的聊天信息),并且将数据包中的有效数据显示到客户端的输出设备上;④ 列表模块,该模块负责每个一定时间向服务器发送一次请求列表数据包,然后服务器通过查询在线数据库中的在线用户端ID,将其打包发送给请求客户端,使每一个客户端的在线好友列表都是有效且是最新的。

由于4个模块是异步进行的,所以在本文中创建了3个子进程用来分别处理3个异步模块,父进程负责接收来自服务器的数据报。以多进程的形式保证了输入请求、接收消息、发送列表请求及更新请求的实现。

2设计实现

2.1 main函数的设计与实现

在main函数中主要实现了2个功能:① 套接字描述符fd的创建和套接字地址结构struct sockaddr_in myend[6]与服务器端的绑定。② 通过switch-case语句根据不同的状态在case部分实现相应函数的跳转。服务器的初始状态总是接受状态,在服务器端接收到数据包后,调用接收函数,接收函数解析完数据包的协议后返回给main函数相应的状态,main函数根据该状态再去调用相应的处理函数。从而实现的程序的模块化设计,这样使main函数简单明了,模块划分细致,易于分块编写、编译和调试。

2.2 接收状态的设计与实现

首先在main函数中,通过调用函数status=st_recv(&pkt_buf,&pkt_len,&srcend);得到了用户的数据包和套接字地址结构,因为数据包pkt_buf和套接字地址结构srcend是在main函数中定义的,实参传入结构指针能够回填相应的结构体,便于其他函数使用这2个参数。根据协议, 在接收状态通过分析数据包的MAJOR字段能够清晰地判断出该用户的意图是什么,然后通过switch-case语句返回给main函数与MAJOR相对应的状态,进而在main函数中调用相应的函数从而实现用户的意图。

2.3 收发消息状态的设计与实现

在接收状态解析出用户请求与其他用户通信后,main函数通过调用status=message(&pkt_buf,pkt_len,&srcend);函数,转去处理收发消息。模块首先根据请求用户数据包中的目的用户ID,通过函数data_bufp=database_find(head,pkt_buf->rcv_snd.src);查询在线数据库,获得目的用户的IP和端口。并且与创建目的套接字地址结构destend,进而利用系统调用函数sendto向目的用户发送数据包,从而实现了客户端与客户端的双向通信。

2.4 下线状态的设计与实现

根据协议,下线时客户端向服务器发送的数据包中只有主命令MAJOR和副命令MINOR。当接收到下线请求时,在下线处理函数中首先调用数据库中的函数id=sleekid(head,clientaddr->sin_port,clientaddr->sin_addr.s_addr);[7]。通过数据包中套接字地址结构字段中的IP和端口查找到下线用户的ID,然后通过ID调用函数database_delet(head,id);将该用户从在线数据库中删除。从而在服务器端实现相应用户的下线操作。

2.5 在线数据库的设计与实现

由于该聊天系统面向的是局域网内的用户,用户数量有限,所以在线数据库采用了易于操作的链表结构,相应的C文件为database.c。在该文件中主要实现了链表的创建DB*database_creat(void),链表的销毁void database_destroy(DB*start),对指定ID用户的信息查找struct data_st *database_find(DB*start,uint16_t id),新登录用户信息的插入int database_insert(DB*start,struct data_st *value),对指定ID的用户的删除void database_delet(DB*start,uint16_t id),通过指定的IP和端口对相应ID的用户的查找uint16_t sleekid(DB*start,uint16_t port,u_int32_t ip),对于链表的更新int database_update(DB*start,uint16_t id),在线的用户ID的列表int database_list(DB*start,uint16_t *arr)[8]等一系列操作,极大地方便了其他模块的编写。

2.6 更新状态的设计与实现

更新状态的作用是为了确定某个用户是否依然在线。每个客户端都会间隔一定时间向服务器端发送更新数据包,以使服务器确定其依然在线。如果超过特定时间后,服务器没有收到某个用户的更新数据包,服务器则将该用户的在线状态更改为离线状态,即从在线数据库中删除,此后其他用户不能再向该用户发送消息。

2.7 列表状态的设计与实现

列表状态的目的是为了使用户知道哪些用户在线,从而向其发送消息。在该模块中通过调用数据库中的函数database_update(head,packet->src);客户端的变长数组中返回在线用户的ID。

2.8 登陆与注册状态的设计与实现

登陆模块设计要考虑到并发情况,故设计起来较为复杂。在登陆模块中,客户端需要与服务器端往返3次发送数据包,而在这个过程中不希望其他的请求打断登陆状态,因此为登陆模块fork出子进程[9],使用子进程独立处理登陆过程。考虑到临时创建子进程系统开销会比较大,所以在设计中采用了进程池的概念。预先创建出10个子进程,并且允许最大的子进程数为20,以此来并发的处理多个客户端同时登陆。

登陆模块的详细设计如下,首先调用本文件中的函数salt=check_ID(packet->id);通过在注册文件中检查登陆ID是否存在,如果该ID存在,则给该客户端返回用于MD5加密[10]的salt。客户端输入密码,经过加密之后将密文发送给服务器端,服务器再在注册文件中检验该密码是否正确,如果正确则返回到接收状态,如果不正确则返回错误状态。

注册状态的设计与登陆状态的设计类似,在此不再赘述。

3结束语

通过对C/S技术以及Linux系统的深入探讨和研究,开发了一套适用于Liunx操作系统的功能较为简单的网络聊天软件,为Liunx系统使用者提供了更便利的沟通方式。

该软件实现了用户注册、登陆登出、更改在线状态、查看好友状态、发送接收即时消息等功能,基本实现了即时通信软件的功能。而且出于人性化考虑,该软件加入了并发执行程序,客户端的输出模块对文件及其在屏幕上的显示位置进行复杂的定位。总体来说,这款即时通信软件综合了各方面的考虑,基本完成了预期的目标。

参考文献

[1]李士东.开源Linux中IP协议栈的移植方法[J].无线电工程,2012,42(6):5-7,15.

[2]苑晓芳,刘志广.Linux下基于TCP传输组件的实现[J].无线电通信技术,2014,40(4):46-49.

[3]李红雨,华宇,于静.多信道环境下的P2P通信系统设计[J].无线电通信技术,2011,37(2):1-3.

[4]Richard.TCP/IP 详解卷1:协议[M].西安:机械工业出版社,2000.

[5]Richard.UNIX 环境高级编程(第2版)[M].北京:人民邮电出版社,2006.

[6]Randal E.深入理解计算机系统(第2版)[M].西安:机械工业出版社,2011.

[7]田泽.嵌入式系统开发[M].北京:北京航空航天大学出版社,2005.

[8]邹思轶.嵌入式linux设计与应用[M].北京:清华大学出版社,2002.

[9]Richard.UNIX 网络编程(第3版)[M].北京:清华大学出版社,2006.

[10]张明敏.图形图像文件格式解码使用程序[J].中国图像图形学报,1998(5):3-10

史黎黎女,(1983— ),助理工程师。主要研究方向:通信与网络。

牛宾男,(1984— ),助理工程师。主要研究方向:通信与网络。

作者简介

基金项目:国家部委基金资助项目。

收稿日期:2015-05-18

中图分类号TP391.4

文献标识码A

文章编号1003-3106(2015)08-0094-05

doi:10.3969/j.issn.1003-3106.2015.08.26

引用格式:史黎黎,牛 宾.基于Linux系统的即时通信软件开发[J].无线电工程,2015,45(8):94-98.