多任务处理:关闭连接

关闭连接 

 

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

 "好了,我得走了。" 

"好的,再见。" 

"再见。" 

另一方面,网络协议通常明确指定了由谁来发起"关闭"连接。在回显协议中,见图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

 

原文地址:https://www.cnblogs.com/wuyida/p/6301063.html