菜鸟初学Java的备忘录(六)

原创 2003年01月24日 17:23:00

2003年1月21日 星期二 晴
通过程序建立了实际的概念之后,现在应该回到最开始的问题,Socket是什么?是实现计算机通信的一种方式,这毫无疑问.但如何能够用最容易理解的语言比较形象而又不偏颇的描述它的原理呢?

Bruce Eckel 在他的《Java 编程思想》一书中这样描述套接字:
套接字是一种软件抽象,用于表达两台机器之间的连接“终端”。对于一个给定的连接,每台机器上都有一个套接字,您也可以想象它们之间有一条虚拟的“电缆”,“电缆”的每一端都插入到套接字中。当然,机器之间的物理硬件和电缆连接都是完全未知的。抽象的全部目的是使我们无须知道不必知道的细节.

 
按我的理解,抽象点来说,一个Socket就是一个电话听筒,你有一个,和你通话的人也有一个,只不过其中有一个人的听筒叫ServerSocket,另一个人的听筒叫Socket.至于谁是ServerSocket,谁是Socket,这不重要,因为客户端和服务器端本来就是相对的,可以互相转化的.通话的两个人通过拿起两个听筒建立了一条通道,这条通道通不通就要看是不是双方都拿起听筒了,假如只有一方拿起听筒,那就只能听到一些嘟嘟的声音,证明通道不同.这里,拿起听筒的过程就是Socket初始化的过程.建立了通道之后,也就是大家都拿起听筒之后,通道两端的人就可以开始通话了.这里又有两个过程,即A对B说话,B接听,和B对A说话,A收听,这两个过程是通过两条线路完成的.传输在这两条线路上的,就是流.流隐藏了所有传输的细节,使得通信双方都认为,他们传过去的是声音,而不是编码.


前面写的服务器端的程序实际上是单任务版本,服务器对客户机的处理机制是在同一时间段内只能处理一个连接,因为handleConnection中采取的是不断循环的阻塞方法,检测到一个,就处理一个,然后再检测到一个,就再处理一个,如果有多个连接同时请求,那只能排队等候.这样的程序是无法在网络中应付多个连接的,因为你无法保证在同一时间内只有一个客户提出与服务器的连接请求,而用阻塞的方法应付多客户连接其速度之慢是可想而知的.

这样就催生了面向多连接的版本.显然,通过多线程可以来实现我们的要求.

由于要解决的是处理客户连接的问题,因此我们的工作只是在服务器端的程序当中修改.其原理不难推出,就是在检测到一个连接请求之后,马上建立一个线程去处理它,然后继续兼听下一个连接请求.所以,我们只需要将原来在handleConnection中的代码原封不动的放到线程的执行代码中,而在handleConnection中添加上新建线程的代码就可以了,十分简单.

同上一篇的风格一样,我们来观察各个部分的代码细节.
首先为这个多线程的版本创建类MultiThreadRemoteFileServer

看看这个类的定义
import java.io.*;
import java.net.*;

public class MultiThreadRemoteFileServer{
    protected int listenPort;
    public MultiThreadRemoteFileServer(int aListenPort){
    }
    public static void main(String[] args) {
    }
    public void acceptConnections() {
    }
    public void handleConnection(Socket incomingConnection) {
    }
}

几乎和RemoteFileServer是一样的,唯一的区别是在我们现在创建的这个类中增加了一个构造函数,这是为了能够使得监听的端口号由我们自己来定.定义如下

public MultithreadedRemoteFileServer(int aListenPort) {
        listenPort = aListenPort;
}


先来看main()
public static void main(String[] args) {
        MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(3000);
        server.acceptConnections();
}

没有区别吧,和RemoteFileServer的main()函数,只是端口号在创建的时候由主程序指定而已。

我们主要关心的改动都在后面
现在看acceptConnection监听程序
public void acceptConnections() {
        try {
        ServerSocket server = new ServerSocket(listenPort, 5);//注意到没有,建立服务器Socket的时候多了一个参数,这个参数是用来指定能够同时申请连接的最大数目,缺省值是50
        Socket incomingConnection = null;
        while (true) {
            incomingConnection = server.accept();
            handleConnection(incomingConnection);
        }
    } catch (BindException e) {
    System.out.println("Unable to bind to port " + listenPort);
    } catch (IOException e) {
    System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
    }
}

改动的地方就一个,多了个参数.这里是它的工作机制。假设我们指定待发数(backlog 值)是5并且有五台客户机请求连接到我们的服务器。我们的服务器将着手处理第一个连接,但处理该连接需要很长时间。由于我们的待发值是 5,所以我们一次可以放五个请求到队列中。我们正在处理一个,所以这意味着还有其它五个正在等待。等待的和正在处理的一共有六个。当我们的服务器仍忙于接受一号连接(记住队列中还有 2—6 号)时,如果有第七个客户机提出连接申请,那么,该第七个客户机将遭到拒绝


接着看,我们的下一个改动显然是在处理监听到的线程的方法handleConnection中,前面已经说了,在多线程的版本中,我们检测到一个连接请求,就马上生成一个线程,然后就不用理它了,那么在这里就是新建线程的一句话.

public void handleConnection(Socket connectionToHandle) {
     new Thread(new ConnectionHandler(connectionToHandle)).start();
}

我们注意到有一个新的类ConnectionHandler,这个类是Runnable的,即是一个接口类(这是用接口实现的一个线程,要是有不明白的话,可以去看看17号的关于线程的东西).我们用 ConnectionHandler 创建一个新 Thread 并启动它。正如我们刚才所说的,原来在RemoteFileServer的handleConnection中的代码统统原封不动的转移到了这个接口类ConnectionHandler的run()方法中来了.

