进入11月份以来,忙得焦头烂额,临近年底就开始进入对账节奏,各种查错纠错,加之又负责一个离职同事的项目,一直没时间更新博客。今天遇到了一个很有意思的问题,一个实习生用socket通讯的时候遇到了java.net.SocketTimeoutException:Read timed out异常,指点他以后,顺便讲讲这个东西。我们都知道,利用Socket读取数据一般有两种方法:
1)按照字节流读取(BufferedInputStream.read!=-1)
2)按照字符流读取(BufferedReader.readLine!=null)
不加while循环时是不会存在异常阻塞情况的,但使用while后,如果无数据可读,就会阻塞到有数据可读,或者到达流的末尾,即字节流返回-1,字符流返回null。那我们为什么写程序的时候一般都要用while呢?(当时讲解的时候这个实习生提出的问题,我觉得这个问题很有意思)为什么要使用while?显然易见呀,我们数据又不仅仅是一两行,大多时候数据都比较多,直接readLine而不加while的话,默认只能取得最后一行的数据。但是使用while也有一个比较显而易见的缺点----阻塞等待。
public String callUnify(String importMsg, String svcCode) {
Socket socket = null;
BufferedWriter pw = null;
BufferedReader br = null;
String resultStr = null;
String svcCodeFormat = HostFmtTool.formatString(svcCode + "", 8, HostFmtTool.FULL_ADDR_RIGHT, ' ');
StringBuffer outStr = new StringBuffer();
// 四个参数代表的意思分别是:原始字符串[获取字符串的字节长度]、需求字符串长度、填充方向、填充空格或0
outStr.append(HostFmtTool.formatString(StringUtils.charLength(importMsg, "UTF-8") + "", 8,
HostFmtTool.FULL_ADDR_RIGHT, ' '));
outStr.append(svcCodeFormat);
outStr.append(importMsg);
StringBuffer imStr = new StringBuffer();
try {
socket = new Socket();
socket.connect(new InetSocketAddress("ip", 8080), 3000);
socket.setSoTimeout(3000); // 设置超时
pw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
pw.write(outStr.toString());
pw.flush();
int rd = 0;
while ((rd = br.read()) != -1) {
imStr.append((char) rd);
}
/**
* 去除报文格式
*/
resultStr = StringUtils.subString(imStr.toString(), 16, StringUtils.charLength(imStr.toString()));
} catch (UnknownHostException e) {
e.printStackTrace();
throw new RuntimeException("核心请求失败[" + e.getMessage() + "]");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("核心连接异常[" + e.getMessage() + "]");
} finally {
try {
if (pw != null) {
pw.close();
}
if (br != null) {
br.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultStr;
}
那么,如何处理这个问题呢?
1)最常用的方法,设置时长。
2)双方约定长度,比如8位交易码+8位长度(长度不足8位补0)
附录工具类:
import java.io.UnsupportedEncodingException;
/**
* 字符串按字节截取GBK标准
*
* @author Admin
*/
public class StringUtils {
private static final String CHARSET = "GBK";
/**
* 获取字符串字节长度
*
* @param str
* 字符串
* @return 字符串长度(使用GBK,汉字算长度为2)
* @throws UnsupportedEncodingException
*/
public static Integer charLength(String str) {
if (str == null) {
return null;
}
int length = 0;
try {
length = str.getBytes(CHARSET).length;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
return length;
}
/** 指定编码集计算中文长度 **/
public static Integer charLength(String str, String charset) {
if (str == null) {
return null;
}
int length = 0;
try {
length = str.getBytes(charset).length;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
return length;
}
/**
* 对字符串srcStr截取从 begin到end段字符串
*
* @param srcStr
* 源字符串
* @param begin
* 起始位置
* @param end
* 结束位置
* @return 截取位置字符串
* @throws UnsupportedEncodingException
*/
private static String subStringInner(String sourceString, int begin, int end) throws UnsupportedEncodingException {
if (sourceString == null) {
return null;
}
if (charLength(sourceString) < begin || charLength(sourceString) < end) {
throw new RuntimeException("字符串截取位置越界");
}
if (begin >= end) {
throw new RuntimeException("传入起始位置错误");
}
byte[] bt = sourceString.getBytes(CHARSET);
byte[] tt = new byte[end - begin];
int i = 0;
int j = 0;
while (i < bt.length) {
if (i >= begin && i < end) {
tt[j] = bt[i];
j++;
}
i++;
}
return new String(tt, CHARSET);
}
/**
* 对字符串srcStr截取从 begin到end段字符串
*
* @param srcStr
* 源字符串
* @param begin
* 起始位置
* @param end
* 结束位置
* @return 截取位置字符串
*/
public static String subString(String sourceString, int begin, int end) {
String str;
try {
str = subStringInner(sourceString, begin, end);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
return str;
}
}
/**
* 字符串填充类 保证字符串定长 不定长则填充
*
* @author guxf
*/
public class HostFmtTool {
public final static int FULL_ADDR_LEFT = 0;
public final static int FULL_ADDR_RIGHT = 1;
/**
* 字符串填充
*
* @param str
* 原始字符串
* @param length
* 需求字符串长度
* @param addtr
* 填充位置 FULL_ADDR_LEFT 0-左 FULL_ADDR_RIGHT 1-右
* @param fullStr
* 填充字符串
* @return 处理后字符串
*/
public static String formatString(String str, int length, int addtr, char fullStr) {
if (str == null) {
str = "";
}
int strLen = StringUtils.charLength(str);
if (strLen >= length) {
return StringUtils.subString(str, 0, length);
}
StringBuffer sb = new StringBuffer();
int l;
switch (addtr) {
case 0:
l = length - strLen;
for (int i = 0; i < l; i++) {
sb.append(fullStr);
}
sb.append(str);
break;
case 1:
l = length - strLen;
sb.append(str);
for (int i = 0; i < l; i++) {
sb.append(fullStr);
}
break;
default:
throw new RuntimeException("传入参数错误!");
}
return sb.toString();
}
}