第三章 ServerSocket用法详解(主要是线程池)

来源:互联网 发布:linux netcat rpm下载 编辑:程序博客网 时间:2024/06/05 13:24
主要的几个构造方法

  ServerSocket() throws IOException

  ServerSocket(int port) throws IOException

 

  ServerSocket(int port, int backlog) throws IOException

  ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

在以上构造方法中,参数port制定服务器要绑定的端口,参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。

 

绑定端口

可能出现BindException,可能的原因,端口已经被其他服务器进程占用;在某些操作系统中,如果没有以超级用户来运行服务器程序,那么操作系统不允许服务器绑定到1~1023之间的端口(一般也不要去触碰这些端口);如果把参数port设为0,标示由操作系统来为服务器分配一个任意可用的端口。

 

请求队列长度

管理客户请求的任务是由操作系统来完成的,操作系统把这些连接请求存在一个先进先出的队列中。当到达最大长度时,服务器进程所在的主机会拒绝新的连接请求。只有当服务器进程通过ServerSocket的accept()方法从队列中取出连接请求,使队列腾出空位时,队列才能继续加入新的连接请求。

对于客户进程,被拒绝后抛出ConnectionException。

 

设定IP地址

多个网卡要确定用哪个网络的情况下才会用bindAddr。

 

默认构造方法和客户端的Scoket()一个作用,为了设置一些条件后再进行bind,比如说SO_REUSEADDR.

 

接收和关闭与客户的连接

如果队列中没有连接请求,accept()方法就会一直等待,这是个阻塞的过程。当有连接请求后,accept()方法取出一个Socket对象,接下来,服务器从Socket对象中获得输入流和输出流,和客户端交换数据。如果服务器正在进行发送数据的操作时,如果客户端断开了连接,那么服务器端就会抛出一个IOException的子类SocketException异常:

java.net.SocketException:Connection reset by peer

 

关闭Serversocket

在某些情况下,如果希望及时释放服务器的端口,需要显示的调用close(),其实作为服务端,需要关闭的情况应该不会太多。

 

获取ServerSocket的信息

ServerSocket的两个get方法获取服务器绑定的IP地址和端口

getInetAddress();

getLocalPort();

构造ServerSocket时,如果把端口设置为0,那么由操作系统为服务器分配一个端口(匿名端口),多数服务器会监听一个固定端口,这样才便于客户程序访问服务器。匿名端口一般适用于服务器与客户之间的临时通信,通信结束,就断开连接,并且ServerSocket占用的临时端口也被释放了。

 

FTP就使用了匿名端口

 

FTP使用两个并行的TCP连接:一个是控制连接,一个是数据连接。控制连接用于在客户和服务器之间发送控制信息,如用户名和口令、改变远程目录的命令或上传和下载文件的命令。数据连接用于传送文件。TCP服务器在21端口上监听控制连接,如果有客户要求上传或下载文件,就另外建立一个数据连接,通过它来传送文件。数据连接的建立有两种方式。

 

如下图,TCP服务器在20端口上监听数据连接,TCP客户主动请求建立与该端口的连接。

 

 

首先由TCP客户创建一个监听匿名端口的ServerSocket,再把这个ServerSocket监听的端口号(调用ServerSocket的getLocalPort()方法就能得到端口号)发送给TCP服务器,然后由TCP服务器主动请求建立与客户端的连接。

 

 

TCP客户在匿名端口上监听数据连接

 

以上第二种方式就使用了匿名端口,并且是在客户端使用的,用于和服务器建立临时的数据连接。在实际应用中,在服务器端也可以使用匿名端口。

 

 

 

ServerSocket选项

SO_TIMEOUT:表示等待客户连接的超时时间。
SO_REUSEADDR:表示是否允许重用服务器所绑定的地址。
SO_RCVBUF:表示接收数据的缓冲区的大小。

 

SO_TIMEOUT选项

设置该选项:public void setSoTimeout(int timeout) throws SocketException
读取该选项:public int getSoTimeout () throws IOException

