• 多任务处理:关闭连接


    关闭连接 

     

    可能你从没有想过由谁来关闭一个连接。在电话交谈中,任何一方都可以发起结束交谈的过程。这通常是这样的:

     "好了,我得走了。" 

    "好的,再见。" 

    "再见。" 

    另一方面,网络协议通常明确指定了由谁来发起"关闭"连接。在回显协议中,见图4.1a),服务器原原本本地将客户端发送的一切数据回显回去。当客户端完成数据发送后,则调用close()方法。在服务器接收并回显了客户端调用close()方法前的所有数据后,它的read操作将返回-1,以表示客户端已经完成了数据发送。然后,服务器端套接字将调用close()方法。关闭连接是协议中的关键部分,因为如果没有关闭,服务器将不知道客户端什么时候发送完了要回显的字符。对于HTTP协议,见图4.1b),是由服务器端发起的关闭连接。客户端先向服务器发送一个请求("get"),然后服务器发送回一个响应头信息(通常由"200OK"开始),后面跟的是所请求的文件。由于客户端不知道文件的大小,因此服务器必须通过关闭套接字来指示文件的结束。

    调用Socketclose()方法将同时终止两个方向(输入和输出)的数据流。(第6.4.2将对TCP连接的终止进行更加详细的介绍。)一旦一个终端(客户端或服务器端)关闭了套接字,它将无法再发送或接收数据。这就意味着close()方法只能在调用者完成通信之后用来给另一端发送信号。在回显协议中,只要服务器收到了客户端的关闭信号,就立即关闭连接。

    (点击查看大图)图4.1:回显协议(a)和HTTP协议(b)的终止

     

    Echo Client:回显客户端;Echo Server:回显服务器;Closed:关闭;Web Browser:网络浏览器;HTTP ServerHTTP服务器;Closed:关闭

    实际上,客户端的关闭表示通信已经完成。HTTP协议也是一样的原理,只是它的通信终止发起者是服务器。

    下面考虑另一种协议。假设你需要一个压缩服务器,将接收到的字节流压缩后,发回给客户端。这种情况下应该由哪一端来关闭连接呢?由于从客户端发来的字节流的长度是任意的,客户端需要关闭连接以通知服务器要压缩的字节流已经发送完毕。那么客户端应该什么时候调用close()方法呢?如果客户端在其发送完最后一个字节后立即调用套接字的close()它将无法接收到压缩后数据的最后一些字节。或许客户端可以像回显协议那样,在接收完所有压缩后的数据才关闭连接。但不幸的是,这样一来服务器和客户端都不知道到底有多少数据要接收,因此这也不可行。我们需要一种方法来告诉连接的另一端"我已经发送完所有数",同时还要保持接收数据的能力。

    幸运的是套接字提供了一种实现这个功能的方法。Socket类的shutdownInput()shutdownOutput()方法能够将输入输出流相互独立地关闭。调用shutdownInput()后,套接字的输入流将无法使用。任何没有发送的数据都将毫无提示地被丢弃,任何想从套接字的输入流读取数据的操作都将返回-1。当Socket调用shutdownOutput() 方法后,套接字的输出流将无法再发送数据,任何尝试向输出流写数据的操作都将抛出一个IOException异常。在调shutdownOutput()之前写出的数据可能能够被远程套接字读取,之后,在远程套接字输入流上的读操作将返回-1。应用程序调用shutdownOutput()后还能继续从套接字读取数据,类似的,在调用shutdownInput()后也能够继续写数据。

     在压缩协议中(见图4.2),客户端向服务器发送待压缩的字节,发送完成后调用shutdownOutput()关闭输出流,并从服务器读取压缩后的字节流。服务器反复地获取未压缩的数据,并将压缩后的数据发回给客户端,直到客户端执行了停机操作,导致服务器的read操作返回-1,这表示数据流的结束。然后服务器关闭连接并退出。

     

    4.2:压缩协议终止

    Compression Client:压缩客户端;Compression Server:压缩服务器;Uncompressed Bytes未压缩字节;Compressed Bytes:已压缩字节;Shutdown:停机;Closed:关闭在客户端调用了shutdownOutput之后,它还要从服务器读取剩余的已经压缩的字节。

     下面的压缩客户端示例程序,CompressClient.java,实现了压缩协议的客户端。程序从命令行中指定的文件读取未压缩字节,然后将压缩后的字节写入一个新的文件。设未压缩文件名是"data",压缩后文件名是"data.gz"。注意,这个程序只适用于处理小文件,对于大文件来说其存在一个缺陷将导致死锁。(我们将在第6.2节讨论并改正这个缺陷。)

    CompressClient.java

    0 import java.net.Socket;

    1 import java.io.IOException;

    2 import java.io.InputStream;

    3 import java.io.OutputStream;

    4 import java.io.FileInputStream;

    5 import java.io.FileOutputStream;

    6

    7 /* WARNING: this code can deadlock if a large file (more

    than a few

    8 * 10's of thousands of bytes) is sent.

    9 */

    10

    11 public class CompressClient {

    12

    13 public static final int BUFSIZE = 256; // Size of read

    buffer

    14

    15 public static void main(String[] args) throws

    IOException {

    16

    17 if (args.length != 3) { // Test for correct # of args

    18 throw new IllegalArgumentException("Parameter(s):

    <Server> <Port> <File>");

    19 }

    20

    21 String server = args[0]; // Server name or IP address

    22 int port = Integer.parseInt(args[1]); // Server port

    23 String filename = args[2]; // File to read data from

    24

    25 // Open input and output file (named input.gz)

    26 FileInputStream fileIn = new

    FileInputStream(filename);

    27 FileOutputStream fileOut = new

    FileOutputStream(filename + ".gz");

    28

    29 // Create socket connected to server on specified port

    30 Socket sock = new Socket(server, port);

    31

    32 // Send uncompressed byte stream to server

    33 sendBytes(sock, fileIn);

    34

    35 // Receive compressed byte stream from server

    36 InputStream sockIn = sock.getInputStream();

    37 int bytesRead; // Number of bytes read

    38 byte[] buffer = new byte[BUFSIZE]; // Byte buffer

    39 while ((bytesRead = sockIn.read(buffer)) != -1) {

    40 fileOut.write(buffer, 0, bytesRead);

    41 System.out.print("R"); // Reading progress indicator

    42 }

    43 System.out.println(); // End progress indicator line

    44

    45 sock.close(); // Close the socket and its streams

    46 fileIn.close(); // Close file streams

    47 fileOut.close();

    48 }

    49

    50 private static void sendBytes(Socket sock, InputStream

    fileIn)

    51 throws IOException {

    52 OutputStream sockOut = sock.getOutputStream();

    53 int bytesRead; // Number of bytes read

    54 byte[] buffer = new byte[BUFSIZE]; // Byte buffer

    55 while ((bytesRead = fileIn.read(buffer)) != -1) {

    56 sockOut.write(buffer, 0, bytesRead);

    57 System.out.print("W"); // Writing progress indicator

    58 }

    59 sock.shutdownOutput(); // Finished sending

    60 }

    61 }

     

    CompressClient.java

     

    1.应用程序设置和参数解析:第17-23

    2.创建套接字和打开文件:第25-30 

    3.调用sendBytes()方法传输字节:第33

    4.接收压缩后的数据流:第35-42

    while循环反复接收压缩后的数据流并将字节写入输出文件,直到read()方法返回-1示数据流的结束。

    5.关闭套接字和文件流:第45-47

    6.sendBytes():50-60

    给定一个连接到压缩服务器的套接字和一个文件输入流,从文件中读取所有未压缩的字节,并将其写入套接字的输出流。

    获取套接字输出流:第52

    向压缩服务器发送未压缩字节:第55-58

    while循环从输入流读(在这个例子中是从一个文件)取数据并反复将字节发送到套接字的输出流,直到read()方法返回-1表示到达文件结尾。每一次写操作由打印到控制台的"W"指示。

    关闭套接字输出流:第59

    在读取和发送完输入文件的所有字节后,关闭输出流,以通知服务器客户端已经完成了数据发送。close操作将导致服务器端的read()方法返回-1

    我们简单地为多线程的服务器构架写了一个协议,来实现压缩服务器。我们的协议实现,CompressProtocol.java,使用GZIP压缩算法实现了服务器端的压缩协议。服务器从客户端接收未压缩的字节,并将其写入GZIPOutputStream,它对套接字的输出流进行了包装。

    CompressProtocol.java

    0 import java.net.Socket;

    1 import java.io.IOException;

    2 import java.io.InputStream;

    3 import java.io.OutputStream;

    4 import java.util.zip.GZIPOutputStream;

    5 import java.util.logging.Logger;

    6 import java.util.logging.Level;

    7

    8 public class CompressProtocol implements Runnable {

    9

    10 public static final int BUFSIZE = 1024; // Size of receive

    buffer

    11 private Socket clntSock;

    12 private Logger logger;

    13

    14 public CompressProtocol(Socket clntSock, Logger

    logger) {

    15 this.clntSock = clntSock;

    16 this.logger = logger;

    17 }

    18

    19 public static void handleCompressClient(Socket

    clntSock, Logger logger) {

    20 try {

    21 // Get the input and output streams from socket

    22 InputStream in = clntSock.getInputStream();

    23 GZIPOutputStream out = new

    GZIPOutputStream(clntSock.getOutputStream());

    24

    25 byte[] buffer = new byte[BUFSIZE]; // Allocate

    read/write buffer

    26 int bytesRead; // Number of bytes read

    27 // Receive until client closes connection, indicated

    by -1 return

    28 while ((bytesRead = in.read(buffer)) != -1)

    29 out.write(buffer, 0, bytesRead);

    30 out.finish(); // Flush bytes from GZIPOutputStream

    31

    32 logger.info("Client " +

    clntSock.getRemoteSocketAddress() + " finished");

    33 } catch (IOException ex) {

    34 logger.log(Level.WARNING, "Exception in echo

    protocol", ex);

    35 }

    36

    37 try { // Close socket

    38 clntSock.close();

    39 } catch (IOException e) {

    40 logger.info("Exception = " + e.getMessage());

    41 }

    42 }

    43

    44 public void run() {

    45 handleCompressClient(this.clntSock, this.logger);

    46 }

    47 }

    CompressProtocol.java 

    1.变量和构造函数:第10-17 

    2.handleCompressClient():19-42 

    给定一个连接到压缩客户端的套接字,从客户端读取未压缩字节并将压缩后的字节写回客户端。

    获取套接字I/O流:第22-23

    套接字的输出流包装在一个GZIPOutputStream中。写向这个流的字节序列将由GZIP算法对其进行压缩,然后再写入底层的输出流。

    读取未压缩字节和写压缩后的字节:第28-29

    while循环从套接字输入流读取数据,并写入GZIPOutputStream,再由它将压缩后的数据写入套接字的输出流,直到接收到流结束标记。 

    刷新和关闭:第30-42

    在关闭GZIPOutputStream之前需要刷新提交可能被压缩算法缓存的字节。

    run()方法:第44-46

    run()方法只是简单地对handleCompressClient()方法进行调用。

    为了使用这个协议,我们对TCPEchoServerExecutor.java进行了简单的修改,创建了一CompressProtocol实例来替代EchoProtocol实例:

    service.execute(new CompressProtocol(clntSock,logger));

     

    相关下载:

    Java_TCPIP_Socket编程(doc)

    http://download.csdn.net/detail/undoner/4940239

     

    文献来源:

    UNDONER(小杰博客) :http://blog.csdn.net/undoner

    LSOFT.CN(琅软中国) :http://www.lsoft.cn

     

  • 相关阅读:
    BZOJ1076 [SCOI2008]奖励关 概率 状态压缩动态规划
    BZOJ1040 [ZJOI2008]骑士 基环树林(环套树) 树形动态规划
    洛谷1623 树的匹配 树形动态规划 高精度
    BZOJ1053 [HAOI2007]反素数ant 数论
    Vijos1906 联合权值 NOIP2014Day1T2 树形动态规划
    网络流24题 第五题
    网络流24题 第四题
    网络流24题 第三题
    网络流24题 第二题
    网络流24题 第一题
  • 原文地址:https://www.cnblogs.com/wuyida/p/6301063.html
Copyright © 2020-2023  润新知