3.7 关闭服务器
前面介绍的EchoServer服务器都无法关闭自身,只有依靠操作系统来强行终止服务器程序。这种强行终止服务器程序的方式尽管简单方便,但是会
导致服务器中正在执行的任务被突然中断。如果服务器处理的任务不是非常重要,允许随时中断,则可以依靠操作系统来强行终止服务器程序;如果服务器处理的任
务非常重要,不允许被突然中断,则应该由服务器自身在恰当的时刻关闭自己。
本节介绍的EchoServer服务器就具有关闭自己的功能。它除了在8000端口监听普通客户程序EchoClient的连接外,还会在8001
端口监听管理程序AdminClient的连接。当EchoServer服务器在8001端口接收到了AdminClient发送的“shutdown”
命令时,EchoServer就会开始关闭服务器,它不会再接收任何新的EchoClient进程的连接请求,对于那些已经接收但是还没有处理的客户连
接,则会丢弃与该客户的通信任务,而不会把通信任务加入到线程池的工作队列中。另外,EchoServer会等到线程池把当前工作队列中的所有任务执行
完,才结束程序。
如例程3-10所示是EchoServer的源程序,其中关闭服务器的任务是由shutdown- Thread线程来负责的。
例程3-10 EchoServer.java(具有关闭服务器的功能)
package multithread4;
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class EchoServer {
private int port=8000;
private ServerSocket serverSocket;
private ExecutorService executorService; //线程池
private final int POOL_SIZE=4; //单个CPU时线程池中工作线程的数目
private int portForShutdown=8001; //用于监听关闭服务器命令的
端口
private ServerSocket serverSocketForShutdown;
private boolean isShutdown=false; //服务器是否已经关闭
private Thread shutdownThread=new Thread(){ //负责关闭服务器的线程
public void start(){
this.setDaemon(true); //设置为守护线程(
也称为后台线程)
super.start();
}
public void run(){
while (!isShutdown) {
Socket socketForShutdown=null;
try {
socketForShutdown= serverSocketForShutdown.accept();
BufferedReader br = new BufferedReader(
new InputStreamReader(socketForShutdown.getInputStream()));
String command=br.readLine();
if(command.equals("shutdown")){
long beginTime=System.currentTimeMillis();
socketForShutdown.getOutputStream().write("服务器正在关闭\r\n".getBytes());
isShutdown=true;
//请求关闭线程池
//线程池不再接收新的任务,但是会继续执行完工作队列中现有的任务
executorService.shutdown();
//等待关闭线程池,每次等待的超时时间为30秒
while(!executorService.isTerminated())
executorService.awaitTermination(30,TimeUnit.SECONDS);
serverSocket.close(); //关闭与EchoClient客户通信的ServerSocket
long endTime=System.currentTimeMillis();
socketForShutdown.getOutputStream().write(("服务器已经关闭,"+
"关闭服务器用了"+(endTime-beginTime)+"毫秒\r\n").getBytes());
socketForShutdown.close();
serverSocketForShutdown.close();
}else{
socketForShutdown.getOutputStream().write("错误的命令\r\n".getBytes());
socketForShutdown.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
};
public EchoServer() throws IOException {
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(60000); //设定等待客户连接的超过时间为60秒
serverSocketForShutdown = new ServerSocket(portForShutdown);
//创建线程池
executorService= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * POOL_SIZE);
shutdownThread.start(); //启动负责关闭服务器的线程
System.out.println("服务器启动");
}
public void service() {
while (!isShutdown) {
Socket socket=null;
try {
socket = serverSocket.accept();
//可能会抛出SocketTimeoutException和SocketException
socket.setSoTimeout(60000); //把等待客户发送数据的超时时间设为60秒
executorService.execute(new Handler(socket));
//可能会抛出RejectedExecutionException
}catch(SocketTimeoutException e){
//不必处理等待客户连接时出现的超时异常
}catch(RejectedExecutionException e){
try{
if(socket!=null)socket.close();
}catch(IOException x){}
return;
}catch(SocketException e) {
//如果是由于在执行serverSocket.accept()方法时,
//ServerSocket被ShutdownThread线程关闭而导致的异常,就退出service()方法
if(e.getMessage().indexOf("socket closed")!=-1)return;
}catch(IOException e) {
e.printStackTrace();
}
}
}
public static void main(String args[])throws IOException {
new EchoServer().service();
}
}
/** 负责与单个客户通信的任务,代码与3.6.1节的例程3-5的Handler类相同 */
class Handler implements Runnable{…}
|
shutdownThread线程负责关闭服务器。它一直监听8001端口,如果接收到了AdminClient发送的“shutdown”命令,
就把isShutdown变量设为true。shutdownThread线程接着执行executorService.shutdown()方法,该方
法请求关闭线程池,线程池将不再接收新的任务,但是会继续执行完工作队列中现有的任务。shutdownThread线程接着等待线程池关闭:
while(!executorService.isTerminated())
executorService.awaitTermination(30,TimeUnit.SECONDS); //等待30秒
|
当线程池的工作队列中的所有任务执行完毕,executorService.isTerminated()方法就会返回true。
shutdownThread线程接着关闭监听8000端口的ServerSocket,最后再关闭监听8001端口的ServerSocket。
shutdownThread线程在执行上述代码时,主线程正在执行EchoServer的service()方法。
shutdownThread线程一系列操作会对主线程造成以下影响。
◆如果shutdownThread线程已经把isShutdown变量设为true,而主线程正准备执行service()方法的下一轮while(!isShutdown){…}循环时,由于isShutdown变量为true,就会退出循环。
◆如果shutdownThread线程已经执行了监听8 000端口的ServerSocket的close()方法,而主线程正在执行该
ServerSocket的accept()方法,那么该方法会抛出SocketException。EchoServer的service()方法捕获
了该异常,在异常处理代码块中退出service()方法。
◆如果shutdownThread线程已经执行了executorService.shutdown()方法,而主线程正在执行
executorService.execute(…)方法,那么该方法会抛出Rejected- ExecutionException。
EchoServer的service()方法捕获了该异常,在异常处理代码块中退出service()方法。
◆如果shutdownThread线程已经把isShutdown变量设为true,但还没有调用监听8 000端口的ServerSocket
的close()方法,而主线程正在执行ServerSocket的accept()方法,主线程阻塞60秒后会抛出
SocketTimeoutException。在准备执行service()方法的下一轮while(!isShutdown){…}循环时,由于
isShutdown变量为true,就会退出循环。
◆由此可见,当shutdownThread线程开始执行关闭服务器的操作时,主线程尽管不会立即终止,但是迟早会结束运行。
如例程3-11所示是AdminClient的源程序,它负责向EchoServer发送“shutdown”命令,从而关闭EchoServer。
例程3-11 AdminClient.java
package multithread4;
import java.net.*;
import java.io.*;
public class AdminClient{
public static void main(String args[]){
Socket socket=null;
try{
socket=new Socket("localhost",8001);
//发送关闭命令
OutputStream socketOut=socket.getOutputStream();
socketOut.write("shutdown\r\n".getBytes());
//接收服务器的反馈
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String msg=null;
while((msg=br.readLine())!=null)
System.out.println(msg);
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(socket!=null)socket.close();
}catch(IOException e){e.printStackTrace();}
}
}
}
|
下面按照以下方式运行EchoServer、EchoClient和AdminClient,以观察EchoServer服务器的关闭过程。EchoClient类的源程序参见本书第1章的1.5.2节的例程1-3。
(1)先运行EchoServer,然后运行AdminClient。EchoServer与AdminClient进程都结束运行,并且在AdminClient的控制台打印如下结果:
服务器正在关闭
服务器已经关闭,关闭服务器用了60毫秒
|
(2)先运行EchoServer,再运行EchoClient,然后再运行AdminClient。EchoServer程序不会立即结束,因为
它与EchoClient的通信任务还没有结束。在EchoClient的控制台中输入“bye”, EchoServer、EchoClient和
AdminClient进程都会结束运行。
(3)先运行EchoServer,再运行EchoClient,然后再运行AdminClient。EchoServer程序不会立即结束,因为
它与EchoClient的通信任务还没有结束。不要在EchoClient的控制台中输入任何字符串,过60秒后,EchoServer等待
EchoClient的发送数据超时,结束与EchoClient的通信任务,EchoServer和AdminClient进程结束运行。如果在
EchoClient的控制台再输入字符串,则会抛出“连接已断开”的SocketException。
3.8 小结
在EchoServer的构造方法中可以设定3个参数。
◆参数port:指定服务器要绑定的端口。
◆参数backlog:指定客户连接请求队列的长度。
◆参数bindAddr:指定服务器要绑定的IP地址。
ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如
果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。SO_TIMEOUT选项表示ServerSocket的
accept()方法等待客户连接请求的超时时间,以毫秒为单位。如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认
值。可以通过ServerSocket的setSo- Timeout()方法来设置等待连接请求的超时时间。如果设定了超时时间,那么当服务器等待的时
间超过了超时时间后,就会抛出SocketTimeoutException,它是Interrupted- Exception的子类。
许多实际应用要求服务器具有同时为多个客户提供服务的能力。用多个线程来同时为多个客户提供服务,这是提高服务器的并发性能的最常用的手段。本章采用3种方式来重新实现EchoServer,它们都使用了多线程:
(1)为每个客户分配一个工作线程;
(2)创建一个线程池,由其中的工作线程来为客户服务;
(3)利用java.util.concurrent包中现成的线程池,由它的工作线程来为客户服务。
第一种方式需要频繁地创建和销毁线程,如果线程执行的任务本身很简短,那么有可能服务器在创建和销毁线程方面的开销比在实际执行任务上的开销还要
大。线程池能很好地避免这一问题。线程池先创建了若干工作线程,每个工作线程执行完一个任务后就会继续执行下一个任务,线程池减少了创建和销毁线程的次
数,从而提高了服务器的运行性能。
相关推荐
本教程将深入探讨Java在网络编程方面的基本概念和用法。 1. **Java网络编程基础** - Java网络编程主要依赖于Java的`java.net`和`java.io`包,它们提供了丰富的类和接口来处理网络连接。 - `Socket`和`...
《Java2编程详解》是一本全面深入探讨Java编程技术的书籍,主要针对Java 2平台进行讲解。在Java 2平台上,Java语言的功能得到了极大的扩展,包括多线程、网络编程、I/O流、数据库连接、图形用户界面(GUI)以及Java...
《Java2编程详解》这本书是Java编程领域的一部经典之作,特别版的使用指南深入剖析了Java 2平台的核心技术和编程方法。这本书旨在帮助开发者掌握Java语言的基础以及高级特性,从而能够有效地进行软件开发。 首先,...
【Java编程思想详解】 Java编程思想是理解和使用Java语言的核心理念,它不仅仅是关于语言特性的简单堆砌,而是一种设计理念,强调的是设计、抽象和问题解决。Java的强大在于其面向对象的设计,它允许开发者以更加...
总之,《Java2编程详解》是一本全面覆盖Java编程的指南,不仅包含基本语法和面向对象设计,还深入讲解了高级主题如并发、网络和反射,对于任何想要深入学习Java的人来说都是一份宝贵的资源。通过学习本书,读者将...
Java编程详解是一个深入探讨Java语言及其应用的领域,特别是针对最新的Java版本。在这个最新的Java编程详解中,我们可能涵盖了许多现代Java开发的关键知识点,包括但不限于以下几个方面: 1. **Java语言基础**:从...
Java网络socket编程是Java编程语言在网络编程中的一项核心技术,主要涉及到网络通信的两个基本概念:客户端(Client)和服务器(Server)。在Java中实现网络通信,主要依赖于Socket编程模型,尤其是基于TCP/IP协议的...
《Java2编程详解(Special Edition Using Java)》是一本针对Java初学者和爱好者精心编写的教程,旨在提供全面且深入的Java编程知识。本书详细介绍了Java语言的核心概念、语法和应用,是学习Java 2平台的理想资源。...
Java基础入门编程详解,这本书籍是为初学者精心编写的,旨在帮助他们系统地学习Java编程语言的基础知识。Java作为一种广泛应用于互联网、企业级应用、移动开发等领域的编程语言,其扎实的基础对于开发者来说至关重要...
在传统的Java网络编程中,我们通常使用`Socket`类进行网络通信。当服务器接收到客户端的连接请求后,会为每个客户端创建一个新的线程来处理其请求。例如,下面的代码片段展示了如何使用阻塞方式接收客户端的数据: ...
例如,Java作为一种强大的编程语言,如何通过SOCKET来实现网络通信,以及APPELT(虽然现在已经较少使用)如何嵌入到网页中并通过网络与用户进行交互。 #### 思路详解 实训项目主要包括以下几个方面: 1. **设计三...
#### 三、Java网络编程高级特性 1. **多线程处理**: - 在处理客户端请求时,可以为每个客户端创建一个独立的线程来处理其请求,这样可以提高系统的并发处理能力。 - 可以使用Java的线程池来管理这些线程,以减少...
5. **API详解**:Java的`java.net`包提供了这些网络编程相关的类和接口。例如,Socket和ServerSocket在`java.net`包下,而InetAddress类用于处理IP地址,URL类用于表示统一资源定位符,这些都与网络通信密切相关。 ...
根据提供的信息,《java编程详解》是一本被广泛推荐并深受读者喜爱的专业书籍,它旨在为初学者和进阶学习者提供全面、深入的Java编程知识。以下是对该书可能涵盖的一些核心知识点的概述: ### Java语言基础 1. **...
Java网络编程的核心在于客户端与服务器端的交互,而ServerSocket是Java中用于服务器端的类,它使得服务器能够监听特定端口,接收客户端的连接请求。本文将详细讲解ServerSocket的使用方法及其在多线程环境下的应用。...
### Java使用Socket网络编程详解 #### 一、引言 Socket是网络通信的基础,它使得不同计算机间的进程可以通过网络进行通信。本篇文章旨在详细介绍如何在Java中使用Socket进行网络编程,特别是TCP协议下的客户端与...