在讲监听器时说过处理的新的socket要尽快返回,监听器调用的是ClientFactory的createPhysicalConnection方法,那么就来看这个方法:

    public boolean createPhysicalConnection(PushClientSocket socket,
boolean isObserver, ListenerOptions listenerOptions) {
PhysicalConnectionPool thePhysicalConnectionPool =
serverImpl.getPhysicalConnectionPool();
IOQueue<PhysicalConnection> ioQueue = serverImpl.getIOQueue(); // 内置了一个PhysicalConnection的对象池,这样可以避免每次都要
// 创建PhysicalConnection对象,可以加快处理速度
PhysicalConnection connection =
thePhysicalConnectionPool.borrowObject();
// 把PhysicalConnection对象和socket对象关联起来
connection.reset(socket, isObserver, listenerOptions); // 初始化协议,分配buffer,用来缓存解析请求时的数据
if (!connection.setUpProtocolContexts()) {
thePhysicalConnectionPool.returnObject(connection);
return false;
} Debug.debug("Physical Connection Created for client from: " +
socket.getIP()); // 把连接注册到I/O队列中,这样就可以监听请求
if (!ioQueue.addSocketContext(socket, connection)) {
thePhysicalConnectionPool.returnObject(connection);
//leave socket close to acceptor
return false;
} Debug.debug("Queue adds client from: " + socket.getIP()); // 把创建的PhysicalConnection加入pending队列中,此时连接
// 还不算是真正的已连接状态,要等到第一个请求到达并正确
// 处理之后才会是已连接状态,并且会创建一个LogicalConnection
// 和这个PhysicalConnection相关联
addPhysicalConnection(connection); // 初始化PhysicalConnection
serverImpl.getDispatcher().handleInitialize(connection); return true;
}

ClientFactory是PhysicalConnection的管理程序,这个方法的的作用就是创建PhysicalConnection和新的socket相关联,并且把PhysicalConnection加入请求监听的I/O队列。因此来说说IOQueue。

IOQueue本身是一个接口:

public interface IOQueue<T> {

    public boolean create();

    public void free();

    // 从队列中获取事件,默认实现是带有阻塞超时的,即当没有事件
// 时会阻塞一段时间,超时就会返回null
public IOEvent<T> getQueuedEvent(boolean isInputEvents); // 注册连接,context是关联对象,类似于附件
public boolean addSocketContext(PushClientSocket socket, T context); // 取消注册
public void deleteSocketContext(PushClientSocket socket); // IOQueue的事件监听是一次性,这是为了防止事件在没有被处理之前,这个事件
// 再次被捕捉到(Java的read/write事件都是这样),因此这个方法会在事件
// 被处理后调用,再次注册。
public boolean rearmSocketForWrite(PushClientSocket socket, T context); // Read事件代表的就是从客户端有数据过来
public boolean rearmSocketForRead(PushClientSocket socket, T context);
}

IOQueueImpl是IOQueue的Java NIO版本的实现。IOQueueImpl会内置一个独立线程以及一个Selector,这里关于注册有一点需要说明:

PushClientSocketImpl的registerSelector方法用于注册socket,这里需要调用wakeup方法。因为如果独立线程会调用Selector的select方法等待新的数据,这个时候直接

调用register方法会被阻塞,因此需要先调用wakeup唤醒selector。

    public SelectionKey registerSelector(Selector selector, int ops,
Object attachment) throws IOException {
selector.wakeup(); // To prevent block when calling register method
return channel.register(selector, ops, attachment);
}

接着说说独立线程监听事件,因为OP_WRITE的特殊性,这里只监听OP_READ事件。

    private void pollEvents(boolean isOutPoll) {
Selector selector;
BlockingQueue<SelectionKey> queue;
if (isOutPoll) {
return;
} else {
selector = this.inPollSelector;
queue = this.inPollQueue;
} List<SelectionKey> cache = new LinkedList<SelectionKey>(); while (isPolling) {
try {
selector.select();
} catch (IOException e) {
continue;
} // 这里调用yield释放控制权是为了刚刚提到的register方法能被顺利执行
Thread.yield(); // Add into cache (Add into the blocking queue directly
// may block so that the selector cannot release the selection
// key in time)
if (selector.isOpen()) {
for (SelectionKey key : selector.selectedKeys()) {
// 前面提到监听事件是一次性的,因此这里取消监听
// 后面再调用rearm方法重新注册
key.cancel();
cache.add(key);
} // Clear the keys
selector.selectedKeys().clear(); // 因为使用了限定长度的BlockingQueue,可能因为队列已满导致阻塞
// 因此先把事件转移到缓存中,释放Selector
queue.addAll(cache);
cache.clear();
} else {
break; // selector closed
}
}
}

