java Socket阻塞问题

一、问题描述
客户端接收消息的时候,如果服务端没有发送数据过来,在读取数据的时候会被阻塞,停在这步不进行下去。

reader.read(chars)

我调试的时候,发现,socket连接成功,并且在接收线程的地方设置断点的时候可以正常接收到消息,如果不设置断点就无法接收到消息,说明在什么地方被阻塞了。就找到是上面的原因。
在做Android开发的时候,华为P9和P9 Plus会遇到Socket方面的问题,但其他手机不存在。
同时开启两个线程,输入和输出线程,其他手机,只要服务器再次发送消息过来,read()的阻塞就会消失,但华为P9手机却不行,它会一直阻塞,也就是说,华为P9手机必须第一时间收到服务端返回的数据,否则会一直阻塞下去。
二、代码实例
两个线程,输入和输出

new Thread(tKeepThread).start();
new Thread(tRecvThread).start();

线程流

Runnable tKeepThread  = new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                while(true) {
                    String cmd = "000000:" + appContext.mIStudentID;
                    if(socket == null) {
                        socket = new Socket();
                        SocketAddress socketAddress = new InetSocketAddress(appContext.getCookie("cent_ip"),9998);
                        socket.connect(socketAddress,8000);//
                        socket.setKeepAlive(true); 
                    }
                    Thread.sleep(5000);
                    OutputStream os = socket.getOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(os);
                    Log.e(TAG,"tKeepThread-cmd:" + cmd + ";" + socket.isConnected());

                    oos.writeObject(cmd);
                    oos.flush();
                    //关闭输出的流
//                  socket.shutdownOutput();
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    };

    Runnable tRecvThread  = new Runnable()  {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            try {
                back = null;
                if (socket != null) {
                    while(true){ //
                        Log.e(TAG,"back start");
                        //Thread.sleep(10000);
//                      socket.setSoTimeout(3000);
                        back = SocketUtil.readStrFromStream(socket.getInputStream());
                        if (back == null) break;
                        Log.e(TAG,"tRecvThread-back:" + back);
                        if(back != null && (!" ".equals(back) && (!"".equals(back)))){
                            sendMsgtoActivty(back);
                        }
                    }
                    //关闭输入的流
//                  socket.shutdownInput(); //Unreachable statement
                }
            } catch (IOException e) {
                e.printStackTrace();
//          } catch (InterruptedException e) {
//              e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    };

SocketUtil.readStrFromStream

public static String readStrFromStream(InputStream in) throws IOException {
        //System.out.println(getNowTime() + " : start to read string from stream");
        StringBuffer result = new StringBuffer("");

        // build buffered reader
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));

        // read 2048 bytes per time
        char[] chars = new char[2048];
        int len;

        try {
//          Log.e(TAG, "lenLine:" + reader.readLine());


            while ((len = reader.read(chars)) != -1) {
                Log.e(TAG,"len:" + len);
                // if the length of array is 1M
                if (2048 == len) {
                    //then append all chars of the array
                    result.append(chars);
                    System.out.println("readStrFromStream : " + result.toString());
                } 
                // if the length of array is less then 1M
                else {
                    //then append the valid chars
                    for (int i = 0; i < len; i++) {
                        result.append(chars[i]);
                        //System.out.println("readStrFromStream : " + result.toString());
                    }
                    break;
                }
            }

        } catch (IOException e) {
            System.out.println(e);
            throw e;
        }
        //System.out.println("end reading string from stream");
        return result.toString();
    }

三,解决办法
一开始,我是在接收线程中设置了10秒的休眠,网速好的情况下是可以成功的,但这会导致一个问题,如果网络不好,10秒内服务器不返回数据,依旧会造成阻塞。(所以这办法不好)
我最后的解决办法是,舍弃了上面的Socket线程,采用了Java NIO(非阻塞)来解决该问题。

NIOSocketUtil nioSocketUtil = new NIOSocketUtil(appContext.getCookie("cent_ip"),9998, cmd, appContext);
        nioSocketUtil.start();

NIOSocketUtil


import android.content.Intent;
import android.util.Log;

import com.winso.interactive.MainActivity;
import com.winso.interactive.app.AppContext;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;

public class NIOSocketUtil extends Thread {
    private static final String TAG = "NIOSocketUtil";
    private Selector selector = null;
    private SocketChannel client = null;
    private static int CONNECT_TIMEOUT = 10000;
    private static int READ_TIMEOUT = 10000;
    private static int RECONNECT_TIME = 10000;
    private final byte CONNECT = 1;
    private final byte RUNNING = 2;
    private byte STATE = CONNECT;
    private boolean onWork;// 是否工作状态
    static {
        java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
    };
    public static int STATUS_OK = 1000;
    public static int STATUS_FAIL = 1001;
    private String ip = "127.0.0.1";
    private int port = 9527;
    private ConnectListener connectListener;
    private DataCallbackListener dataCallbackListener;
    private static final int BLOCK = 102400;
    private ByteBuffer readBuffer = ByteBuffer.allocate(BLOCK);// 100kb缓冲区

