关于TCP长连接的一些简单代码

今天看到论坛上有个朋友要心跳包的实现代码。以前碰到过很多类似的问题了。当然,原帖位置是:

http://topic.csdn.net/u/20091020/08/e37c64c0-a416-4b08-a8aa-0d7f964eacb1.html?11914

 

现在谈谈自己对TCP长连接的一些粗浅见解。

1.首先,使用TCP发送信息,其底层也是将信息拆分成若干报文进行发送,在到达目的地按发送的先后顺序重新组装起来。

      其实,相对的每个报文都是有超时限制的,当然,当你不发送报文时,空闲也有超时限制。表现在java里,就是会有异常抛出。

2.其次,为了保持在无有效数据的交互情况下连接不会超时断开,我们从程序上,会人为的发送一些特殊的只用于维持连接不断的信息包。

      这些包,有的人称之为心跳包。我理解的心跳一词,貌似应该是用于信息同步的,当然,用于维持连接,我就不知道是否正确了。

      反正,暂且先这么理解吧。

3.既然,所发信息已经产生了分类的情况(最起码,心跳包就可归为一类),那么,势必牵扯到应用层协议的拟定了。

      也就是说,我们收发双方,要按照共同制定的信息格式进行收发信息,这样,才能相互理解所发的内容。

4.一般情况下,应用层协议,也会参照OSI的设计逻辑,将每个发送的信息分为包头和包体两个部分。

      为了简化解析难度,包头一般都是定长的,内容类型也都会有固定的格式。比如按照:包长、类型、序列号 这样的顺序一次发送。

      包体依据包头当中的类型字段的不同,而采用不同的格式记录信息。心跳包,一般包体为空,不必填充。

5.网络变成,历来都是考验程序员综合能力的一种挑战,尤其是针对初学者。对于多线程,网络连接,数据的协同处理,等等,

      必须要有相当的逻辑思维能力,与全局关。当然,我这方面还是不行,所以,代码也比较垃圾。

      本例中,为了完成心跳包的定时发送,其他代码不写的我都没有写,包头的序列号部分我也没写(写了这个,处理起来就比较麻烦了)。

 

好了,现在开始秀代码,希望更多的朋友能够提出一些好的见解,大家共同进步,共同成长。

 

一、先介绍两个工具类。用于基本数据类型和字节数组的相互转换。

package houlei.net.keepconn.utils; /** * 字节流缓冲区,辅助完成对象向字节流的转换。 *

* 创建时间:2009-10-28 下午02:59:51 * @author 侯磊 * @since 1.0 */ public class ByteArrayBuilder { private int count=0; private byte [] buf = null; public ByteArrayBuilder(){ buf = new byte[8]; } public ByteArrayBuilder(int capacity){ buf = new byte[capacity]; } void expandCapacity(int minimumCapacity) { int newCapacity = (buf.length + 1) * 2; if (newCapacity < 0) { newCapacity = Integer.MAX_VALUE; } else if (minimumCapacity > newCapacity) { newCapacity = minimumCapacity; } byte newBuff[] = new byte[newCapacity]; System.arraycopy(buf, 0, newBuff, 0, count); buf = newBuff; } public ByteArrayBuilder write(int i) { int newcount = count + 4; if (newcount > buf.length) { expandCapacity(newcount); } buf[count+3]=(byte)(i&0x000000FF); buf[count+2]=(byte)((i&0x0000FF00)>>8); buf[count+1]=(byte)((i&0x00FF0000)>>16); buf[count] = (byte)((i&0xFF000000)>>24); count = newcount; return this; } public ByteArrayBuilder write(byte [] b, int off, int len) { if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return this; } int newcount = count + len; if (newcount > buf.length) { expandCapacity(newcount); } System.arraycopy(b, off, buf, count, len); count = newcount; return this; } public ByteArrayBuilder write(byte [] b){ return write(b,0,b.length); } public byte [] toBytes(){ if(count==buf.length) return buf; byte newbuff [] = new byte[count]; System.arraycopy(buf, 0, newbuff, 0, count); return newbuff; } } package houlei.net.keepconn.utils; /** * 可以从字节数组中读取所需要的数据 *

* 创建时间:2009-10-28 下午03:36:48 * @author 侯磊 * @since 1.0 */ public class ByteArrayReader { private int point=0; private byte [] buf = null; public ByteArrayReader(byte [] b){ buf=b; } public int readInt(int offset){ int i = buf[offset++]; i=((i<<8)|buf[offset++]); i=((i<<8)|buf[offset++]); i=((i<<8)|buf[offset++]); return i; } public byte readByte(int offset){ return buf[offset]; } public int readInt(){ int i = buf[point++]; i=((i<<8)|buf[point++]); i=((i<<8)|buf[point++]); i=((i<<8)|buf[point++]); return i; } public byte readByte(){ return readByte(point++); } }

 

