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