在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

阻塞、超时和关闭

2016-8-29 13:24| 发布者: zhangjf| 查看: 582| 评论: 0

摘要: Socket的I/O调用可能会因为多种原因阻塞,数据输入方法read和receive方法在没有数据可读时会阻塞。 TCP套接字的write方法在没有足够的空间缓存传输的数据时可能阻塞,ServerSocket的accept方法和Socket的构造函数都 ...

Socket的I/O调用可能会因为多种原因阻塞,数据输入方法read和receive方法在没有数据可读时会阻塞。

TCP套接字的write方法在没有足够的空间缓存传输的数据时可能阻塞,ServerSocket的accept方法和Socket的构造函数都会阻塞等待,直到连接建立。同时,长的信息往返时间,高错误率的连接和慢速的(或已发生故障的)服务器,都可能导致需要很长的时间来建立连接。所有这些情况,只有在连接请求得到满足后这些方法才会返回。

accept、read、receive方法,可以使用Socket类、ServerSocket类和DatagramSocket类的setSoTimeout方法,设置其阻塞的最长时间(以毫秒为单位)。如果在指定时间内这些方法没有返回,则将抛出一个InterruptedIOException异常。

对于Socket实例,在调用read方法前,还可以使用InputStream的available方法来检测是否可读的数据。

连接和写数据

Socket类的构造函数会尝试根据参数中指定的主机和端口来建立连接,并阻塞等待,直到连接成功建立或发生了系统定义的超时。不过,系统定义的超时时间很长,而Java又没有提供任何缩短它的方法,要改变这个情况,可以使用Socket类的无参数构造函数,它返回一个没有建立连接的Socket实例,需要建立连接时,调用该实例的connect方法,并制定一个远程终端和超时时间(毫秒)。

write方法调用也会阻塞等待,直到最后一个字节成功写入到TCP实现的本地缓存中。如果可用的缓存空间比要写入的数据小,在write方法调用返回前,必须把一些数据成功传输到连接的另一端。因此,write方法阻塞总时间最终还是取决于接收端的应用程序。Java现在还没有提供任何方法使write超时或由其他方法打断的方法。一个可以在Socket实例上发送大量数据的协议可能会无限期地阻塞下去。

  1. public class TimelimitEchoProtocol implements Runnable {
  2. private static final int BUFSIZE = 32; // Size (bytes) buffer
  3. private static final String TIMELIMIT = "10000"; // Default limit (ms)
  4. private static final String TIMELIMITPROP = "Timelimit"; // Thread property
  5. private static int timelimit;
  6. private Socket clntSock;
  7. private Logger logger;
  8. public TimelimitEchoProtocol(Socket clntSock, Logger logger) {
  9. this.clntSock = clntSock;
  10. this.logger = logger;
  11. // Get the time limit from the System properties or take the default
  12. timelimit = Integer.parseInt(System.getProperty(TIMELIMITPROP,TIMELIMIT));
  13. }
  14. public static void handleEchoClient(Socket clntSock, Logger logger) {
  15. try {
  16. // Get the input and output I/O streams from socket
  17. InputStream in = clntSock.getInputStream();
  18. OutputStream out = clntSock.getOutputStream();
  19. int recvMsgSize; // Size of received message
  20. int totalBytesEchoed = 0; // Bytes received from client
  21. byte[] echoBuffer = new byte[BUFSIZE]; // Receive buffer
  22. long endTime = System.currentTimeMillis() + timelimit;
  23. int timeBoundMillis = timelimit;
  24. clntSock.setSoTimeout(timeBoundMillis);
  25. // Receive until client closes connection, indicated by -1
  26. while ((timeBoundMillis > 0) && // catch zero values
  27. ((recvMsgSize = in.read(echoBuffer)) != -1)) {
  28. out.write(echoBuffer, 0, recvMsgSize);
  29. totalBytesEchoed += recvMsgSize;
  30. timeBoundMillis = (int) (endTime - System.currentTimeMillis()) ;
  31. clntSock.setSoTimeout(timeBoundMillis);
  32. }
  33. logger.info("Client " + clntSock.getRemoteSocketAddress() +
  34. ", echoed " + totalBytesEchoed + " bytes.");
  35. } catch (IOException ex) {
  36. logger.log(Level.WARNING, "Exception in echo protocol", ex);
  37. }
  38. }
  39. public void run() {
  40. handleEchoClient(this.clntSock, this.logger);
  41. }
  42. }
复制代码

试图将总时间限制在10秒内,每次read调用结束后将重新计算剩余的timeout。

