在一个服务器程序中,监听器的作用类似于公司前台,起引导作用,因此监听器花在每个新连接上的时间应该尽可能短,这样才能保证最快响应。

回到编程本身来说:

1. 监听器最好由单独的线程运行

2. 监听器在接到新的连接之后,处理连接的方法需要尽快返回

在Java Push Framework中,因为需要同时监听普通客户端和服务器监视服务的客户端,所以定义两种监听器:Acceptor和MonitorAcceptor。

由于两者的关于监听部分的逻辑是相同的,因此首先定义了抽象类Listener来实现了监视器的功能,把处理socket的部分定义为抽象方法。

// 处理socket的抽象方法
protected abstract boolean handleAcceptedSocket(
PushClientSocket clientSocket);

对于监听功能的实现比较简单,还是那三步:create,bind,accept。

    private boolean doListening(InetSocketAddress serverAddr) {
boolean ret = false;
int socketBufferSize = getServerImpl().getServerOptions()
.getSocketBufferSize();
int socketType = getServerImpl().getServerOptions()
.getSocketType();
try {
// Create
serverSocket = SocketFactory.getDefault().createServerSocket(
socketType, socketBufferSize); // Bind
serverSocket.bind(serverAddr); Debug.debug("Start to listen " + serverAddr.getHostName() + ":"
+ serverAddr.getPort()); // Accept
doAccept(); ret = true;
} catch (IOException e) {
e.printStackTrace();
if (serverSocket != null) {
serverSocket.close();
serverSocket = null;
}
} return ret;
}

考虑Java中现在有好几种不同的socket:同步阻塞Socket,同步非阻塞Socket,以及JDK7新添加的异步Socket,如果直接使用Java的Socket类,不方便在不同类型的socket之间切换使用。所以我自定义了PushServerSocket和PushClientSocket两个新接口:

// 对于服务器socket来说,只定义了必须的bind和accept,
// 以及一个不会抛出异常的close。
public interface PushServerSocket { public void bind(InetSocketAddress serverAddr) throws IOException; public PushClientSocket accept() throws IOException; public void close();
}
// 客户端socket接口的定义是C++的风格,因为原来的代码是C++写的,这么定义便于翻译原来的C++代码
public interface PushClientSocket { public String getIP();
public int getPort(); // 这里直接使用Selector其实是有问题的,注定了只能使用NIO的方式
// 后面会考虑修改
public SelectionKey registerSelector(Selector selector, int ops,
Object attachment) throws IOException; public int send(byte[] buffer, int offset, int size) throws IOException; public int recv(byte[] buffer, int offset, int size) throws IOException; public boolean isOpen(); public boolean isConnected(); public void close();
}

两者对应的NIO版本实现是PushServerSocketImpl和PushClientSocketImpl,代码实现比较简单,这里就不贴出来了。

