资讯专栏INFORMATION COLUMN

阻塞、超时和关闭

focusj / 943人阅读

摘要:套接字的方法在没有足够的空间缓存传输的数据时可能阻塞,的方法和的构造函数都会阻塞等待,直到连接建立。连接和写数据类的构造函数会尝试根据参数中指定的主机和端口来建立连接,并阻塞等待,直到连接成功建立或发生了系统定义的超时。

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实例上发送大量数据的协议可能会无限期地阻塞下去。

public class TimelimitEchoProtocol implements Runnable {
    private static final int BUFSIZE = 32;  // Size (bytes) buffer
    private static final String TIMELIMIT = "10000";  // Default limit (ms)
    private static final String TIMELIMITPROP = "Timelimit";  // Thread property
    private static int timelimit;
    private Socket clntSock;
    private Logger logger;
    public TimelimitEchoProtocol(Socket clntSock, Logger logger) {
        this.clntSock = clntSock;
        this.logger = logger;
        // Get the time limit from the System properties or take the default
        timelimit = Integer.parseInt(System.getProperty(TIMELIMITPROP,TIMELIMIT));
    }
    public static void handleEchoClient(Socket clntSock, Logger logger) {
        try {
            // Get the input and output I/O streams from socket
            InputStream in = clntSock.getInputStream();
            OutputStream out = clntSock.getOutputStream();
            int recvMsgSize;                        // Size of received message
            int totalBytesEchoed = 0;               // Bytes received from client
            byte[] echoBuffer = new byte[BUFSIZE];  // Receive buffer
            long endTime = System.currentTimeMillis() + timelimit;
            int timeBoundMillis = timelimit;
            clntSock.setSoTimeout(timeBoundMillis);
            // Receive until client closes connection, indicated by -1
            while ((timeBoundMillis > 0) &&     // catch zero values
                    ((recvMsgSize = in.read(echoBuffer)) != -1)) {
                out.write(echoBuffer, 0, recvMsgSize);
                totalBytesEchoed += recvMsgSize;
                timeBoundMillis = (int) (endTime - System.currentTimeMillis()) ;
                clntSock.setSoTimeout(timeBoundMillis);
            }
            logger.info("Client " + clntSock.getRemoteSocketAddress() +
                    ", echoed " + totalBytesEchoed + " bytes.");
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Exception in echo protocol", ex);
        }
    }
    public void run() {
        handleEchoClient(this.clntSock, this.logger);
    }
}

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

关闭套接字

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

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

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

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

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

