基于TCP协议的聊天私聊的实现
2012-08-06张钟
张钟
重庆理工大学 重庆 400054
0 前言
随着Internet的普及,作为网络通信的应用之一,网上聊天由于实时、快捷、方便,具有保护隐私的功能(私聊),而受到了现代人的喜爱。我们通常使用的聊天程序,按实现的功能可分为公聊和私聊二大类,私聊亦简称为悄悄话。网上聊天时用得较多的是私聊。
在计算机网络体系中,网络体系一般可分为5层,从上到下,第1层是:应用层;第2层是:传输层;第3层是:网络层;第4层是:链路层;第5层是:物理层。TCP协议位于其第2层的传输层中,TCP是可用于在不可靠的因特网上提供面向连接的、可靠的、有序的、无重复、全双工端到端的字节流通信的协议,通信过程就像打电话一样,可用于设计聊天室。在Windows系统中,对于Win32汇编语言而言,提供了WinSock API函数来支持TCP协议等,但在通信之前通信双方必须先建立一个通信对象,这就是套接字Socket,而用于TCP的套接字是流套接字SOCK_STREAM,TCP协议的工作方式采用的是客户端/服务器模式(Client/Server model),另外,WinSock API函数提供的TCP套接字的使用模式有两种:阻塞模式(同步模式)和非阻塞模式(异步模式),创建一个套接字时,默认工作方式是阻塞模式,如需要使用非阻塞模式,则可用WSAAsyncSelect函数再设置为非阻塞模式。因此,客户端和服务器端的通信套接字可以有四种工作方式组合:(1)客户端阻塞模式<——>服务器端阻塞模式;(2)客户端阻塞模式<——>服务器端非阻塞模式;(3)客户端非阻塞模式<——>服务器端阻塞模式;(4)客户端非阻塞模式<——>服务器端非阻塞模式。
1 聊天、私聊的实现思路
对于服务器端,当客户端与服务器端连接上了后,服务器端就会创建一个服务线程与客户端对应,接收客户端发来的登录数据包,其中包含有客户名和密码,服务器端将检查每个客户名和密码,当登录客户名和密码与已登录的客户名和密码都不同时,则存入客户端信息链表中,并回应客户端登录成功,紧接着将所有在线客户名信息发给客户端;否则,回应客户名或密码相同,断开连接。登录成功后,服务线程将客户信息放入消息队列,供其它服务线程取出发往客户端,告知某客户进入了聊天室;同样,当某客户端退出时,服务线程也将该客户信息放入消息队列,让其它服务线程取出发送到客户端,告知某客户离线了,同时删除客户端信息链表中该客户端的客户名和密码;另外,服务器端还会实时列出所有在线的客户名,实时监视聊天内容,对于不合适的聊天内容给予删除,实时跟踪登录和离线客户人数和客户名。服务器端每收到一条客户端发来的聊天信息或登录或退出信息,就对它进行编号,如第1条消息编号为1,第2条消息编号为2,……等等,然后放入消息队列中,供各服务线程提取发往对应的客户端,给消息编号在于方便各服务线程识别已发送过了的消息。每一个服务线程采用循环的方式,按消息的编号从小到大,从队列中取出消息向自己的客户端发送,如果已发送消息的编号大于消息队列中的消息的最大编号,则表示队列中的消息已发送完了,在发送消息的空闲时间,如客户端有聊天信息到达,则暂停发送消息,优先接收消息,并将消息放入消息队列中,以供其它的服务线程提取并发向客户端。此外,各服务线程在存、取消息队列的消息时需要保持线程间的同步,以防出错。
那么如何实现私聊呢?公聊时服务器端的服务线程从消息队列中取出消息发送给客户端就行了;而私聊则不同,它要求服务器端按指定的客户名进行有选择性的发送给客户端。因此,可以在服务线程中增加一个私聊客户名的检查,因为私聊时,客户端都会在上传的聊天数据包里,私聊客户名字段中,选定有私聊客户名,因此,服务线程每次从队列中取出一个聊天数据包时,总是把客户端的客户名(存储在服务线程中的会话信息局部结构体变量中,放在局部变量中可方便分散管理)与聊天数据包中的私聊客户名字段进行比较,如果相同,表示是私聊客户,应将聊天信息发往指定的客户端;否则,表示客户端不是私聊客户,不发送聊天信息,将聊天信息丢弃;如果聊天数据包中的私聊客户名字段是0,表示这是公聊,聊天信息应发往所有客户端(见图1)。
对于客户端,当与服务器端连接上后,立即发送登录数据包,如果登录回应数据包中字段dbResponse=0表示登录成功,dbResponse=1表示客户名或密码与在线客户相同,应重新选择客户名或密码;登录成功后,服务器端会将所有在线客户名发给客户端,客户端把在线客户名列出,以方便选择私聊客户;或当有客户离线后,服务器端也会将离线客户名发给客户端,客户端可实时将离线客户名删除,使客户端与服务器端保持同步。当客户端选择公聊时,一次只须向服务器端发一个聊天数据包,并将上传聊天数据包中的私聊客户名字段赋0;当客户端选择私聊时,选择了几个私聊客户名后,就循环发送几个上行聊天数据包,每一个数据包,都包含了惟一一个私聊客户名,在服务器端接收后,并判断客户名后,再转发给对应的私聊客户。
图1 服务器端服务线程私聊实现流程图(部分)
2 客户端与服务器端通信双方自定义的通信协议
由于TCP连接传输的是字节流,对传输的数据不进行边界保护,对数据包的发送可以组合或分割发送。因此,为了便于客户端和服务器端能正确地接收和发送数据包,需要自定义通信双方都能识别的数据包结构体和识别码,其实识别码就是数据包的标识,不同的数据包对应不同的识别码。比如客户端上传的数据包有登录数据包和聊天数据包,如果没有对应的识别码,那么服务器端就无法判断所接收的数据包是聊天数据包还是登录数据包,当然你可以选用其它的标志来标识各种数据包。每个数据包以定长数据包头 + 可变长度的数据包体组成,每个聊天数据包的总长度填入数据包头的数据包长度字段dwPacketLength中。客户端/服务器端实际的接收和发送数据包的长度,就是依据数据包的长度字段dwPacketLength中的值执行的。
; 聊天数据包,不等长数据包(上传,客户端——>服务器端)
3 服务器端服务线程自用的结构体
; 客户端会话信息结构
4 总结
本文从网络技术应用角度出发,介绍了聊天私聊的一种实现方法,用Win32汇编编程实现,并进行了测试,实现了聊天私聊的功能。由于篇幅的限制,所附程序代码在此省略。另外,实现私聊还有几种方法,比如说可将所有私聊用户名和聊天信息一次上传给服务器,再由服务器端逐个判断后,再将聊天信息发往各指定的私聊客户端等。
[1] 罗云彬编著. Windows环境下32位汇编语言程序设计(第2版).北京:电子工业出版社. 2006.
[2] 李媛媛编著.Visual C++网络通信开发入门与编程实践.北京:电子工业出版社.2008.