基于字节数组的定长队列
这个队列可以在队尾追加(add)数据,由于是定长的,所以追加数据会将头部的数据“推出”队列。另外该队列可以检查是否包含(contains)某一子字符串,或者更进一步,检查是否包含某个用正则表达式表示的模式。这种数据结构主要用在Telnet Client的自动登录过程中,检查收到的报文中是否有预期的标志,以决定是否发送登录信息(用户名、密码等)。下面是一个实现:
public class ByteQueue { private final Logger logger = LoggerFactory.getLogger(this.getClass().getName()); private final int BUFFER_LENGTH = 32; byte[] buffer = new byte[BUFFER_LENGTH]; public void add(byte[] income, int len) { logger.debug("income is: " + BytesHandler.bytes2HexString(income)); byte[] tmp = new byte[BUFFER_LENGTH + len]; System.arraycopy(buffer, 0, tmp, 0, buffer.length); System.arraycopy(income, 0, tmp, buffer.length, len); System.arraycopy(tmp, tmp.length - BUFFER_LENGTH, buffer, 0, BUFFER_LENGTH); } public boolean contains(String sub) { String bufStr = null; try { bufStr = new String(buffer, "ISO8859-1"); } catch (UnsupportedEncodingException e) { logger.error("bad encoding: ISO8859-1"); } return bufStr.contains(sub); } }
上述"ISO8859-1"编码又名Latin-1,据“ ISO8859-1、UTF-8 与GB2312 ”一文是Java网络传输的标准编码,又据“ Java: Regex on byte array ”一文,这是一个单字节的编码方案,所以可以用字符串的contains方法完成字节的查找。“ 字符在utf-8,gbk,gb2312,iso8859-1下的编码实验 ”的结果很有参考价值。
下面的代码演示了如何使用上面的类完成自动登录:
ByteQueue bq = new ByteQueue(); while (true) { byte[] buf = new byte[128]; int recv = nis.read(buf); if (recv == -1) { logger.info("read -1 from client."); break; } logger.debug("client send: " + BytesHandler.bytes2HexString(buf)); uout.write(buf, 0, recv); uout.flush(); // execute login script
String expTest = expQ.peek(); //expQ是预期队列:"ogin:", "assword:",sendQ是发送队列:"myusername", "mypwd" if (expTest != null) { bq.add(buf, recv); //由于buf是定长数组,其中会有很多0,所以add的第二个参数len标示了有效数据的长度很重要 if (bq.contains(expTest)) { String exp = expQ.poll() + "\r\n"; // 一定不要忘了加回车! String send = sendQ.poll() + "\r\n"; nos.write(send.getBytes("UTF-8")); nos.flush(); } } }
打印字节数组内容(调试时很有用)
public static String bytes2HexString(byte[] b) { String ret = ""; for (int i = 0; i < b.length; i++) { String hex = Integer.toHexString(b[i] & 0xFF); if (hex.length() == 1) { hex = '0' + hex; } ret += hex.toUpperCase(); } return ret; }