4.1.2 服务器协议

既然我们将要介绍的多任务服务器方法与特定的客户/服务器协议相互独立,我们希望能够实现一个同时满足两者的协议。EchoProtocol中给出了回显协议的代码。这个类的静态方法handleEchoClient()中封装了对每个客户端的处理过程。除添加了写日志功能(下面马上会对其介绍)外,这段代码与TCPEchoServer.java中的连接处理部分几乎完全一致。该方法的参数是客户端Socket实例和Logger实例的引用。

EchoProtocol类实现了Runnable接口(run()方法只是根据该实例的Socket和Logger引用,简单地调用handleEchoClient()方法),因此我们可以创建一个独立执行run()方法的线程。另外,服务器端的协议执行过程可以通过直接调用这个静态方法实现(为其传入Socket和Logger实例的引用)。

EchoProtocol.java

figure_0090_0107

figure_0090_0108

1.声明实现Runnable接口:第7行

2.类成员变量和构造函数:第8~15行

每个EchoProtocol实例都包含了一个相应连接的套接字和对logger实例的引用。

3.handleEchoClient():第17~43行

实现回显协议:

·从套接字中获取输入/输出流:第20~21行

·接收和回显:第25~30行

循环执行直到连接关闭(由read()方法返回值为-1指示),每次循环中在接收到数据后就立即写回。

·在日志中记录连续的详细信息:第32~33行

同时记录远端的SocketAddress和回显的字节数。

·异常处理:第36行

将异常写入日志。

你的服务器每分钟将执行上千次客户端请求。现在有用户报告了一个问题。那么如何才能确定到底发生了什么呢?是不是服务器的问题呢?也许是客户端在破坏这个协议。为了处理这种情况,大部分的服务器都将它们的活动记录写入日志。这里我们只对写日志作非常基础的介绍,不过,你得知道还存在更多日志记录功能以满足企业级需求。

首先我们介绍Logger类,它代表了本地或远端的一个日志记录工具。通过该类的一个实例,我们就可以记录服务器的各种活动信息,就像在EchoProtocol中演示的那样。或许你会在服务器上使用多个日志记录器(logger),每个记录器以不同的方式负责不同的需求。例如,你可以有不同的日志记录器分别负责记录操作、安全和出错消息。在Java中,每个日志记录器由一个全局唯一的名字识别。按照如下代码调用Logger.getLogger()静态工厂方法即可获取一个Logger实例:

Logger logger=Logger. getLogger(“practical”);

这行代码用于获取名字为“practical”的记录器。如果这个名字的记录器不存在,则用这个名字创建一个新的记录器,否则,返回已存在的记录器实例。无论在程序中获取名字为“practical”的记录器多少次,都将返回同一个实例。

有了日志记录器,需要记录什么内容呢?这取决于你要做什么。如果服务器在正常地运行,你可能不想将服务器执行的每一步都记录到日志中,因为记录日志是要消耗系统资源的,如为日志项分配存储空间,写日志需要占用服务器的处理时间等。另一方面,如果是为了调试,你可能就希望记录服务器执行的每一步。为了对不同情况进行处理,记录器中通常包含了日志项的等级或严格度的概念。Level类中就封装了消息的重要程度信息。每个Logger实例都有一个当前等级,每条日志消息也有对应的等级,低于Logger实例的当前等级的消息将被抛弃(即不写入日志)。每个等级都有一个对应的整数值,因此等级之间可以相互比较和排序。系统识别的Level实例共有7种,同时还能创建用户定义的等级,不过通常没有必要这样做。内置的等级(定义为Level类的静态字段)包括:severe,warning,info,config,fine,finer和finest。

当你写日志的时候,消息又去哪儿呢?记录器将消息发送到一个或多个Handler实例中,该实例用来发布这些消息。默认情况,每个logger有一个ConsoleHandler用来将消息打印到System.err中。你也可以为一个logger改变或添加handler(如FileHandler)。注意,与logger一样,每个handler也有自己的最小日志等级,因此要发布一条消息,它的等级必须同时高于logger和handler的等级阈值。logger和handler的可配置性很高,包括他们的最小等级。

Logger对我们来说一个重要的特征是它是线程安全的(thread-safe),即可以在并行运行的不同线程中调用它的方法,而不需要在调用者中添加额外的同步措施。如果没有这个特征,由不同线程记录的不同消息将错乱无章地写到日志中。

Logger:查找/创建

figure_0092_0109

这些静态工厂方法返回指定名字的Logger实例,必要时创建新的实例。

一旦有了logger,我就需要做的就是……写日志。Logger提供了从细粒度到粗粒度的日志记录工具,并能够区分消息的不同等级甚至消息的上下文。

Logger:记录日志消息

figure_0092_0110

figure_0093_0111

severe(),warning()等方法根据其名字对应等级,将给定的符合等级的消息写入日志。entering()和exiting()方法在程序进入或退出给定类的指定方法时将记录写入日志。注意你也可以选择性地指定额外信息,如参数和返回值等。throwing()方法用于将指定方法所抛出的异常信息写入日志。log()方法提供了一种一般性的日志记录方式,该方法可以指定需要记录的消息等级和内容,并可以选择性地添加异常信息。当然还存在很多其他记录日志的方法,这里我们只提到了主要的几种。

我们可能希望能够通过设置最小记录等级或日志消息处理器的等级,来定制自己的记录器。

Logger:设置/获取日志的等级和处理器

figure_0093_0112

getHandlers()方法返回一个包含了和该记录器关联的所有日志处理器数组。addHandler()和removeHandler()方法用于给该记录器添加或删除日志处理器。getLevel()和setLevel()方法用于获取和设置日志记录的最小等级。isLoggable()方法则在该记录器能够记录所给定的等级的日志时返回true。

现在我们准备介绍一些不同的方法来实现并行服务器。