13.3 实现参考(在线用户列表的维护)
在服务器端创建了一个Hash Map容器后,每当有客户端与服务器端建立了网络连接时,就将该客户端的用户名和socket这一键值对存入容器。
当有一个客户端上线时,就可以从容器中得到所有当前在线用户的用户名(容器中所有的键)。先将这些用户名发送给新上线的客户端,加入新上线客户端的用户下拉列表中,然后在容器中获得所有当前在线用户的socket(容器中所有的值),通过这些socket将当前新上线的用户名发送给所有已经在线的用户,从而把新上线用户名加入其他所有在线用户的用户下拉列表中。
同理,当一个客户端下线时,就可以把下线用户名发送给所有在线用户,从而所有在线用户将下线用户名从用户下拉列表中去除。
服务器端对聊天内容的转发方法:先在容器中取得发送目的方的socket,然后通过这个socket将聊天内容转发过去。
客户端程序的Login类、Login Face类不变。
【代码13.2】Talk Face类
代码13.2
服务器端发送给客户端的信息可能是用户上线通知、用户下线通知、聊天信息,所有信息本身要附带功能字符串,以说明信息种类。
服务器端发送的信息格式:①online@上线用户名;②offline@下线用户名;③talk@聊天对象用户名@聊天内容。信息字符串用“@”分割为不同部分,第0部分是功能字符串。
代码13.2的第33行到第66行是客户端启动接收服务器端信息的线程。当接收到服务器端发来的字符串(见第39行)时,先将字符串用“@”进行分割,得到的第0部分的功能字符串有3种可能,分别是“online”、“offline”、“talk”。如果是“online”,则将之后的用户名字符串加入用户下拉列表中(见代码13.2的第45行到46行);如果是“offline”,则将之后的用户名字符串从用户下拉列表中删除(见代码13.2的第48行到49行);如果是“talk”,则将之后的信息来源用户名和聊天信息输出在界面聊天框中,并存入聊天历史磁盘文件(见第51行到第60行)。
当前客户端关闭窗口下线时,向服务器发送下线通知字符串(见代码13.2的第156行)。
服务器端的程序如下。
代码13.3和13.4-1
代码13.3和13.4-2
代码13.3和13.4-3
【代码13.3】Server类
服务器端需要创建一个Hash Map容器,用来存储每个上线的客户端的用户名,以及该客户端与服务器端的网络连接对象socket。这个Hash Map容器在服务器端是唯一的,所有的服务器端线程共享这个容器,并且这个容器应该是在服务器启动的时候就创建的,为了要满足这些条件,Hash Map容器的username_socket对象被定义为静态变量(static),见代码13.3的第3行。因为被定义为static,所以usermame_socket对象在其他类中不需要创建Server类的对象,直接用Server.username_socket来引用。有关static的概念在第16章会讲到。
【代码13.4】Server Thread类
在代码13.4的Server Thread类中,服务器端接收由某客户端发来的字符串(见代码13.4的第17行),用“@”对字符串进行分割,如果第0部分是“login”的话,就进行登录检查(见代码13.4的第26行,这里暂时让所有的用户都通过登录检查)。
首先进行登录,登录成功后,第一,在Server.username_socket容器中取得所有在线用户的用户名,将在线用户名逐个发送给此新上线的用户(见代码13.4的第30行到第32行)。这里注意,容器username_socket是静态变量,用类名来引用;第二,在Server.username_socket容器中取得所有在线用户和服务器的网络连接对象socket,在每个socket上创建输出流,通过该输出流将当前新上线的用户名发送给每个在线用户(见代码13.4的第34行到第38行);第三,将当前新上线的用户名和socket加入Server.username_socket容器。这里的第三步必须在第一步和第二步之后,请自行解释原因。然后进行聊天,这里将聊天的功能单独写入一个方法talk中。
talk方法是服务器端与一个客户端进行交互的处理。如果收到客户端下线信息,则先将当前下线用户的键值对从Server.username_socket容器中删除。然后将当前下线用户名发送给所有其他在线用户(见代码13.4的第73行到第80行)。如果收到客户端聊天信息,则通过目的用户名,在Server.username_socket容器中取得目的用户的socket,在此socket上创建输出流,通过此输出流,将信息来源用户名和聊天信息发送过去(见代码13.4的第84行到第92行)。服务器端对聊天信息的转发是循环的,直到该客户端下线为止(见代码13.4的第66行的循环,stop初值为false,在客户端下线的时候,赋值为true,见代码13.4的第83行)。
另外,当多客户端登录时,多个Server Thread线程对象会并发访问服务器端的username_socket对象(在代码13.3的第3行定义)。为了实现多线程同步,保障线性安全,在Server Thread类中增加两个新的函数定义。
将代码13.4中的第40行改为“add OnlineUser(username,socket);”,将第74行改为“removeOffline User(username);”。