《用Java写一个通用的服务器程序》03 处理新socket
在讲监听器时说过处理的新的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的更多相关文章
- 《用Java写一个通用的服务器程序》01 综述
最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...
- 《用Java写一个通用的服务器程序》02 监听器
在一个服务器程序中,监听器的作用类似于公司前台,起引导作用,因此监听器花在每个新连接上的时间应该尽可能短,这样才能保证最快响应. 回到编程本身来说: 1. 监听器最好由单独的线程运行 2. 监听器在接 ...
- 五:用JAVA写一个阿里云VPC Open API调用程序
用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...
- 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1
package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...
- CBrother脚本10分钟写一个拯救“小霸王服务器”的程序
CBrother脚本语言10分钟写一个拯救“小霸王服务器”的程序 到了一家新公司,接手了一坨c++服务器代码,到处内存泄漏,这服务器没有数据库,挂了后重启一下就好了,公司就这么凑活着用了几年了,定时重 ...
- (原创)如何使用boost.asio写一个简单的通信程序(一)
boost.asio相信很多人听说过,作为一个跨平台的通信库,它的性能是很出色的,然而它却谈不上好用,里面有很多地方稍不注意就会出错,要正确的用好asio还是需要花一番精力去学习和实践的,本文将通过介 ...
- 用JAVA写一个函数,功能例如以下: 随意给定一组数, 找出随意数相加之后的结果为35(随意设定)的情况
用JAVA写一个函数.功能例如以下:随意给定一组数,比如{12,60,-8,99,15,35,17,18},找出随意数相加之后的结果为35(随意设定)的情况. 能够递归算法来解: package te ...
- 用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载
用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载,将一个完整的项目进行展示,主要有以下几个部分: 1.servlet部分 Export 2.工具类:TxtFileU ...
- 使用JAVA写一个简单的日历
JAVA写一个简单的日历import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateF ...
随机推荐
- Windows-universal-samples-master示例 XamlCommanding
Windows-universal-samples-master XamlCommanding 运行默认如果是 ARM会出现没有引用System,只要在调试选择CPU为PC的就好 默认 选择PC平台 ...
- 正六边形网格化(Hexagonal Grids)原理与实现
在路径规划.游戏设计栅格法应用中,正六边形网格不如矩形网格直接和常见,但是正六边形具有自身的应用特点,更适用于一些特殊场景中,比如旷阔的海洋.区域或者太空.本文主要讲述如何对正六边形进行几何学分析.网 ...
- C陷阱和缺陷学习笔记
这段时间把<C陷阱和缺陷>看了,没时间自己写总结.就转一下别人的学习笔记吧http://bbs.chinaunix.net/thread-749888-1-1.html Chapter 1 ...
- USACO奶牛博览会(DP)
Description 奶牛想证明他们是聪明而风趣的.为此,贝西筹备了一个奶牛博览会,她已经对N头奶牛进行了面试,确定了每头奶牛的智商和情商. 贝西有权选择让哪些奶牛参加展览.由于负的智商或情商会造成 ...
- 浏览器缓存相关HTTP头部字段
1.Cache-Control/Pragma 2.Expires 3.Last-Modified/Etag
- request的getServletPath(),getContextPath(),getRequestURI(),getRealPath("/")区别
假定你的web application 名称为news,你在浏览器中输入请求路径: http://localhost:8080/news/main/list.jsp 则执行下面向行代码后打印出如下结果 ...
- 轻松学会ES6新特性之生成器
生成器虽然是ES6最具魔性的新特性,但也是最难懂得的一节,笔者写了大量的实例来具体化这种抽象的概念,能够让人一看就懂,目的是希望别人不要重复或者减少笔者学习生成器的痛苦经历. 在说具体的ES6生成器之 ...
- [C#]使用ILMerge将源DLL合并到目标EXE(.NET4.6.2)
本文为原创文章,如转载,请在网页明显位置标明原文名称.作者及网址,谢谢! 本文主要是使用微软的ILMerge工具将源DLL合并到目标EXE,因此,需要下载以下工具: https://www.micro ...
- Lua与javascript的差异
Lua与javascript的差异 2010-03-08 Lua模拟器js方案 1.语法级模拟 lua与js语言差异 1.1注释 js 为//,lua为--. 1.2变量 js利用var来声明全局变量 ...
- empty()和remove()的区别
这两个都是删除元素,但是两者还是有区别的. remove()这个方法呢是删除被选元素的所有文本和子元素,当然包括被选元素自己. 而empty()呢,被选元素自己是不会被删除的. 比如: <div ...