    private String cmd = "";
    AppContext activity;
    public NIOSocketUtil(String ip, int port, String cmd, AppContext acitivity) {
        this.ip = ip;
        this.port = port;
        onWork = true;
        this.cmd = cmd;
        this.activity = acitivity;
    }
    public boolean isReady() {
        return STATE == RUNNING;
    }
    public void stopWork() {
        onWork = false;
        closeKey(null);
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (onWork) {
            switch (STATE) {
                case CONNECT:
                    connect();
                    break;
                case RUNNING:
                    running();
                    break;
            }
        }
    }
    private void running() {
        SelectionKey key = null;
        try {
            while (selector.select() > 0) {
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = keys.iterator();
                while (iterator.hasNext()) {
                    key = iterator.next();
                    iterator.remove();
                    read(key);
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            closeKey(key);
        }
    }
    private final void read(SelectionKey selectionKey) throws IOException {
        if (selectionKey.isReadable()) {
            SocketChannel client = (SocketChannel) selectionKey.channel();
            // 如果缓冲区过小的话那么信息流会分成多次接收
            int actual = client.read(readBuffer);
            Log.e(TAG, "actual:" +actual);
            if (actual > 0) {
                readBuffer.flip();
                int limit = readBuffer.limit();
                byte[] data = new byte[limit];
//                readBuffer.get(data);
                String back = byteBufferToString(readBuffer);
                Log.e(TAG, "back:" +back);
                if(back != null && (!" ".equals(back) && (!"".equals(back)))){
                    sendMsgtoActivty(back);
                }
                readBuffer.clear();// 清空
                // process data
                if (dataCallbackListener != null)
                    dataCallbackListener.callback(data);
            }
        }
    }
    /**
     * ByteBuffer 转换 String
     * @param buffer
     * @return
     */
    public static String byteBufferToString(ByteBuffer buffer) {
        CharBuffer charBuffer = null;
        try {
            Charset charset = Charset.forName("UTF-8");
            CharsetDecoder decoder = charset.newDecoder();
            charBuffer = decoder.decode(buffer);
            buffer.flip();
            return charBuffer.toString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
     public final boolean write(byte[] data) {
        try {
            if (STATE == RUNNING && client != null && client.isConnected()) {
                ByteBuffer buffer = ByteBuffer.wrap(data);
                int size = buffer.limit();
                // 此处需加中途断开逻辑,下次再继续发送数据包
                int actually = client.write(buffer);
                if (actually == size)
                    return true;
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            closeKey(null);
        }
        return false;
    }
    private void connect() {
        try {
            selector = Selector.open();
            InetSocketAddress isa = new InetSocketAddress(ip, port);
            client = SocketChannel.open();
            // 设置连超时
            client.socket().connect(isa, CONNECT_TIMEOUT);
            // 设置读超时
            client.socket().setSoTimeout(READ_TIMEOUT);
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            if (client.isConnected()) {
                // 连接成功开始监听服务端消息
                // 发送一个验证数据包到服务器进行验证
                STATE = RUNNING;
                if (connectListener != null) {
                    connectListener.connect(STATUS_OK);
                }
                write(cmd.getBytes());
            } else {
                // 关闭通道过60S重新开始连接
                if (connectListener != null)
                    connectListener.connect(STATUS_FAIL);
                StringBuffer buffer = new StringBuffer("服务器连接失败");
                buffer.append(RECONNECT_TIME / 1000);
                buffer.append("秒后再尝试连接");
                if (connectListener != null)
                    connectListener.error(buffer.toString());
                closeKey(null);// 关闭通道
                Wait(RECONNECT_TIME);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            // 有异常关闭通道过60S重新开始连接
            e.printStackTrace();
            StringBuffer buffer = new StringBuffer("连接出错");
            buffer.append(RECONNECT_TIME / 1000);
            buffer.append("秒后再尝试连接");
            if (connectListener != null)
                connectListener.error(buffer.toString());
            closeKey(null);// 关闭通道
            Wait(RECONNECT_TIME);
        }
    }
    private void closeKey(SelectionKey key) {
        STATE = CONNECT;
        try {
            if (client != null) {
                client.socket().close();
                client.close();
                client = null;
            }
            if (selector != null) {
                selector.close();
                selector = null;
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (key != null) {
            key.cancel();
            try {
                key.channel().close();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                StringBuffer buffer = new StringBuffer("连接断开");
                buffer.append(RECONNECT_TIME / 1000);
                buffer.append("秒后再尝试连接");
                if (connectListener != null)
                    connectListener.error(buffer.toString());
                Wait(RECONNECT_TIME);
            }
        }
    }
    private synchronized void Wait(long millis) {
        try {
            wait(millis);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public void setDataCallbackListener(
            DataCallbackListener dataCallbackListener) {
        this.dataCallbackListener = dataCallbackListener;
    }
    public void setConnectListener(ConnectListener connectListener) {
        this.connectListener = connectListener;
    }
    public interface ConnectListener {
        public void connect(int status);
        public void error(String msg);
    }
    public interface DataCallbackListener {
        public void callback(byte[] data);
    }
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值