回到Listener,来看doAccept:

    private void doAccept()
{
// Start a new thread
acceptorThread = new Thread(new Runnable() {
public void run() {
while (blnRunning) {
try {
PushClientSocket clientSocket = serverSocket.accept(); Debug.debug("New client from " + clientSocket.getIP()); // Start servicing the client connection
if (!handleAcceptedSocket(clientSocket)) {
clientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
return;
}
}
} }); // Start the thread
acceptorThread.start();
}

这里服务器socket的accept方法实现是阻塞的,这样可以避免不停地轮询,因此在用NIO实现accept时要不能调用configureBlocking设置成非阻塞模式。

后面停止监听时直接调用服务器socket的close方法,accept方法会抛出异常从而跳出循环,结束监听线程的运行。

结束监听时不要忘记使用线程的join方法等待线程结束。

    public void stopListening() {
blnRunning = false; // Close server socket
if (serverSocket != null) {
serverSocket.close();
serverSocket = null;
} // Wait the thread to terminate
if (acceptorThread != null) {
try {
acceptorThread.join();
} catch (InterruptedException e) {
//e.printStackTrace();
} acceptorThread = null;
}
}

Acceptor的实现相对复杂一些,需要记录访问的信息,做一些检查,然后再交给ClientFactory处理:

    protected boolean handleAcceptedSocket(PushClientSocket clientSocket) {
// 记录日志
ClientFactory clientFactoryImpl = serverImpl.getClientFactory();
ServerStats stats = serverImpl.getServerStats();
ServerOptions options = serverImpl.getServerOptions(); stats.addToCumul(ServerStats.Measures.VisitorsSYNs, 1);
// 检查是否达到最大允许访问数
if (clientFactoryImpl.getClientCount() >= options.getMaxConnections()) {
Debug.debug("Reach maximum clients allowed, deny it");
return false;
} //检查IP是否被允许
if (!clientFactoryImpl.isAddressAllowed(clientSocket.getIP())) {
Debug.debug("IP refused: " + clientSocket.getIP());
return false;
}
// 处理socket
return clientFactoryImpl.createPhysicalConnection(clientSocket,
false, listenerOptions);
}

MonitorAcceptor的实现比较简单,直接交给ClientFactory处理就可以。

    protected boolean handleAcceptedSocket(PushClientSocket clientSocket) {
return serverImpl.getClientFactory().createPhysicalConnection(
clientSocket, true, listenerOptions);
}

关于ClientFactory的处理逻辑后面的文章里细讲。

实现一个监听器功能是很容易的,所以可以说的东西不多。

《用Java写一个通用的服务器程序》02 监听器的更多相关文章

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

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

  2. 《用Java写一个通用的服务器程序》03 处理新socket

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

  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. table插件

    //动态添加一行function addRow(){ var firstrow=document.getElementById('firstrow'); var firstCopy=firstrow. ...

  2. 2017上海QCon之旅总结(上)

    本来这个公众号的交流消息中间件相关的技术的.这周去上海参加了QCon,第一次参加这样的技术会议,感受挺多的,所以整理一下自己的一些想法接公众号和大家交流一下. 下面进入正题,从自己参加了的一些分享中挑 ...

  3. HTML配色工具!在线配色工具

    url地址:   https://color.adobe.com/zh/create/color-wheel/ 编者前语: 很多刚开始编写网页的菜鸟,都不知道怎么搭配色彩,刚开始的时候,我也是这样的. ...

  4. 超级详细 一听就会:利用JavaScript jQuery实现图片无限循环轮播(不借助于轮播插件)

    前言 作为一个前端工程师,无论公司是什么行业,无论你做什么端,基本都会遇到一个避不开的动画效果:循环轮播.做轮播并不难,市场上的轮播插件有很多,其中比较著名的是swiper,使用也非常简单.但轮播插件 ...

  5. 双向链表--Java实现

    /*双向链表特点: *1.每个节点含有两个引用,previos和next,支持向前或向后的遍历(除头节点) *2.缺点插入或删除的时候涉及到引用修改的比较多 *注意:下面的双向链表其实也实现了双端链表 ...

  6. java时间格式

    Calendar startdate = Calendar.getInstance(); startdate.setTime(new Date()); //当前时间 startdate.add(Cal ...

  7. SimpleDateFormat 常规用法

    public class SimpleDateFormat extends DateFormat SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (d ...

  8. java调用oracle数据库发布WebService

    package com.hyan.service; import java.io.FileInputStream;import java.sql.Connection;import java.sql. ...

  9. C++获取基类指针所指子类对象的类名

    我们在程序中定义了一个基类,该基类有n个子类,为了方便,我们经常定义一个基类的指针数组,数组中的每一项指向都指向一个子类,那么在程序中我们如何判断这些基类指针是指向哪个子类呢? 关键字 typeid, ...

  10. K - 迷宫问题 POJ - 3984

    定义一个二维数组: int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, ...