那么我们来看看整个ConnectionHandler类的定义吧。

class ConnectionHandler implements Runnable {
    protected Socket socketToHandle;
    public ConnectionHandler(Socket aSocketToHandle) {
        socketToHandle = aSocketToHandle;//通过构造函数,将待处理的Socket实例作为参数传送进来
    }
    public void run() {//原来对Socket的读/写的代码都在这里了
        try {
            PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
            BufferedReader streamReader = new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));

            String fileToRead = streamReader.readLine();
            BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));

            String line = null;
            while ((line = fileReader.readLine()) != null)
                streamWriter.println(line);

            fileReader.close();
            streamWriter.close();
            streamReader.close();
        } catch (Exception e) {
            System.out.println("Error handling a client: " + e);
        }
    }
}


ConnectionHandler 的 run() 方法所做的事情就是 RemoteFileServer 上的 handleConnection() 所做的事情。首先把 InputStream 和 OutputStream 分别包装(用 Socket 的 getOutputStream() 和 getInputStream())进 BufferedReader 和 PrintWriter。然后我们用这些代码逐行地读目标文件.由于InputStream中装的是文件路径,所以中间还需要使用FileReader流将文件路径包装,再经由BufferedReader包装读出.


我们的多线程服务器研究完了,同样,我们回顾一下创建和使用“多线程版”的服务器的步骤:

1.修改 acceptConnections() 以用缺省为 50(或任何您想要的大于 1 的指定数字)实例化 ServerSocket。

2. 修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一个实例生成一个新的 Thread。

3.借用 RemoteFileServer 的 handleConnection() 方法的代码实现 ConnectionHandler 类的run()函数。

 

菜鸟初学Java的备忘录(八)

2003年1月24日 星期五 晴我在22号的笔记中不是有一个疑问吗?为什么我编的程序没有不同步的现象产生呢,我把它发到csdn上去了,现在我已经基本解决这个问题了,下面是论坛的回复纪录摘要回复人:bl...
  • xm4014
  • xm4014
  • 2003年01月28日 13:47
  • 789

菜鸟初学Java的备忘录(九)

2003年1月25日 星期六 雨我突然发现了利用接口实现多线程和利用类构造线程体的不同了,以前我好像并没有太多的注意.利用类构造线程体的时候,需要使用这个类来定义线程对象,比如MyThread thr...
  • xm4014
  • xm4014
  • 2003年01月27日 10:31
  • 823

菜鸟初学Java的备忘录(五)

2003年1月20日 星期一 阴对几个Java的基础知识作一下补充。一.异常Java对异常的处理同Delphi一样,不是刻意的去避免它的发生,而是等它发生后去补救.Delphi的异常处理简单来说就是一...
  • xm4014
  • xm4014
  • 2003年01月24日 17:00
  • 810

菜鸟初学Java的备忘录(七)

2003年1月22日 星期三 晴我突然发现还有很多东西需要我弄明白,比如synchronized这个关键字的用法.因为在我昨天进行创建连接池套接字的研究的时候,发现假如我不弄清楚这个概念,根本就无法进...
  • xm4014
  • xm4014
  • 2003年01月24日 17:32
  • 725

菜鸟初学Java的备忘录(三)

2003年1月17日 星期五 晴今天开始学习Java中多线程的实现.线程是一些可以并行的,独立的执行的代码.之前我编的程序都只能做一件事情,也就是只有一个线程.多线程的编程就是可以将程序任务分成多个并...
  • xm4014
  • xm4014
  • 2003年01月25日 08:47
  • 856

菜鸟初学Java的备忘录(二)

2003年1月16日 星期四 晴   Java的入门比我想象中的还要简单的多,目前为止我已经对Java的基本语法结构有所了解.但我知道,深入的研究任何一门语言,都需要时间和实践的积累.  Applet...
  • xm4014
  • xm4014
  • 2003年01月28日 16:55
  • 1000

菜鸟初学Java的备忘录(一)

2003年1月14日 星期二 晴今天第一次接触Java。虽然以前间或的也看了些书籍,但多是隔靴搔痒,上不了正席,绝不敢说自己懂Java。接触一门新的技术和初恋一样,都是第一次,但不同的是后者一般开始无...
  • xm4014
  • xm4014
  • 2003年01月24日 16:51
  • 1306

菜鸟初学Java的备忘录(十)

2003年1月26日 星期日 雨回顾一下昨天所学习的基于连接池的Socket,其原理中的要点如下:服务器开放有限个PooledConnectionHandler线程用来处理连接;客户的连接请求均加入到...
  • xm4014
  • xm4014
  • 2003年01月29日 14:06
  • 831

菜鸟初学Java的备忘录(四)

2003年1月19日 星期天 晴说了那么多,线程的几个基本函数都没有记下来,现在来补启动一个线程T1=new YourThread();T1.start()操作线程T1.run(),这个前面说过了,启...
  • xm4014
  • xm4014
  • 2003年01月24日 17:42
  • 736

备忘录实现 源码

首先创建保存备忘录信息的表: DbHelper.java [java] view plaincopy在CODE上查看代码片派生到我的代码片 package com.cjq.androi...
  • syf6568362
  • syf6568362
  • 2015年12月30日 16:15
  • 1534
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:菜鸟初学Java的备忘录(六)
举报原因:
原因补充:

(最多只允许输入30个字)