二、介绍几个数据包的封装类。

package houlei.net.keepconn.messages; /** * 网络数据包对应类的最高抽象
* 一般数据包分包头和包体两个部分,包头一般含包长度和包类型等信息,包体是本包所承载的内容。
* 本例中,包头就包含长度和类型两个信息,分别占4字节空间。 *

* 创建时间:2009-10-28 下午02:12:43 * @author 侯磊 * @since 1.0 */ public interface Message { /** * 网络数据包类型:心跳包(请求包) */ public static final int ActiveTestRequest = 0x00000001; /** * 网络数据包类型:心跳包(应答包) */ public static final int ActiveTestResponse = 0x80000001; /** * 网络数据包类型:登陆包(请求包) */ public static final int LoginRequest = 0x00000002; /** * 网络数据包类型:登陆包(应答包) */ public static final int LoginResponse = 0x80000002; /** * 网络数据包类型:登出包(请求包) */ public static final int LogoutRequest = 0x00000003; /** * 网络数据包类型:登出包(应答包) */ public static final int LogoutResponse = 0x80000003; /* * 这里还可以添加其他信息包的类型 * */ /** * 获取数据包的总长度(包括包头加包体的长度) * @return数据包的总长度 */ public abstract int getMessageLength(); /** * 获取数据包的类型 * @return数据包的类型 */ public abstract int getMessageType(); /** * 解析数据包的内容。使字节流转换成类对象。该方法主要完成对包体的解析过程。 * @param b 字节数组(字节流) * @throws ParseException 当解析时发生异常时抛出 */ public abstract void parse(byte [] b) throws ParseException; /** * 将类对象的内容转换成字节数组 * @return 类对象对应的字节数组 */ public abstract byte [] getBytes(); } package houlei.net.keepconn.messages; /** * 网络数据包对应类的抽象类 *

* 创建时间:2009-10-28 下午02:47:15 * @author 侯磊 * @since 1.0 */ public abstract class AbstractMessage { /** * 包头长度。(协议采用定长包头,长度为8) */ public static final int MessageHeaderLength = 8; }package houlei.net.keepconn.messages; import houlei.net.keepconn.utils.ByteArrayBuilder; /** * 心跳包(请求包) *

* 创建时间:2009-10-28 下午02:48:36 * @author 侯磊 * @since 1.0 */ public class ActiveTestRequest extends AbstractMessage implements Message { /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#getBytes() */ public byte[] getBytes() { return new ByteArrayBuilder().write(MessageHeaderLength).write(ActiveTestRequest).toBytes(); } /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#getMessageLength() */ public int getMessageLength() { return MessageHeaderLength; } /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#getMessageType() */ public int getMessageType() { return ActiveTestRequest; } /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#parse(byte[]) */ public void parse(byte[] b) throws ParseException { //空包体,所以空函数体。 } } package houlei.net.keepconn.messages; import houlei.net.keepconn.utils.ByteArrayBuilder; /** *心跳包(应答包) *

* 创建时间:2009-10-28 下午04:11:04 * @author 侯磊 * @since 1.0 */ public class ActiveTestResponse extends AbstractMessage implements Message { /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#getBytes() */ public byte[] getBytes() { return new ByteArrayBuilder().write(MessageHeaderLength).write(ActiveTestResponse).toBytes(); } /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#getMessageLength() */ public int getMessageLength() { return MessageHeaderLength; } /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#getMessageType() */ public int getMessageType() { return ActiveTestResponse; } /* (非 Javadoc) * @see houlei.net.keepconn.messages.Message#parse(byte[]) */ public void parse(byte[] b) throws ParseException { // 空包体,所以空函数体。 } } package houlei.net.keepconn.messages; /** * *

* 创建时间:2009-10-28 下午04:37:53 * @author 侯磊 * @since 1.0 */ public class MessageFactory { public static Message getInstance(int messageType){ Message m = null; switch(messageType){ case Message.ActiveTestRequest : m= new ActiveTestRequest();break; case Message.ActiveTestResponse : m= new ActiveTestResponse();break; default:throw new RuntimeException("所需求的数据包类未能提供"); } return m; } }package houlei.net.keepconn.messages; /** * 解析数据包产生异常时抛出。 *

* 创建时间:2009-10-28 下午02:42:16 * @author 侯磊 * @since 1.0 */ public class ParseException extends Exception { private static final long serialVersionUID = 1L; public ParseException() { } public ParseException(String message) { super(message); } public ParseException(Throwable cause) { super(cause); } public ParseException(String message, Throwable cause) { super(message, cause); } }

 

三、客户端长连接的封装类

package houlei.net.keepconn.client; import houlei.net.keepconn.messages.AbstractMessage; import houlei.net.keepconn.messages.Message; import houlei.net.keepconn.messages.MessageFactory; import houlei.net.keepconn.messages.ParseException; import houlei.net.keepconn.utils.ByteArrayReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; /** * 客户端长连接的封装类。 *

* 创建时间:2009-10-28 下午04:17:34 * @author 侯磊 * @since 1.0 */ public class Connection { private Socket socket; private OutputStream out; private InputStream in ; private long lastActTime = 0; Connection(String host,int port) throws IOException{ socket = new Socket(); socket.connect(new InetSocketAddress(host,port)); in =socket.getInputStream(); out = socket.getOutputStream(); } Connection(Socket socket) throws IOException{ this.socket=socket; in =socket.getInputStream(); out = socket.getOutputStream(); } private void send0(Message m) throws IOException{ lastActTime = System.currentTimeMillis(); out.write(m.getBytes()); out.flush(); } private Message readWithBlock0() throws IOException, ParseException{ lastActTime = System.currentTimeMillis(); byte [] header = new byte[AbstractMessage.MessageHeaderLength]; if(in.read(header)!=AbstractMessage.MessageHeaderLength) throw new IOException("未能读取完整的包头部分"); ByteArrayReader bar = new ByteArrayReader(header); int len = bar.readInt(); if(len<0)throw new ParseException("错误的包长度信息"); int type = bar.readInt(); byte [] cache = new byte [len]; System.arraycopy(header, 0, cache, 0, header.length); if(in.read(cache, header.length, len-header.length)!=len-header.length) throw new IOException("未能读取完整的包体部分"); Message m = MessageFactory.getInstance(type); m.parse(cache); return m; } /** * 用于发送数据包,由于包头缺少序列号,所以,交互过程,每一阶段必须等到上一阶段应答包收到才能发起下次的请求包。 * @param m 待发送的信息包 * @return 对应的应答包 * @throws IOException * @throws ParseException */ public synchronized Message send(Message m) throws IOException, ParseException{ send0(m); return readWithBlock0(); } public synchronized void close() throws IOException{ lastActTime = System.currentTimeMillis(); ConnectionManager.removeConnection(this); if(socket!=null)socket.close(); if(in!=null)in.close(); if(out!=null)out.close(); } public synchronized long getLastActTime(){ return lastActTime; } } package houlei.net.keepconn.client; import houlei.net.keepconn.messages.Message; import houlei.net.keepconn.messages.MessageFactory; import houlei.net.keepconn.messages.ParseException; import java.io.IOException; import java.net.Socket; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * 客户端长连接管理类,负责维持所产生的长连接。 *

* 创建时间:2009-10-28 下午04:17:09 * @author 侯磊 * @since 1.0 */ public class ConnectionManager { /** * 心跳周期(单位:毫秒) */ private volatile static long activeCycle = 1000; /** * 存放产生的长连接 */ private static Set pool = Collections.synchronizedSet(new HashSet()); /** * 用于定时发送心跳包 */ private static ConnectActiveMonitor monitor = new ConnectActiveMonitor(); static{ monitor.start(); } public static Connection createConnection(String host,int port) throws IOException{ Connection conn = new Connection(host,port); pool.add(conn); return conn; } public static Connection createConnection(Socket socket) throws IOException{ Connection conn = new Connection(socket); pool.add(conn); return conn; } public static void removeConnection(Connection conn){ pool.remove(conn); } static class ConnectActiveMonitor extends Thread{ private volatile boolean running = true; public void run(){ while(running){ long time = System.currentTimeMillis(); for(Connection con : pool){ try { if(con.getLastActTime()+activeCycle

 

四,最后就是测试心跳包的简单代码了

package houlei.net.keepconn.test; import houlei.net.keepconn.client.ConnectionManager; import java.io.IOException; /** * 测试心跳包的简单客户端程序 *

* 创建时间:2009-10-28 下午05:28:10 * * @author 侯磊 * @since 1.0 */ public class TestClient { public static void main(String[] args) throws IOException { ConnectionManager.createConnection("localhost", 65432); } } package houlei.net.keepconn.test; import houlei.net.keepconn.messages.AbstractMessage; import houlei.net.keepconn.messages.Message; import houlei.net.keepconn.messages.MessageFactory; import houlei.net.keepconn.messages.ParseException; import houlei.net.keepconn.utils.ByteArrayReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * 测试心跳包的服务端简单程序 *

* 创建时间:2009-10-28 下午05:28:20 * * @author 侯磊 * @since 1.0 */ public class TestServer { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(65432); Socket s = ss.accept(); new SimpleProcessor(s).start(); } static class SimpleProcessor extends Thread{ private Socket socket; private OutputStream out; private InputStream in ; private volatile boolean running = true; public SimpleProcessor(Socket s) throws IOException { this.socket = s; in = s.getInputStream(); out = s.getOutputStream(); } public void run(){ while(running){ try { Message m = read(); System.out.println("收到信息"); if(m.getMessageType()==Message.ActiveTestRequest){ m = MessageFactory.getInstance(Message.ActiveTestResponse); } send(m); } catch (Exception e) { e.printStackTrace(); } } try { if(socket!=null)socket.close(); } catch (IOException e) { } } private void send(Message m) throws IOException{ out.write(m.getBytes()); out.flush(); } private Message read() throws IOException, ParseException{ byte [] header = new byte[AbstractMessage.MessageHeaderLength]; if(in.read(header)!=AbstractMessage.MessageHeaderLength) throw new IOException("未能读取完整的包头部分"); ByteArrayReader bar = new ByteArrayReader(header); int len = bar.readInt(); if(len<0)throw new ParseException("错误的包长度信息"); int type = bar.readInt(); byte [] cache = new byte [len]; System.arraycopy(header, 0, cache, 0, header.length); if(in.read(cache, header.length, len-header.length)!=len-header.length) throw new IOException("未能读取完整的包体部分"); Message m = MessageFactory.getInstance(type); m.parse(cache); return m; } } }

 

你可能感兴趣的:(Java,SE,tcp,byte,javadoc,socket,header,class)