public class CompressClient {
    public static final int BUFSIZE = 256;  // Size of read buffer
    public static void main(String[] args) throws IOException {
        if (args.length != 3) { // Test for correct # of args
            throw new IllegalArgumentException("Parameter(s):   ");
        }
        String server = args[0];               // Server name or IP address
        int port = Integer.parseInt(args[1]);  // Server port
        String filename = args[2];             // File to read data from
        // Open input and output file (named input.gz)
        FileInputStream fileIn = new FileInputStream(filename);
        FileOutputStream fileOut = new FileOutputStream(filename + ".gz");
        // Create socket connected to server on specified port
        Socket sock = new Socket(server, port);
        // Send uncompressed byte stream to server
        sendBytes(sock, fileIn);
        // Receive compressed byte stream from server
        InputStream sockIn = sock.getInputStream();
        int bytesRead;                      // Number of bytes read
        byte[] buffer = new byte[BUFSIZE];  // Byte buffer
        while ((bytesRead = sockIn.read(buffer)) != -1) {
            fileOut.write(buffer, 0, bytesRead);
            System.out.print("R");   // Reading progress indicator
        }
        System.out.println();      // End progress indicator line
        sock.close();     // Close the socket and its streams
        fileIn.close();   // Close file streams
        fileOut.close();
    }
    private static void sendBytes(Socket sock, InputStream fileIn)
            throws IOException {
        OutputStream sockOut = sock.getOutputStream();
        int bytesRead;                      // Number of bytes read
        byte[] buffer = new byte[BUFSIZE];  // Byte buffer
        while ((bytesRead = fileIn.read(buffer)) != -1) {
            sockOut.write(buffer, 0, bytesRead);
            System.out.print("W");   // Writing progress indicator
        }
        sock.shutdownOutput();     // Finished sending
    }
}
基于GZIP压缩算法的压缩协议
public class CompressProtocol implements Runnable {
    public static final int BUFSIZE = 1024;   // Size of receive buffer
    private Socket clntSock;
    private Logger logger;
    public CompressProtocol(Socket clntSock, Logger logger) {
        this.clntSock = clntSock;
        this.logger = logger;
    }
    public static void handleCompressClient(Socket clntSock, Logger logger) {
        try {
            // Get the input and output streams from socket
            InputStream in = clntSock.getInputStream();
            GZIPOutputStream out = new GZIPOutputStream(clntSock.getOutputStream());
            byte[] buffer = new byte[BUFSIZE];   // Allocate read/write buffer
            int bytesRead;                       // Number of bytes read
            // Receive until client closes connection, indicated by -1 return
            while ((bytesRead = in.read(buffer)) != -1)
                out.write(buffer, 0, bytesRead);
            out.finish();      // Flush bytes from GZIPOutputStream
            logger.info("Client " + clntSock.getRemoteSocketAddress() + " finished");
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Exception in echo protocol", ex);
        }
        try {  // Close socket
            clntSock.close();
        } catch (IOException e) {
            logger.info("Exception = " +  e.getMessage());
        }
    }
    public void run() {
        handleCompressClient(this.clntSock, this.logger);
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65903.html

相关文章

  • 从0到1玩转线程池

    摘要:提交任务当创建了一个线程池之后我们就可以将任务提交到线程池中执行了。提交任务到线程池中相当简单,我们只要把原来传入类构造器的对象传入线程池的方法或者方法就可以了。 我们一般不会选择直接使用线程类Thread进行多线程编程,而是使用更方便的线程池来进行任务的调度和管理。线程池就像共享单车,我们只要在我们有需要的时候去获取就可以了。甚至可以说线程池更棒,我们只需要把任务提交给它,它就会在合...

    darkerXi 评论0 收藏0
  • Java线程池从使用到阅读源码(3/10)

    摘要:最后,我们会通过对源代码的剖析深入了解线程池的运行过程和具体设计,真正达到知其然而知其所以然的水平。创建线程池既然线程池是一个类,那么最直接的使用方法一定是一个类的对象,例如。单线程线程池单线程线程 我们一般不会选择直接使用线程类Thread进行多线程编程,而是使用更方便的线程池来进行任务的调度和管理。线程池就像共享单车,我们只要在我们有需要的时候去获取就可以了。甚至可以说线程池更棒,...

    468122151 评论0 收藏0
  • ThreadPoolExecutor线程池源码分析

    摘要:线程池技术旨在解决两个不同的问题在处理大量异步任务时可以提高性能,因为减少了线程的销毁,新建,切换等消耗性能的操作。线程池还有能力统一管理,调度,监控,调优线程等,还提供了一下基本的统计,比如已完成的任务数量。线程数量,线程池的状态。 了解ThreadPoolExecutor 先看一下线程池类的类图关系: showImg(https://segmentfault.com/img/bV3...

    stormzhang 评论0 收藏0
  • Node中的事件循环

    摘要:的事件循环一个线程有唯一的一个事件循环。索引就是指否还有需要执行的事件,是否还有请求,关闭事件循环的请求等等。先来看一下定义的定义是在事件循环的下一个阶段之前执行对应的回调。虽然是这样定义的,但是它并不是为了在事件循环的每个阶段去执行的。 Node中的事件循环 如果对前端浏览器的时间循环不太清楚,请看这篇文章。那么node中的事件循环是什么样子呢?其实官方文档有很清楚的解释,本文先从n...

    lwx12525 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<