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

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; /** * 字节流缓冲区,辅助完成对象向字节流的转换。 * <p> * 创建时间: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; /** * 可以从字节数组中读取所需要的数据 * <p> * 创建时间: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; /** * 网络数据包对应类的最高抽象<br/> * 一般数据包分包头和包体两个部分,包头一般含包长度和包类型等信息,包体是本包所承载的内容。<br/> * 本例中,包头就包含长度和类型两个信息,分别占4字节空间。 * <p> * 创建时间: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; /** * 网络数据包对应类的抽象类 * <p> * 创建时间: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; /** * 心跳包(请求包) * <p> * 创建时间: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; /** *心跳包(应答包) * <p> * 创建时间: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; /** * * <p> * 创建时间: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; /** * 解析数据包产生异常时抛出。 * <p> * 创建时间: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; /** * 客户端长连接的封装类。 * <p> * 创建时间: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; /** * 客户端长连接管理类,负责维持所产生的长连接。 * <p> * 创建时间:2009-10-28 下午04:17:09 * @author 侯磊 * @since 1.0 */ public class ConnectionManager { /** * 心跳周期(单位:毫秒) */ private volatile static long activeCycle = 1000; /** * 存放产生的长连接 */ private static Set<Connection> pool = Collections.synchronizedSet(new HashSet<Connection>()); /** * 用于定时发送心跳包 */ 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<time) con.send(MessageFactory.getInstance(Message.ActiveTestRequest)); } catch (IOException e) { removeConnection(con); } catch (ParseException e) { } } yield(); } } void setRunning(boolean b){ running = b; } } }

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

package houlei.net.keepconn.test; import houlei.net.keepconn.client.ConnectionManager; import java.io.IOException; /** * 测试心跳包的简单客户端程序 * <p> * 创建时间: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; /** * 测试心跳包的服务端简单程序 * <p> * 创建时间: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; } } }

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

  1. TCP长连接和短连接代码及其比较

    前言: 最近又看到了关于TCP长连接和短连接的概念,以前也看过Http长连接和短连接的概念,因为Http是建立在TCP协议之上的,所以它其实是依赖TCP的长连接和短连接.所以,我就萌生了一个想法,看看 ...

  2. 通da信TCP长连接数据算法分析

    点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 分析通da信TCP长连接内部分数据的算法." 作为一款老牌的炒股软件,通da信里面的数据是相当的丰富,免费的也很丰富,准确性也很好 ...

  3. Python实现心跳保活TCP长连接

    之前参与了一个横向项目,对方要求和他们的服务端对接时,我们开发的客户端必须一直保持连接,即维护一个长连接,这样服务端可以随时对我们下发控制命令. 简介 本文主要介绍如何实现TCP的长连接维护,主要通过 ...

  4. tcp长连接和保活时间

    tcp长连接和保活时间 TCP协议中有长连接和短连接之分.短连接在数据包发送完成后就会自己断开,长连接在发包完毕后,会在一定的时间内保持连接,即我们通常所说的Keepalive(存活定时器)功能.   ...

  5. Android之间互相的录屏直播 --点对点传输(tcp长连接发送h264)(一)

    前言 转载请注明出处 ,来自: 暂时两篇: (1) Android之间互相的录屏直播 –点对点传输(tcp长连接发送h264)(一) http://blog.csdn.net/baidu_335462 ...

  6. HTTP协议浅谈(一)之TCP长连接

    之前的认识 刚接触HTTP请求就听说过HTTP请求有1.0和1.1两个版本(其实还有个0.9版本,因为只接受GET一种请求,不支持POST方法,因此客户端无法向服务器传递太多信息而为人们所忽略),而且 ...

  7. python使用socket实现协议TCP长连接框架

    点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 使用python实现协议中常见的TCP长连接框架." 分析多了协议就会发现,很多的应用,特别是游戏类和IM类应用,它们的协议会使用 ...

  8. TCP长连接与短链接

    1. TCP连接 当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次 ...

  9. TCP长连接和短连接

    2019独角兽企业重金招聘Python工程师标准>>> 1. TCP连接 当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操 ...

最新文章

  1. php rule engine,jinchunguang
  2. android 发送邮件
  3. css设置元素继承父元素宽度_CSS设置HTML元素的高度与宽度的各种情况总结
  4. Python find() 方法
  5. WebRTC Google的 BBR拥塞控制算法解析
  6. 软件数控编程_这么多CNC数控编程软件, 你觉得哪个好?
  7. ffmpeg视频处理
  8. [CF]Codeforces Round #546 (Div. 2)
  9. 记一次服务器故障及解决办法
  10. ESB 分布式处理技术 Remoting
  11. 快速获取知网,万方,维普等永久性免费下载权限
  12. Vue+UpLoad实现上传、点图预览、删除图片
  13. 调整HTML5画布中图像的大小
  14. oracle dbf 超大,system01.dbf文件过大——SYSTEM表空间AUD$使用空间过大问题处理
  15. 不要高估自己的自制力
  16. 如何在html中加锚定标记,给一个锚定标记多个功能 - HTML
  17. 名编辑电子杂志大师教程 | 如何直接输出安卓apk格式?
  18. 如何在Swift中创建漂亮的iOS图表
  19. 共享图书APP开发解决方案
  20. linux学习2shell脚本编程案例

热门文章

  1. [ 工具 ] ___ Browser : Chrome
  2. 视联网将会给行业带来怎么样的发展
  3. 微信小程序——云函数三方库request-promise的使用详解
  4. 中心极限与大数定理律的关系_CLT 中心极限定理
  5. java实现第七届蓝桥杯四平方和
  6. CUDA编程:矩阵乘运算从CPU到GPU
  7. RGB接口和MPU接口区别
  8. [OpenGL] Bloom自发光效果
  9. 如何修改已提交commit信息
  10. SCORM学习交互开发