SO_TIMEOUT表示ServerSocket的accept()方法等待客户连接的超时时间,以毫秒为单位。如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认值。

当服务器执行ServerSocket的accept()方法时,如果连接请求队列为空,服务器就会一直等待,直到接收到了客户连接才从 accept()方法返回。如果设定了超时时间,那么当服务器等待的时间超过了超时时间,就会抛出SocketTimeoutException,它是 InterruptedException的子类。

 

SO_REUSEADDR选项

设置该选项:public void setResuseAddress(boolean on) throws SocketException
读取该选项:public boolean getResuseAddress() throws SocketException

这个选项与Socket的SO_REUSEADDR选项相同,用于决定如果网络上仍然有数据向旧的ServerSocket传输数据,是否允许新的 ServerSocket绑定到与旧的ServerSocket同样的端口上。SO_REUSEADDR选项的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口。

当ServerSocket关闭时,如果网络上还有发送到这个ServerSocket的数据,这个ServerSocket不会立刻释放本地端口,而是会等待一段时间,确保接收到了网络上发送过来的延迟数据,然后再释放端口。许多服务器程序都使用固定的端口。当服务器程序关闭后,有可能它的端口还会被占用一段时间,如果此时立刻在同一个主机上重启服务器程序,由于端口已经被占用,使得服务器程序无法绑定到该端口,服务器启动失败,并抛出 BindException

值得注意的是,serverSocket.setResuseAddress(true)方法必须在ServerSocket还没有绑定到一个本地端口之前调用,否则执行serverSocket.setResuseAddress(true)方法无效。此外,两个共用同一个端口的进程必须都调用serverSocket.setResuseAddress(true)方法,才能使得一个进程关闭ServerSocket后,另一个进程的ServerSocket还能够立刻重用相同端口。

 

SO_RCVBUF选项

设置该选项:public void setReceiveBufferSize(int size) throws SocketException
读取该选项:public int getReceiveBufferSize() throws SocketException

SO_RCVBUF表示服务器端的用于接收数据的缓冲区的大小,以字节为单位。一般说来,传输大的连续的数据块(基于HTTP或FTP协议的数据传输)可以使用较大的缓冲区,这可以减少传输数据的次数,从而提高传输数据的效率。而对于交互式的通信(Telnet和网络游戏),则应该采用小的缓冲区,确保能及时把小批量的数据发送给对方。

无论在ServerSocket绑定到特定端口之前或之后,调用setReceiveBufferSize()方法都有效。例外情况下是如果要设置大于64K的缓冲区,则必须在ServerSocket绑定到特定端口之前进行设置才有效。

执行serverSocket.setReceiveBufferSize()方法,相当于对所有由serverSocket.accept()方法返回的Socket设置接收数据的缓冲区的大小。

 

 

 

创建线程池

为每个客户分配一个新的工作线程,当工作线程与客户通信结束,这个线程就被销毁,这种实现方式有以下不足之处:

1,服务器创建和销毁工作线程的开销(包括所花费的时间和系统资源)很大。

2,除了创建和销毁线程的开销之外,活动的线程也消耗系统资源。每个线程本身都会占用一定的内存(每个线程需要大约1MB内存)。

3,如果线程数目固定,并且每个线程都有很长的生命周期,那么线程切换也是相对固定的。不同的操作系统有不同的切换周期,一般在20ms左右。这里所说的线程切换是指在Java虚拟机,以及底层操作系统的调度下,线程之间转让CPU的使用权。如果频繁穿件和销毁线程,那么将导致频繁地切换线程,因为一个线程被销毁后,必然要把CPU转让给另一个已经就绪的线程,使该线程获得运行机会。在这种情况下,线程之间的切换不在遵循系统的固定切换周期,切换线程的开销甚至比创建线程的开销还大。

线程池为线程生命周期开销问题和系统资源不足的问题提供了解决方案。

减少创建和销毁线程的次数,每个工作线程都可以一直被重用,能执行多个任务。

可以根据系统的承载能力,方便的调整线程池中线程的数目,防止因为消耗过量系统资源而系统崩溃。


