读Socket流时产生阻塞的解决方案

原文地址:https://www.cnblogs.com/qhyuan1992/p/5385289.html

 

在用socket写一个服务器时遇到了问题于是将主要的问题抽了出来,代码如下,由于代码很简单于是也没有注释。

public class Main {
    private static ServerSocket serverSocket;
    private final static ExecutorService exec = Executors.newFixedThreadPool(30);
    public static void main(String[] args) {
        try {
            serverSocket = new ServerSocket(8888);
            while (true) {
                Socket socket = serverSocket.accept();
                exec.execute(new ServerRunnable(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ServerRunnable implements Runnable {
    private Socket socket;
    private InputStream is;
    private OutputStream out;
    private String reqStr;
    private String resContent;
    public ServerRunnable(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        handleSocket(socket);
    }
    private void handleSocket(Socket socket) {
        try {
            byte[] buffer = new byte[1024];
            is = socket.getInputStream();
            System.out.println(is);
            out = socket.getOutputStream();
            int len = 0;
            StringBuilder sb = new StringBuilder();
            while ((len = is.read(buffer)) != -1) {
                String str = new String(buffer, 0, len);
                sb.append(str);
            }
            reqStr = sb.toString();
            System.out.println(reqStr);
            resContent = "Welcome!";
            StringBuilder resBuilder  = new StringBuilder();
            resBuilder.append("HTTP/1.1 200 OK").append("\r\n").
            append("Date:").append(new Date()).append("\r\n").
            append("Content-Type:").append("text/plain;charset=UTF-8").append("\r\n").
            append("Content-Length:").append(resContent.getBytes().length).append("\r\n").
            append("\r\n");
            resBuilder.append(resContent);
            out.write(resBuilder.toString().getBytes());
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码很简单,就是写了一个Socket的服务器,通过浏览器来访问localhost:8888会返回Welcome! 
可是在实际工作时,死活不能达到效果。

我想到过可能是out根本就没把数据写进去,然后断点调试,但就是因为断点调试才导致很长时间没能把错误找出来。

1.在测试的时候有这样一个现象一直没引起我的注意:服务器端打印的浏览器发过来的数据在点击停止加载网页/刷新时才会打印!!(知道真相后明白了是因为断开连接另一端就会跳出阻塞继续执行下去) 
而我在测试的时候由于浏览器一直收不到服务器端发的数据而处于不停地等待状态,我就会再次刷新或者再访问一次,而恰恰由于这样愚蠢的操作,服务器端打印了数据,断点调试也进去了,于是我好长时间没有怀疑是因为压根就没走到这一步。而怀疑是我的电脑哪里或者浏览器哪里没设置好。

2.屏蔽了handleSocket里面接收客户端的输入代码,仅仅加上给客户端发的数据,发现可以收到数据,明确了数据没有写错,最后在发现上面的问题后在while循环处打断点,最终发现程序阻塞在那里。

刚开始感到很奇怪,大文件的复制不都是这样做的么,怎么还会出错,在网上搜了一下,socket在close后,才会发送给另一端结束符EOF,从而才会read到流结尾信息而返回-1。 
以前写java聊天功能的时候其实遇到过这样的问题的,要退出聊天发一个特定的字符,然后在break出循环,接着会close掉socket,这样另一端的会由于这端的socket被close掉也跳出循环。只是现在由于只写服务端就没想到。

因为无法知道远程的socket是否还有没有东西要发送。所以read一直不会返回。 
read的文档说明大致是:如果因已到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流的末尾或者抛出异常前,此方法一直阻塞。 
socket和文件不一样,从文件中读,读到末尾就到达流的结尾了,所以会返回-1或null,循环结束,但是socket是连接两个主机的桥梁,一端无法知道另一端到底还有没有数据要传输。 
socket如果不关闭的话,read之类的阻塞函数会一直等待它发送数据,就是所谓的阻塞。

当然这里我们可以将缓冲buffer调整的大一点,这样不用while循环,只读一次即可,然而其他的场景比如发送的数据很大一次读不完那么就只能while循环来处理了。这种场景下的解决方案方案见下面。

四种途径解决:

1.调用socke的shutdownOutput方法关闭输出流,该方法的文档说明为,将此套接字的输出流置于“流的末尾”,这样另一端的输入流上的read操作就会返回-1。不能调用socket.getInputStream().close()。这样会导致socket被关闭。 
2.约定结束标志,当读到该结束标志时退出不再read。 
3.设置超时,会在设置的超时时间到达后抛出SocketTimeoutException异常而不再阻塞。 
4.在头部约定好数据的长度。当读取到的长度等于这个长度时就不再继续调用read方法。

总之tcp方式会经常由于阻塞函数等read/readLine和流处理的函数如刷新缓冲导致代码出现问题。一定要小心!

方式一一般用在通信双方均由开发者掌控。方式二有一定的局限,并且双方还要沟通好标结束志。方式三总感觉不好,超时应该用在其他更有意义的地方,如网络不好时的时间限制。方式四应该是最好的方式,并且大多数的情况都是这样做的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当在Linux系统中使用socket读取数据时,如果返回-115错误,通常表示接收缓冲区空间不足的错误。 当你使用socket函数创建套接字并进行数据传输时,内核会为每个套接字分配接收缓冲区,用于存储接收到的数据。如果接收缓冲区已满,而继续接收数据,则会导致缓冲区溢出,数据丢失或覆盖。 -115错误常见于非阻塞模式的套接字操作。在非阻塞模式下,操作会立即返回,并不会等待数据准备好。而当接收缓冲区为空时,操作尝试读取数据会返回-115错误。 如果你遇到这个错误,你可以通过以下方法解决: 1. 增加接收缓冲区大小:通过修改套接字选项,可以增加接收缓冲区的大小。可以使用setsockopt函数来设置SO_RCVBUF选项,将缓冲区大小增加到你需要的大小。 2. 使用非阻塞模式的循环读取:可以使用非阻塞模式,并在循环中进行读取操作。当返回-115错误时,可以继续循环等待数据准备好,并进行读取。 3. 使用select或epoll函数进行异步IO操作:这些函数可以用于监视套接字文件描述符,当有数据可时再进行读取操作,避免出现接收缓冲区空间不足的错误。 总之,linux socket读取数据返回-115错误通常是由于接收缓冲区空间不足引起的。通过增大接收缓冲区大小、使用非阻塞模式的循环读取或使用异步IO操作等方法,可以解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值