《用Java写一个通用的服务器程序》02 监听器
在一个服务器程序中,监听器的作用类似于公司前台,起引导作用,因此监听器花在每个新连接上的时间应该尽可能短,这样才能保证最快响应。
回到编程本身来说:
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 监听器的更多相关文章
- 《用Java写一个通用的服务器程序》01 综述
最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...
- 《用Java写一个通用的服务器程序》03 处理新socket
在讲监听器时说过处理的新的socket要尽快返回,监听器调用的是ClientFactory的createPhysicalConnection方法,那么就来看这个方法: public boolean c ...
- 五:用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 ...
随机推荐
- Table样式设置
<table class="listTable"> <tr><th width="40px">序号</th>&l ...
- 模块:time,random,os,sys
时间模块 import time # print(time.time()) #时间戳 # print(time.strftime('%Y-%m-%d %X')) #格式化字符 # print(time ...
- uva242,Stamps and Envelope Size
这题紫薯上翻译错了 应该是:如果有多个并列,输出邮票种类最少的那个,如果还有并列,输出最大面值最小的那个 坑了我一个下午 dp[p][q]==1表示可以用不超过q张组成面额p 结合记忆化,p从1开始枚 ...
- 原生javascript跨浏览器常用事件处理
var eventUntil = { getEvent: function (event) {//获取事件 return event ? eve ...
- [ACdream]女神教你字符串——导字符串
Problem Description 正如大家知道的,女神喜欢字符串,而在字符串中,女神最喜欢回文字符串,但是不是所有的字符串都是回文字符串,但是有一些字符串可以进行“求导”来变成回文字符串. 字符 ...
- 【面向对象】详解之JavaScript篇
[重点提前说:面向对象的思想很重要!] 最近开始接触学习后台的PHP语言,在接触到PHP中的面向对象相关思想之后,突然想到之前曾接触的JS中的面向对象思想,无奈记性太差,便去翻了翻资料,花了点时间梳理 ...
- 【前端】主流API-promise解析,js基础。
前言 在js领域,promise出现的时间已经很久了,从jquery的$.get().done().fail() 这样的API开始,到现在的es6默认支持的new Promise(),它的出现无疑使异 ...
- 66、django之模型层(model)--多表相关操作(图书管理小练习)
前面几篇随笔的数据库增删改查操作都是在单表的操作上的,然而现实中不可能都是单表操作,更多的是多表操作,一对一,一对多,多对多的表结构才是我们经常需要处理的,本篇将带我们了解多表操作的一些相关操作.也会 ...
- 有两组随机生成的(0~99999)Int32数据A和B,将A按顺序判断在B中是否存在并记录在Boolean型的C中
http://www.cnblogs.com/asxinyu/p/4504487.html
- HDU 6069 Counting Divisors
Counting Divisors Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Oth ...