ThreadPool类提供了线程池的一种实现方案。

view plainprint?

   1. import java.util.LinkedList;  
   2.   
   3. public class ThreadPool extends ThreadGroup {  
   4.     private boolean isClosed = false;  
   5.       
   6.     private LinkedList<Runnable> workQueue; // 表示工作队列  
   7.     private static int threadPoolID; //表示线程池ID  
   8.     private int threadID; //表示工作线程ID  
   9.       
  10.     public ThreadPool(int poolSize) {  
  11.         super("ThreadPool-"+(threadPoolID));  
  12.           
  13.         setDaemon(true);  
  14.         //创建工作队列,  
  15.         workQueue = new LinkedList<Runnable>();  
  16.         //启动工作线程  
  17.         for(int i = 0; i < poolSize; i++) {  
  18.             new WorkThread().start();  
  19.         }  
  20.           
  21.     }  
  22.       
  23.     public synchronized  void execute(Runnable task) {  
  24.           
  25.         if(isClosed) {  
  26.             //线程池被关闭则抛出IllegalStateException  
  27.             throw new IllegalStateException();  
  28.         }  
  29.           
  30.         if(task != null) {  
  31.             workQueue.add(task);  
  32.             //唤醒getTask()方法中正在等待任务的工作队列  
  33.             notify();  
  34.         }  
  35.           
  36.     }     
  37.       
  38.     protected synchronized Runnable getTask() throws InterruptedException {  
  39.         while(workQueue.size() == 0) {  
  40.             if(isClosed)  
  41.                 return null;  
  42.             //如果工作队列中没有任务,就等待任务  
  43.             wait();  
  44.         }  
  45.           
  46.         return workQueue.removeFirst();  
  47.     }  
  48.       
  49.     /**
  50.      *关闭线程池
  51.      */  
  52.     public synchronized void close() {  
  53.         if(!isClosed) {  
  54.             isClosed = true;  
  55.             workQueue.clear(); //清空工作队列  
  56.             interrupt(); //中断所有的工作线程,该方法继承于ThreadGroup类  
  57.         }  
  58.     }  
  59.       
  60.     /**
  61.      * 等待工作线程把所有任务执行完
  62.      */  
  63.     public void join() {  
  64.         synchronized(this) {  
  65.             isClosed = true;  
  66.             notifyAll();  
  67.         }  
  68.           
  69.         Thread[] threads = new Thread[activeCount()];  
  70.         //获得线程组中当前所有或者的工作线程,继承于ThreadGroup  
  71.         int count = enumerate(threads);  
  72.           
  73.         for(int i=0; i < count; i++){  
  74.             try{  
  75.                 threads[i].join();  
  76.             }catch(InterruptedException ex) {  
  77.                   
  78.             }  
  79.         }         
  80.     }  
  81.       
  82.       
  83.     private class WorkThread extends Thread{  
  84.         public WorkThread() {  
  85.             //加入当前ThreadPool线程组中  
  86.             super(ThreadPool.this, "WorkThread-"+threadID);  
  87.         }  
  88.           
  89.         public void run() {  
  90.             //isInterrupted()方法集成自Thread类,判断线程是否被中断  
  91.             while(!isInterrupted()) {  
  92.                 Runnable task = null;  
  93.                 try {  
  94.                     task = getTask(); //取出任务  
  95.                 }catch(InterruptedException ex) {  
  96.                     ex.printStackTrace();  
  97.                 }  
  98.                   
  99.                 //如果getTask()为null,或者线程执行getTask()时被中断,则结束此线程  
 100.                 if(task == null) {  
 101.                     return;  
 102.                 }  
 103.                   
 104.                 //运行任务,在while中catch所有东西  
 105.                 try {  
 106.                     task.run();  
 107.                 }catch(Throwable t) {  
 108.                     t.printStackTrace();  
 109.                 }  
 110.             }  
 111.         }  
 112.           
 113.     }  
 114.       
 115.       
 116.       
 117.       
 118.       
 119. } 

原创粉丝点击