ddpush 学习之路 6 TCPClientBase.java
来源:互联网 发布:网络电视放不出来 编辑:程序博客网 时间:2024/05/17 04:24
和上一个类的学习时间间隔了几天。两个原因。第一个原因:这个类,稍微的那么有些复杂。我看、我看、我在看。才基本明白了。第二个原因:我屋里路由器总是掉线在深深的无奈之下。花了两三天时间。学会了一款软路由的架设以及配置。好了。不说了。我们来继续我们的 ddpush 学习之路
这次学习一个有些复杂的类 TCPClientBase 光看名字就能看出来。这是一个比较核心的类。因为ddpush就是用来服务器和客户端消息通信的。这个类就是TCP客户端的基类。
当然ddpush主要推荐我们使用UDP 不过我们下次在学习UDP的client 好了。
我们来按照惯例 发出来 根据我理解 并且添加注释的TCPClientBase.java这个类的代码
//TCP客户端基类public abstract class TCPClientBase implements Runnable { //超时时间 单位 秒 protected static int connectTimeout = 10; //套接字缓冲 protected SocketChannel channel; //最后一次发送消息包得时间 protected long lastSent = 0; //远程服务器端口 protected int remotePort = 9966; //应用id protected int appid = 1; //uuid protected byte[] uuid; //远程服务器地址 protected String remoteAddress = null; //并发消息队列 protected ConcurrentLinkedQueue<Message> mq = new ConcurrentLinkedQueue<Message>(); //消息队列接收到得消息计数器 protected AtomicLong queueIn = new AtomicLong(0); //消息队列取出消息计数器 protected AtomicLong queueOut = new AtomicLong(0); //缓冲区大小 protected int bufferSize = 1024; //心跳包间隔时间 protected int heartbeatInterval = 50; //用来存放消息数据的byte[] protected byte[] bufferArray; //处理消息数据的缓冲区 protected ByteBuffer buffer; //是否需要重置连接 protected boolean needReset = true; //启动状态标识 protected boolean started = false; //停止状态标识 protected boolean stoped = false; //当前TCPClientBase线程 protected Thread receiverT; //工作线程类 (消息处理类) protected Worker worker; //工作线程 protected Thread workerT; //发送的数据包个数 private long sentPackets; //接收到得数据包个数 private long receivedPackets; //TCP客户端初始化 public TCPClientBase(byte[] uuid, int appid, String serverAddr, int serverPort, int connectTimeout) throws Exception { if (uuid == null || uuid.length != 16) { throw new java.lang.IllegalArgumentException("uuid byte array must be not null and length of 16 bytes"); } if (appid < 1 || appid > 255) { throw new java.lang.IllegalArgumentException("appid must be from 1 to 255"); } if (serverAddr == null || serverAddr.trim().length() == 0) { throw new java.lang.IllegalArgumentException("server address illegal: " + serverAddr); } this.uuid = uuid; this.appid = appid; this.remoteAddress = serverAddr; this.remotePort = serverPort; TCPClientBase.connectTimeout = connectTimeout; } //入队 将消息添加到消息队列 protected boolean enqueue(Message message) { boolean result = mq.add(message); if (result == true) { queueIn.addAndGet(1); } return result; } //出队 将消息从消息队列中取出 取出最早放入的消息 protected Message dequeue() { Message m = mq.poll(); if (m != null) { queueOut.addAndGet(1); } return m; } //初始化 数据块 缓冲 private synchronized void init() { bufferArray = new byte[bufferSize]; buffer = ByteBuffer.wrap(bufferArray); buffer.limit(Message.SERVER_MESSAGE_MIN_LENGTH); } //socket重置 和服务器的连接中断了就需要重新连接 protected synchronized void reset() throws Exception { //如果连接被关闭了。就重新创建连接 if (needReset == false) { return; } //如果socket通道 != null 就把socket通道销毁 if (channel != null) { try { channel.socket().close(); } catch (Exception e) { } try { channel.close(); } catch (Exception e) { } } //检测网络连接状态 如果网络连接状态正常就开始创建连接远程服务器 if (hasNetworkConnection() == true) { channel = SocketChannel.open(); //当前socket连接被设置为 阻塞模式 channel.configureBlocking(true); channel.socket().connect(new InetSocketAddress(remoteAddress, remotePort), 1000 * connectTimeout); channel.socket().setSoTimeout(1000 * 5); needReset = false; } else { try { Thread.sleep(1000); } catch (Exception e) { } } } //启动TCPClientBase public synchronized void start() throws Exception { if (this.started == true) { return; } //初始化 this.init(); //当前线程 receiverT = new Thread(this, "DDPUSH-TCP-CLIENT-RECEIVER"); //守护线程 receiverT.setDaemon(true); synchronized (receiverT) { receiverT.start(); receiverT.wait(); } //工作线程 发送心跳包 以及处理接收到的消息 worker = new Worker(); workerT = new Thread(worker, "DDPUSH-TCP-CLIENT-WORKER"); workerT.setDaemon(true); synchronized (workerT) { workerT.start(); workerT.wait(); } this.started = true; } //停止TCPClientBase public synchronized void stop() { //改变客户端状态 stoped = true; //如果socket通道 != null 就关闭他 并将他置 null if (channel != null) { try { channel.socket().close(); } catch (Exception e) { } ; try { channel.close(); } catch (Exception e) { } ; } channel = null; //如果当前线程 != null 就中断当前线程 if (receiverT != null) { try { receiverT.interrupt(); } catch (Exception e) { } } //如果worker工作线程 != null 就中断工作线程 if (workerT != null) { try { workerT.interrupt(); } catch (Exception e) { } } } //当前TCPClientBase线程体 public void run() { //唤醒当前TCPClientBase的所有处于wait状态的线程 synchronized (receiverT) { receiverT.notifyAll(); } //如果 当前client状态 stoped != true 状态。就开始一直工作 while (stoped == false) { try { //如果网络状态不正常 无网络连接 的时候。就一直跳出循环。不连接服务器 不处理数据。直到网络连接正常 if (hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } continue; } //检测连接是否被关闭 reset(); //接收服务器发送来得数据。并存放到消息队列中 //然后唤醒work线程开始处理消息包 receiveData(); } catch (java.net.SocketTimeoutException e) { } catch (java.nio.channels.ClosedChannelException e) { //连接被关闭 this.needReset = true; } catch (Exception e) { e.printStackTrace(); //连接被关闭 this.needReset = true; } catch (Throwable t) { t.printStackTrace(); this.needReset = true; } finally { //如果连接被关闭 就休息一会儿 if (needReset == true) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } } //如果消息队列是空得或者网络有问题。就休息一会儿 if (mq.isEmpty() == true || hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } } } } //销毁socket if (this.channel != null) { try { channel.socket().close(); } catch (Exception e) { } try { channel.close(); } catch (Exception e) { } channel = null; } } //发送心跳包 private void heartbeat() throws Exception { //当前时间和上次发送数据时间间隔不超过50s。就不发送心跳。也就是控制心跳间隔50s if (System.currentTimeMillis() - lastSent < heartbeatInterval * 1000) { return; } //创建消息包头,应为这是个心跳包。所以没有消息内容。只有消息包头。所以创建的消息byte[]长度是 Message.CLIENT_MESSAGE_MIN_LINGHT //即Client数据包最小长度 byte[] buffer = new byte[Message.CLIENT_MESSAGE_MIN_LENGTH]; //用buffer作存储空间创建一个缓冲区 第一位 消息版本 第二位 appid 第三位 消息类型 MC_0x00 心跳包 //第四位 uuid 第五位 0 ByteBuffer.wrap(buffer).put((byte) Message.version).put((byte) appid).put((byte) Message.CMD_0x00).put(uuid).putChar((char) 0); //发送心跳包 send(buffer); } //接收服务器发送的数据 private void receiveData() throws Exception { //判断消息包是否可操作 是否达到了上限 limit //整体来说。这个while循环将服务器发送来得消息包分为包头和消息内容两次来读取到buffer中 while (hasPacket() == false) { //如果buffer没有达到上限 也就是 SERVER消息的最小长度 //就从socket通道里读取数据读取放到buffer中,返回值是本次读取了多少数据放到buffer中 int read = channel.read(buffer); //如果读取的数据长度 < 0 就表明这个连接被关闭了 if (read < 0) { throw new Exception("end of stream"); } //如果读取结束 if (hasPacket() == true) { break; } //如果消息队列为空 或者网络状态 出问题了。就休眠等待 if (mq.isEmpty() == true || hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } } } //根据bufuer的当前操作位置,创建一个刚好能够容纳buffer中数据的byte[] byte[] data = new byte[buffer.position()]; //byte[] 拷贝 将接受到得数据拷贝到一个新的byte[]中以便 //当前的buffer能够继续的接受服务器发送来得消息数据 System.arraycopy(bufferArray, 0, data, 0, buffer.position()); //根据服务器发送来得消息数据。创建一个Message消息数据包 Message m = new Message(channel.socket().getRemoteSocketAddress(), data); //清空缓冲区 buffer.clear(); //设置缓冲区的操作限制 buffer.limit(Message.SERVER_MESSAGE_MIN_LENGTH); //如果这个数据包格式错误就丢弃不处理他 if (m.checkFormat() == false) { return; } //修改接收到的数据包个数 this.receivedPackets++; //告诉服务器。我收到的数据包 this.ackServer(m); //如果是心跳包。就丢弃这个消息 if (m.getCmd() == Message.CMD_0x00) { return; } //将接收到得消息放到消息队列 this.enqueue(m); //唤醒工作线程、开始处理接收到得消息包 worker.wakeup(); } //判断是否有可以接收的数据 private boolean hasPacket() { //如果buffer的上限limit == Message.SERVER_MESSAGE_MIN_LENGHT SERVER消息包最小长度 if (buffer.limit() == Message.SERVER_MESSAGE_MIN_LENGTH) { //如果buffer未达到上限 if (buffer.hasRemaining() == true) { return false; } else { //如果已经读取完成了SERVER数据包的包头。就开始查看数据包中是否有 消息内容 //获取数据的内容长度 int dataLen = (int) ByteBuffer.wrap(bufferArray, Message.SERVER_MESSAGE_MIN_LENGTH - 2, 2).getChar(); //是否有消息内容 if (dataLen == 0) { return true; } else { //设置buffer的可操作区域为 消息包头长度 + 消息数据长度 buffer.limit(Message.SERVER_MESSAGE_MIN_LENGTH + dataLen); return false; } } } else { //buffer是否可操作 是否达到上限limit if (buffer.hasRemaining() == true) { return false; } else { return true; } } } //向服务器发送 接收到服务器发送的数据包的 应答包 告诉服务器我收到了一个 什么样的数据包 private void ackServer(Message m) throws Exception { if (m.getCmd() == Message.CMD_0x10) { byte[] buffer = new byte[Message.CLIENT_MESSAGE_MIN_LENGTH]; ByteBuffer.wrap(buffer).put((byte) Message.version).put((byte) appid).put((byte) Message.CMD_0x10).put(uuid).putChar((char) 0); send(buffer); } if (m.getCmd() == Message.CMD_0x11) { byte[] buffer = new byte[Message.CLIENT_MESSAGE_MIN_LENGTH + 8]; byte[] data = m.getData(); ByteBuffer.wrap(buffer).put((byte) Message.version).put((byte) appid).put((byte) Message.CMD_0x11).put(uuid).putChar((char) 8) .put(data, Message.SERVER_MESSAGE_MIN_LENGTH, 8); send(buffer); } if (m.getCmd() == Message.CMD_0x20) { byte[] buffer = new byte[Message.CLIENT_MESSAGE_MIN_LENGTH]; ByteBuffer.wrap(buffer).put((byte) Message.version).put((byte) appid).put((byte) Message.CMD_0x20).put(uuid).putChar((char) 0); send(buffer); } } //向服务器发送消息 private void send(byte[] data) throws Exception { //数据为空或者socket通道没有准备好就不发送消息 if (data == null) { return; } if (channel == null || channel.isOpen() == false) { return; } //缓冲区包裹数据 ByteBuffer bb = ByteBuffer.wrap(data); //判断当前buffer操作位置是否在可操作限制之内。如果当前位置可以操作。就向cannel写一个字节序列 while (bb.hasRemaining()) { channel.write(bb); } //将缓冲区内的数据取出并发送、 channel.socket().getOutputStream().flush(); //记录最后一次(本次发送消息的时间。) lastSent = System.currentTimeMillis(); //更新发送的数据包个数 this.sentPackets++; } //得到消息发送次数 public long getSentPackets() { return this.sentPackets; } //得到消息接收次数 public long getReceivedPackets() { return this.receivedPackets; } //得到最后一次发送消息的时间 public long getLastHeartbeatTime() { return lastSent; } //设置心跳时间间隔 public void setHeartbeatInterval(int second) { if (second <= 0) { return; } this.heartbeatInterval = second; } //获取心跳时间间隔 public int getHeartbeatInterval() { return this.heartbeatInterval; } //检测网络状态 public abstract boolean hasNetworkConnection(); //休眠 public abstract void trySystemSleep(); //收到消息的 消息处理 回调 public abstract void onPushMessage(Message message); //发送心跳包以及处理收到的消息包 class Worker implements Runnable { public void run() { // 唤醒工作线程 synchronized (workerT) { workerT.notifyAll(); } //如果当前Client是正在运行的状态。否则,就结束客户端工作线程 while (stoped == false) { try { //查看是否需要发送心跳包 到了心跳时间间隔,就发送心跳包。 heartbeat(); //处理Message消息 handleEvent(); } catch (Exception e) { e.printStackTrace(); } finally { waitMsg(); } } } //休眠1s private void waitMsg() { synchronized (this) { try { this.wait(1000); } catch (java.lang.InterruptedException e) { } catch (Exception e) { e.printStackTrace(); } } } //唤醒Worker里的所有wait状态的线程退出wait状态。 private void wakeup() { synchronized (this) { this.notifyAll(); } } //处理Message消息 private void handleEvent() throws Exception { Message m = null; //这里的死循环是为了要在这里处理完消息队列中得所有消息 while (true) { //取出一个消息 m = dequeue(); //如果这个取出的消息为null就表明这个消息队列里没有消息了 if (m == null) { return; } //检测这个消息的格式是否正确,如果不正确就丢弃当前消息继续处理下一个消息 if (m.checkFormat() == false) { continue; } // real work here 收到一个消息。在这里回调消息处理的函数 //将当前消息传递给消息处理函数 onPushMessage(m); } // finish work here, such as release wake lock } }}
好了。以上就是我理解的并添加注释的TCPClientBase.java 的代码。这个类代码稍微有点多。不过没有关系。我都给详细的添加了注释。这个类因为涉及到和服务器通信。但是服务器的那个类我们还没有学习。所以就不做这个类的调用测试代码了。但是我们要来讲讲这个类。讲讲他得工作流程以及工作内容。
好了。首先我们看到。这个类 implements Runnable 这让你想到了什么?对了。这个类是一个线程类。同时这个类是个abstract 抽象类。所以我们使用的时候。就需要新建我们的TCPClientBaseImpl 实现类。extends TCPClientBase这个类。实现里面的abstract 函数 。 同时这个TCPClientBase的类中应该有run方法。 我们在外部使用的时候。基本的用法就是
new Thread(new TCPClientBaseImpl(xxx)).start()
好了。首先我们看到类名的声明这一行。就可以想象到以上这么多东西。那么我们下面就来看看这个类中具体有什么东西。
属性:
吧啦吧啦一大堆。但是我们可以稍微记着有点印象的属性。比如连接超时时间、SocketCannel、最后发包时间、心跳间隔时间、消息队列、接收消息用得缓冲区和byte[] 以及几个线程、还有TCPClientBase运行的状态、还有就是消息个数的记录,当然肯定还有服务器的一些信息,服务器端口号。服务器ip 等。好了。就暂时了解到这些。不过大家一定要具体的把这个类的所有的属性照着我上面的注释看一遍。
函数:方法
靠记忆的话。这个函数的确有点多。能知道个大概的。肯定有run方法。然后有client得start stop 还得有receive 接收数据 send 发送数据 还有处理Message消息队列的函数。还有消息具体处理的回调函数。
好了。类里面的内容我们大概回忆到这里。当然大家一定要具体的去把我上面注释过的这个类里面的东西都看、一遍肯定是不够的。多看几遍。
好了。下面我们来说一下这个类都做了什么。
首先:
这个类作为client 再初始化的时候肯定是需要连接服务器的。
然后:
客户端需要和服务器端保持心跳 来保证 client和server的连接不会被释放掉。这里就有个心跳机制,还有发送数据包到服务器的代码
接着:
客户端肯定需要一直等待服务器给发送的数据包。所以有个receive 接收服务器的发送过来的数据包
之后:
客户端接收到服务器发送的数据包之后。会通知一下服务器 我收到了一个 xxx样子的数据包 以便服务器确定这个数据包是否发送成功
最后:
客户端接收到服务器发送的消息,肯定是要处理的。这里就有一个专门处理服务器发送的消息的线程。来处理服务器发送过来的消息这个处理方式是回调TCPClientBase这个类的一个抽象函数。并将当前要处理的Message消息数据包传递到这个函数中去。
好了这就是这个TCPClientBase的整体的工作内容。
下面我来挑选一些具体的代码来解释一下我理解的作者的写这个类的思路。
这个类。在start中。从表面上看。创建了两个线程。一个是当前TCPClientBase本身的这个线程。另一个是Worker线程。Worker这个线程我们来帖一部分代码来看看。
//发送心跳包以及处理收到的消息包class Worker implements Runnable { public void run() { // 唤醒工作线程 synchronized (workerT) { workerT.notifyAll(); } //如果当前Client是正在运行的状态。否则,就结束客户端工作线程 while (stoped == false) { try { //查看是否需要发送心跳包 到了心跳时间间隔,就发送心跳包。 heartbeat(); //处理Message消息 handleEvent(); } catch (Exception e) { e.printStackTrace(); } finally { waitMsg(); } } } ...//处理Message消息private void handleEvent() throws Exception { Message m = null; //这里的死循环是为了要在这里处理完消息队列中得所有消息 while (true) { //取出一个消息 m = dequeue(); //如果这个取出的消息为null就表明这个消息队列里没有消息了 if (m == null) { return; } //检测这个消息的格式是否正确,如果不正确就丢弃当前消息继续处理下一个消息 if (m.checkFormat() == false) { continue; } // real work here 收到一个消息。在这里回调消息处理的函数 //将当前消息传递给消息处理函数 onPushMessage(m); } // finish work here, such as release wake lock}
从这些代码中。我们可以看出。worker线程在TCPClientBase正在运行的状态下会一直工作。工作的内容。只有两点。
第一点:发送心跳包 heartbeat();
第二点:处理Message消息
我们来看看这里的处理Message消息的地方。handleEvent() 我们可以发现这里也有个while(true) 我们看完这个while(true)之后就会明白。handleEvent 每次被调用的时候。都会把 消息队列中得所有消息 “处理” 完,这里的处理方式,就是回调onPushMessage这个abstract函数。来处理。我们需要再子类实现这个abstract函数。来具体的区处理我们收到数据包之后的具体处理方式。
下面来看看心跳包的发送
//发送心跳包private void heartbeat() throws Exception { //当前时间和上次发送数据时间间隔不超过50s。就不发送心跳。也就是控制心跳间隔50s if (System.currentTimeMillis() - lastSent < heartbeatInterval * 1000) { return; } //创建消息包头,应为这是个心跳包。所以没有消息内容。只有消息包头。所以创建的消息byte[]长度是 Message.CLIENT_MESSAGE_MIN_LINGHT //即Client数据包最小长度 byte[] buffer = new byte[Message.CLIENT_MESSAGE_MIN_LENGTH]; //用buffer作存储空间创建一个缓冲区 第一位 消息版本 第二位 appid 第三位 消息类型 MC_0x00 心跳包 //第四位 uuid 第五位 0 ByteBuffer.wrap(buffer).put((byte) Message.version).put((byte) appid).put((byte) Message.CMD_0x00).put(uuid).putChar((char) 0); //发送心跳包 send(buffer);}
代码就发送心跳包或者说 心跳包机制 就这么多代码 这个 发送心跳包 heartbeat函数。再外部常规情况下来说。大概是1秒 被调用一次。 再发送心跳包函数内部。首先就是判断当前时间到没有到需要发送心跳包的时候。如果没有到需要发送心跳包的时间。就不发送。如果到了。就构造一个心跳包数据。发送出去。
说道发送。我们来看看发送的具体代码。
//向服务器发送消息private void send(byte[] data) throws Exception { //数据为空或者socket通道没有准备好就不发送消息 if (data == null) { return; } if (channel == null || channel.isOpen() == false) { return; } //缓冲区包裹数据 ByteBuffer bb = ByteBuffer.wrap(data); //判断当前buffer操作位置是否在可操作限制之内。如果当前位置可以操作。就向cannel写一个字节序列 while (bb.hasRemaining()) { channel.write(bb); } //将缓冲区内的数据取出并发送、 channel.socket().getOutputStream().flush(); //记录最后一次(本次发送消息的时间。) lastSent = System.currentTimeMillis(); //更新发送的数据包个数 this.sentPackets++;}
发送数据包模块。首先做了常规的验证。发送的数据是否为空、是否能够发送数据
然后将date byte[]数据包用buffer包裹然后写入SocketCannel的输出缓冲区。刷新缓冲区将数据发送出去。然后记录本次给服务器发送数据的时间和发送的数据包个数。
好了。我们了解了心跳包的发送、数据包Message的处理。下面我们来看看我们怎么样从服务器接收数据。接收数据是在TCPClientBase线程中做得。那我们就来从入口开始看看。从run开始。
//当前TCPClientBase线程体public void run() { //唤醒当前TCPClientBase的所有处于wait状态的线程 synchronized (receiverT) { receiverT.notifyAll(); } //如果 当前client状态 stoped != true 状态。就开始一直工作 while (stoped == false) { try { //如果网络状态不正常 无网络连接 的时候。就一直跳出循环。不连接服务器 不处理数据。直到网络连接正常 if (hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } continue; } //检测连接是否被关闭 reset(); //接收服务器发送来得数据。并存放到消息队列中 //然后唤醒work线程开始处理消息包 receiveData(); } catch (java.net.SocketTimeoutException e) { } catch (java.nio.channels.ClosedChannelException e) { //连接被关闭 this.needReset = true; } catch (Exception e) { e.printStackTrace(); //连接被关闭 this.needReset = true; } catch (Throwable t) { t.printStackTrace(); this.needReset = true; } finally { //如果连接被关闭 就休息一会儿 if (needReset == true) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } } ...
这里也有个类似while(true)的东西。不过这里是根据当前TCPClient的运行标识来判断。
TCPClientBase 开始之后。首先 检测网络状态。如果网络状态不正常。就休眠。休眠之后。返回到while从新开始、继续检测网络状态。也就是说。如果 hasNetworkConnection获取到得网络状态是false 那么这里就会一直不停的检测。而不去与服务器连接通信。
好了。当网络状态正常的时候。就开始重置客户端和服务器的连接,开始我这里也没有明白。这样意思就是每次while循环的时候。都要重新创建一遍和服务器的连接。后来才发现我忽略了reset函数的第一句代码。
//socket重置 和服务器的连接中断了就需要重新连接 protected synchronized void reset() throws Exception { //如果连接被关闭了。就重新创建连接 if (needReset == false) { return; }
如果连接被关闭了或者其他原因导致需要重新创建连接。才会去连接服务器。否则服务器连接正常的情况下。这里直接就return了就不会再创建新的客户端到服务器的连接了。
我们继续看run reset主机后。就开始接受服务器发送的数据包了。receiveData
//接收服务器发送的数据private void receiveData() throws Exception { //判断消息包是否可操作 是否达到了上限 limit //整体来说。这个while循环将服务器发送来得消息包分为包头和消息内容两次来读取到buffer中 while (hasPacket() == false) { //如果buffer没有达到上限 也就是 SERVER消息的最小长度 //就从socket通道里读取数据读取放到buffer中,返回值是本次读取了多少数据放到buffer中 int read = channel.read(buffer); //如果读取的数据长度 < 0 就表明这个连接被关闭了 if (read < 0) { throw new Exception("end of stream"); } //如果读取结束 if (hasPacket() == true) { break; } //如果消息队列为空 或者网络状态 出问题了。就休眠等待 if (mq.isEmpty() == true || hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } } } //根据bufuer的当前操作位置,创建一个刚好能够容纳buffer中数据的byte[] byte[] data = new byte[buffer.position()]; //byte[] 拷贝 将接受到得数据拷贝到一个新的byte[]中以便 //当前的buffer能够继续的接受服务器发送来得消息数据 System.arraycopy(bufferArray, 0, data, 0, buffer.position()); //根据服务器发送来得消息数据。创建一个Message消息数据包 Message m = new Message(channel.socket().getRemoteSocketAddress(), data); //清空缓冲区 buffer.clear(); //设置缓冲区的操作限制 buffer.limit(Message.SERVER_MESSAGE_MIN_LENGTH); //如果这个数据包格式错误就丢弃不处理他 if (m.checkFormat() == false) { return; } //修改接收到的数据包个数 this.receivedPackets++; //告诉服务器。我收到的数据包 this.ackServer(m); //如果是心跳包。就丢弃这个消息 if (m.getCmd() == Message.CMD_0x00) { return; } //将接收到得消息放到消息队列 this.enqueue(m); //唤醒工作线程、开始处理接收到得消息包 worker.wakeup();}
好吧。代码挺多。我们来一点一点说。首先我们先不开这个函数里开头的while这个语句。从 while循环体之后看。然后我们再回过头来看while
while之后。创建了一个临时的byte[] 用来存放当前Message消息对象的具体消息内容 吧接收到的bufferArray数据拷贝到我们创建的临时byte[]中。然后创建一个Message消息对象。Message消息对象我们在前面已经学习过了这个类。不知道的请看我之前的文章。 创建完Message消息对象之后。就把用来接受存放服务器发送的数据的buffer这个成员属性内容都清除掉,以便继续接受从服务器发送来得数据(注意这个buffer不是临时创建的byte[]) 然后把buffer缓冲区的操作上限设置成SERVER_MESSAGE_MIN_LENGTH 就是服务器发送到客户端的数据包的包头的长度
之后。开始检测这个用接收到的数据创建出来的Message消息对象的格式是否正确。如果消息格式不正确。就丢弃这个数据包。
如果消息包格式正确。就向服务器发送一条消息。告诉服务器我接收到了这个消息数据包。
然后判断这个消息是否是心跳包。如果是心跳包。也就扔掉。没用。
然后。吧这个Message消息对象添加到消息队列中去。
最后唤醒worker工作线程。就是上面说的Worker线程。开始处理这个消息数据包。
好了消息数据包的处理。我们知道了。我们现在回头看看消息数据包是如何接收到的。
//接收服务器发送的数据private void receiveData() throws Exception { //判断消息包是否可操作 是否达到了上限 limit //整体来说。这个while循环将服务器发送来得消息包分为包头和消息内容两次来读取到buffer中 while (hasPacket() == false) { //如果buffer没有达到上限 也就是 SERVER消息的最小长度 //就从socket通道里读取数据读取放到buffer中,返回值是本次读取了多少数据放到buffer中 int read = channel.read(buffer); //如果读取的数据长度 < 0 就表明这个连接被关闭了 if (read < 0) { throw new Exception("end of stream"); } //如果读取结束 if (hasPacket() == true) { break; } //如果消息队列为空 或者网络状态 出问题了。就休眠等待 if (mq.isEmpty() == true || hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { } } } ...
由于这个while主要是用hasPacket来控制。我们下面就来看看hasPacket
//判断是否有可以接收的数据private boolean hasPacket() { //如果buffer的上限limit == Message.SERVER_MESSAGE_MIN_LENGHT SERVER消息包最小长度 if (buffer.limit() == Message.SERVER_MESSAGE_MIN_LENGTH) { //如果buffer未达到上限 if (buffer.hasRemaining() == true) { return false; } else { //如果已经读取完成了SERVER数据包的包头。就开始查看数据包中是否有 消息内容 //获取数据的内容长度 int dataLen = (int) ByteBuffer.wrap(bufferArray, Message.SERVER_MESSAGE_MIN_LENGTH - 2, 2).getChar(); //是否有消息内容 if (dataLen == 0) { return true; } else { //设置buffer的可操作区域为 消息包头长度 + 消息数据长度 buffer.limit(Message.SERVER_MESSAGE_MIN_LENGTH + dataLen); return false; } } } else { //buffer是否可操作 是否达到上限limit if (buffer.hasRemaining() == true) { return false; } else { return true; } }}
哈哈。是不是有点迷糊了。这也没看出来什么所以然来啊? 不慌不慌。我们来仔细的看。
首先 while的条件我们知道当hasPacket 返回false的时候。就执行while循环体。
好了我们看 hasPacket 看到第一句。 buffer.limit() == Message.SERVER_MESSAGE_MIN_LENGTH 好像是一头雾水。这时要做啥?
我们来字面上理解。如果 buffer的上限==Message.SERVER_MESSAGE_MIN_LENGTH 这里能看出来有地方将buffer的limit修改了。
我们来找关于从服务器接收数据的这些代码。什么地方修改了buffer得Limit
我们找到两个地方。一个是接收完数据之后。将数据处理成Message数据包 这里我们吧buffer.limit 设置成了 Message.SERVER_MESSAGE_MIN_LENGTH 另一个地方时hasPacket里 吧buffer.limit 设置成了 Message.SERVER_MESSAGE_MIN_LENGTH + dataLen
从第一处修改的地方。我们看出来了。这个if判断就是用来判断buffer是不是刚刚处理过消息包。然后我们在看里面的if 这是判断 如果buffer是否可操作。如果可以操作。就返回false receive里的while当接收到hasPacket返回false时。进入while循环体。我们来看receive的while循环体。
//如果buffer没有达到上限 也就是 SERVER消息的最小长度//就从socket通道里读取数据读取放到buffer中,返回值是本次读取了多少数据放到buffer中int read = channel.read(buffer);//如果读取的数据长度 < 0 就表明这个连接被关闭了if (read < 0) { throw new Exception("end of stream");}//如果读取结束if (hasPacket() == true) { break;}//如果消息队列为空 或者网络状态 出问题了。就休眠等待if (mq.isEmpty() == true || hasNetworkConnection() == false) { try { trySystemSleep(); Thread.sleep(1000); } catch (Exception e) { }}
我这里都有注释。 如果buffer可以操作。就从cannel里读取数据到buffer并且返回一个读取到的数据长度。这里做了一下判断。如果没有读取到数据。就抛出一个异常。这个异常再TCPClientBase的run中得while里被捕获。当发生这个错误时。就需要从新和服务器简历连接。
好了。当我们读取到数据的时候。开始再次判断hasPacket == true 就break 也就是hasPacket return true 就继续while循环接收cannel里的数据。我们再来看hasPacket
这里面返回true的只有两个地方。一个地方时。当获取的消息的数据内容长度为0的时候。就返回true 也就是重新while 再次判断一次hasPacket 如果数据内容长度不为0 就继续从服务器读取数据。
好吧。我承认这里有点绕。我吧这里的流程说一下。大家再看代码。就会明白了。
这里首先hasPacket 进入while里直接从SocketCannel里读取数据。然后开始判断这个消息有没有消息数据内容。如果有消息数据内容。就修改buffer的limit 让buffer可以容纳消息包头和消息包的数据内容
被修改了limit的的buffer再次被hasPacket 再次从SocketCancel里读取数据。
也就是说。这里默认认为服务器发送的数据。只有包头。我们先接受。接受到数据滞后。查看包头。获取这个数据包是否有消息数据内容。如果有消息数据内容。我们再次从SocketCannel里接收
好了。这就是TCPClientBase.java 这个类。
这时第五个类。稍微有些复杂。多看看。挺晚了。好困。睡觉去了。
by brok1n 20150322
- ddpush 学习之路 6 TCPClientBase.java
- ddpush 学习之路 2 DateTimeUtil.java
- ddpush 学习之路 3 StringUtil.java
- ddpush 学习之路 4 PropertyUtil.java
- ddpush 学习之路 5 Message.java
- ddpush 学习之路 7 UDPClientBase.java
- ddpush 学习之路 8 Constant.java
- ddpush 学习之路 9 ClientMessage.java
- ddpush 学习之路 10 MyTcpClient.java
- ddpush 学习之路 11 MyUdpClient.java
- ddpush 学习之路 12 Sender.java
- ddpush 学习之路 13 Receiver.java
- ddpush 学习之路 14 UdpConnector.java
- ddpush 学习之路 1 前言
- ddpush 学习之路 15 关于DDPUSH 的一些很多人会问到的问题和解答
- DDPush开源推送框架源码分析之APPServer到DDPush
- DDPush开源推送框架源码分析之Client到DDPush(UDP模式)
- android ddpush
- [Leetcode] Number of 1 Bits
- Mapped Statements collection does not contain value for
- 《JAVA与模式》之责任链模式
- 基础加强第三天 练习总结
- [Leetcode] 59. Spiral Matrix II
- ddpush 学习之路 6 TCPClientBase.java
- [Leetcode] 60. Permutation Sequence
- 《JAVA与模式》之命令模式
- 证书文件编码格式介绍
- UVa 804 - Petri Net Simulation(模拟)
- [Leetcode] Single Number
- RTSP详解
- new和malloc
- LeetCode Longest Consecutive Sequence