顺便说说Demultiplexor获取事件调用的getQueuedEvent方法,这里使用BlockingQueue来实现阻塞等待:

    public IOEvent<PhysicalConnection> getQueuedEvent(boolean isInputEvents) {
final IOEventType type;
final BlockingQueue<SelectionKey> pollQueue; if (isInputEvents) {
type = IOEventType.read;
pollQueue = inPollQueue;
} else {
type = null;
pollQueue = null;
} if (pollQueue == null) {
return null;
} try {
// 设置1秒的超时,这样后面关闭时清空I/O队列的时候不会导致
// Demultiplexor一直被阻塞
SelectionKey key = pollQueue.poll(1000L, TimeUnit.MILLISECONDS);
if (key != null) {
if (key.attachment() instanceof PhysicalConnection) {
return new IOEvent<PhysicalConnection>(type,
(PhysicalConnection)(key.attachment()));
}
}
} catch (InterruptedException e) {
// Ignore
} return null;
}

关于新socket的处理就说这么多吧。

《用Java写一个通用的服务器程序》03 处理新socket的更多相关文章

  1. 《用Java写一个通用的服务器程序》01 综述

    最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...

  2. 《用Java写一个通用的服务器程序》02 监听器

    在一个服务器程序中,监听器的作用类似于公司前台,起引导作用,因此监听器花在每个新连接上的时间应该尽可能短,这样才能保证最快响应. 回到编程本身来说: 1. 监听器最好由单独的线程运行 2. 监听器在接 ...

  3. 五:用JAVA写一个阿里云VPC Open API调用程序

    用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...

  4. 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1

    package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...

  5. CBrother脚本10分钟写一个拯救“小霸王服务器”的程序

    CBrother脚本语言10分钟写一个拯救“小霸王服务器”的程序 到了一家新公司,接手了一坨c++服务器代码,到处内存泄漏,这服务器没有数据库,挂了后重启一下就好了,公司就这么凑活着用了几年了,定时重 ...

  6. (原创)如何使用boost.asio写一个简单的通信程序(一)

    boost.asio相信很多人听说过,作为一个跨平台的通信库,它的性能是很出色的,然而它却谈不上好用,里面有很多地方稍不注意就会出错,要正确的用好asio还是需要花一番精力去学习和实践的,本文将通过介 ...

  7. 用JAVA写一个函数,功能例如以下: 随意给定一组数, 找出随意数相加之后的结果为35(随意设定)的情况

    用JAVA写一个函数.功能例如以下:随意给定一组数,比如{12,60,-8,99,15,35,17,18},找出随意数相加之后的结果为35(随意设定)的情况. 能够递归算法来解: package te ...

  8. 用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载

    用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载,将一个完整的项目进行展示,主要有以下几个部分: 1.servlet部分   Export 2.工具类:TxtFileU ...

  9. 使用JAVA写一个简单的日历

    JAVA写一个简单的日历import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateF ...

随机推荐

  1. java zip解压

    /** * 解压文件到指定目录 * @param zipFile * @param descDir * @author sqdll */@SuppressWarnings("rawtypes ...

  2. 【NOIP2016提高组】 Day2 T1 组合数问题

    题目传送门:https://www.luogu.org/problemnew/show/P2822                 ↓题目大意↓ 数据的极限范围:n,m≤2000,k≤21,数据组数≤ ...

  3. HTTPS和HTTP有什么区别?如何将HTTP转化成HTTPS

    不知道大家有没有注意到输入网址时的HTTP部分,在打开网站进行操作时有时候会自动跳转为HTTPS格式,这是为什么?HTTP与HTTPS到底有什么区别?如何将HTTP转化成HTTPS,针对这些问题,我们 ...

  4. PHP的Session机制

    客户端浏览器和服务器之间通信使用的http协议是一种无状态的协议,在它看来,客户端发起的每个请求都是独立.没有关联的.然而,在实际的Web应用开发中,服务器却经常需要根据用户以往的一些状态或数据对请求 ...

  5. windows平台安装并使用MongoDB

    下载并安装MongoDB,我的安装路径:D:\Program_Files\MongoDB 创建数据库目录,我的目录:D:\mongodb\data\db 命令行下运行MongoDB服务器: 在命令行窗 ...

  6. 微信小程序之页面下拉刷新

    如果需要给单个页面设置下拉刷新功能,不需要写在""window"对象里面,直接在  文件名称.json 里面设置即可 { "enablePullDownRefr ...

  7. Leetcode题解(十二)

    33.Search in Rotated Sorted Array 题目 这道题目如果没有其他要求,直接遍历一遍就可以知道答案,但是,题目给出了是排序了数组,但是数组有可能经过了旋转得到,其解题思路仍 ...

  8. maximum shortest distance

    maximum shortest distance Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/O ...

  9. Pseudoforest(伪最大生成树)

    Pseudoforest Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Total ...

  10. 【JAVA零基础入门系列】Day14 Java对象的克隆

    今天要介绍一个概念,对象的克隆.本篇有一定难度,请先做好心理准备.看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充. 克隆,自然就是将对象重新复制一份,那为什么要用克隆呢 ...