关闭套接字

网络协议通常会明确指定了由谁来发起关闭连接。

对于HTTP协议,由于客户端不知道文件大小,因此是由服务器端发起关闭套接字来指示文件的结束。

调用Socket的close方法将同时终止两个方向的数据流,一旦一个终端(客户端或服务端)关闭了套接字,它将无法再发送或接收数据。这就是意味着close方法只能够在调用者完成通信之后用来给另一端发送信号。

Socket类的shutdownInput和shutdownOutput方法能够将输入输出流相互独立地关闭,关闭之后,任何没有发送的数据都将毫无提示地被丢弃,任何想从套接字的输入流读取数据的操作都将返回-1。同理,关闭之后,任何尝试向输出流写数据的操作都将抛出一个IOException异常。

在客户端调用了shutdownOutput之后,它还要从服务器读取剩余的已经压缩的字节

  1. public class CompressClient {
  2. public static final int BUFSIZE = 256; // Size of read buffer
  3. public static void main(String[] args) throws IOException {
  4. if (args.length != 3) { // Test for correct # of args
  5. throw new IllegalArgumentException("Parameter(s): <Server> <Port> <File>");
  6. }
  7. String server = args[0]; // Server name or IP address
  8. int port = Integer.parseInt(args[1]); // Server port
  9. String filename = args[2]; // File to read data from
  10. // Open input and output file (named input.gz)
  11. FileInputStream fileIn = new FileInputStream(filename);
  12. FileOutputStream fileOut = new FileOutputStream(filename + ".gz");
  13. // Create socket connected to server on specified port
  14. Socket sock = new Socket(server, port);
  15. // Send uncompressed byte stream to server
  16. sendBytes(sock, fileIn);
  17. // Receive compressed byte stream from server
  18. InputStream sockIn = sock.getInputStream();
  19. int bytesRead; // Number of bytes read
  20. byte[] buffer = new byte[BUFSIZE]; // Byte buffer
  21. while ((bytesRead = sockIn.read(buffer)) != -1) {
  22. fileOut.write(buffer, 0, bytesRead);
  23. System.out.print("R"); // Reading progress indicator
  24. }
  25. System.out.println(); // End progress indicator line
  26. sock.close(); // Close the socket and its streams
  27. fileIn.close(); // Close file streams
  28. fileOut.close();
  29. }
  30. private static void sendBytes(Socket sock, InputStream fileIn)
  31. throws IOException {
  32. OutputStream sockOut = sock.getOutputStream();
  33. int bytesRead; // Number of bytes read
  34. byte[] buffer = new byte[BUFSIZE]; // Byte buffer
  35. while ((bytesRead = fileIn.read(buffer)) != -1) {
  36. sockOut.write(buffer, 0, bytesRead);
  37. System.out.print("W"); // Writing progress indicator
  38. }
  39. sock.shutdownOutput(); // Finished sending
  40. }
  41. }
复制代码
基于GZIP压缩算法的压缩协议
  1. public class CompressProtocol implements Runnable {
  2. public static final int BUFSIZE = 1024; // Size of receive buffer
  3. private Socket clntSock;
  4. private Logger logger;
  5. public CompressProtocol(Socket clntSock, Logger logger) {
  6. this.clntSock = clntSock;
  7. this.logger = logger;
  8. }
  9. public static void handleCompressClient(Socket clntSock, Logger logger) {
  10. try {
  11. // Get the input and output streams from socket
  12. InputStream in = clntSock.getInputStream();
  13. GZIPOutputStream out = new GZIPOutputStream(clntSock.getOutputStream());
  14. byte[] buffer = new byte[BUFSIZE]; // Allocate read/write buffer
  15. int bytesRead; // Number of bytes read
  16. // Receive until client closes connection, indicated by -1 return
  17. while ((bytesRead = in.read(buffer)) != -1)
  18. out.write(buffer, 0, bytesRead);
  19. out.finish(); // Flush bytes from GZIPOutputStream
  20. logger.info("Client " + clntSock.getRemoteSocketAddress() + " finished");
  21. } catch (IOException ex) {
  22. logger.log(Level.WARNING, "Exception in echo protocol", ex);
  23. }
  24. try { // Close socket
  25. clntSock.close();
  26. } catch (IOException e) {
  27. logger.info("Exception = " + e.getMessage());
  28. }
  29. }
  30. public void run() {
  31. handleCompressClient(this.clntSock, this.logger);
  32. }
  33. }
复制代码

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-7-7 18:13

Copyright 2015-2025 djqfx

返回顶部