在讲监听器时说过处理的新的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. vue-cli 自定义指令directive 添加验证滑块

    vue项目注册登录页面遇到了一个需要滑块的功能,网上看了很多插件发现都不太好用,于是自己写了一个插件供大家参考: 用的是vue的自定义指令direcive,只需要在需要的组件里放入对应的标签嵌套即可: ...

  2. iOS 记录近期遇到的几个bug

    1. actionSheet与pickerView 不兼容 发生环境:ios 9以上,其他无测试. actionSheet与pickerView在一起使用时,当actionSheet弹出后,紧接着再弹 ...

  3. TsBatis 预览

    前言 在发布了 mybatis-dynamic-query之后感觉基本功能稳定了,而且现在在工作项目开发效率大大提高,而且非常易于维护. 最近准备带几个小朋友以前用typescript 打通前后端,当 ...

  4. Redis 学习笔记-应用场景

    Redis作缓存系统 Redis可以对每个键设置生存时间 可以限定数据占用的最大内存空间,在数据达到空间限制后可以按照一定规则自动淘汰不需要的键. 设置方法: 修改配置文件的maxmemory参数,限 ...

  5. 初识.Net IL

    1.IL基本资料 1.IL概述 IL是.NET框架中中间语言(Intermediate Language)的缩写.使用.NET框架提供的编译器可以直接将源程序编译为.exe或.dll文件,但此时编译出 ...

  6. oracle赋值问题(将同一表中某一字段赋值给另外一个字段的语句)

    将同一表中某一字段赋值给另外一个字段的语句update jxc_ckmx ckmx1 set ckmx1.ddsl = (select ckmx2.sl from jxc_ckmx ckmx2 whe ...

  7. Ionic3 编程-应用启动进入引导页

    新建引导页面 ionic g page welcome 导入组件 修改模版文件:welcome.html 修改样式文件:welcome.scss 安装相关插件: 数据库使用SQLite:ionic c ...

  8. linux学习(二)linux配置网卡以及常见网络问题排查

    实验环境环境:mac,vmware fusion 一.常用的虚拟机网络连接模式. NAT:推荐方式.它可以使你在切换网络环境(比如在工作中和家里)时,不需要修改虚拟主机的配置,而维持正常的上网功能. ...

  9. Rank of Tetris

    Rank of Tetris Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...

  10. EF动态拼接查询

    1.业务中遇到个问题,需要查询如某表的id为1或者2或者3,这里是根据传递参数获取如:传递1,2或者1,3或者1,2,3这里在sql中很好拼接如下: or id= or name=3//3代表另一个字 ...