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

回到编程本身来说:

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

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

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

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

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

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

  1. private boolean doListening(InetSocketAddress serverAddr) {
  2. boolean ret = false;
  3. int socketBufferSize = getServerImpl().getServerOptions()
  4. .getSocketBufferSize();
  5. int socketType = getServerImpl().getServerOptions()
  6. .getSocketType();
  7. try {
  8. // Create
  9. serverSocket = SocketFactory.getDefault().createServerSocket(
  10. socketType, socketBufferSize);
  11.  
  12. // Bind
  13. serverSocket.bind(serverAddr);
  14.  
  15. Debug.debug("Start to listen " + serverAddr.getHostName() + ":"
  16. + serverAddr.getPort());
  17.  
  18. // Accept
  19. doAccept();
  20.  
  21. ret = true;
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. if (serverSocket != null) {
  25. serverSocket.close();
  26. serverSocket = null;
  27. }
  28. }
  29.  
  30. return ret;
  31. }

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

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

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

回到Listener,来看doAccept:

  1. private void doAccept()
  2. {
  3. // Start a new thread
  4. acceptorThread = new Thread(new Runnable() {
  5. public void run() {
  6. while (blnRunning) {
  7. try {
  8. PushClientSocket clientSocket = serverSocket.accept();
  9.  
  10. Debug.debug("New client from " + clientSocket.getIP());
  11.  
  12. // Start servicing the client connection
  13. if (!handleAcceptedSocket(clientSocket)) {
  14. clientSocket.close();
  15. }
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. return;
  19. }
  20. }
  21. }
  22.  
  23. });
  24.  
  25. // Start the thread
  26. acceptorThread.start();
  27. }

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

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

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

  1. public void stopListening() {
  2. blnRunning = false;
  3.  
  4. // Close server socket
  5. if (serverSocket != null) {
  6. serverSocket.close();
  7. serverSocket = null;
  8. }
  9.  
  10. // Wait the thread to terminate
  11. if (acceptorThread != null) {
  12. try {
  13. acceptorThread.join();
  14. } catch (InterruptedException e) {
  15. //e.printStackTrace();
  16. }
  17.  
  18. acceptorThread = null;
  19. }
  20. }

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

  1. protected boolean handleAcceptedSocket(PushClientSocket clientSocket) {
  2. // 记录日志
  3. ClientFactory clientFactoryImpl = serverImpl.getClientFactory();
  4. ServerStats stats = serverImpl.getServerStats();
  5. ServerOptions options = serverImpl.getServerOptions();
  6.  
  7. stats.addToCumul(ServerStats.Measures.VisitorsSYNs, 1);
  8. // 检查是否达到最大允许访问数
  9. if (clientFactoryImpl.getClientCount() >= options.getMaxConnections()) {
  10. Debug.debug("Reach maximum clients allowed, deny it");
  11. return false;
  12. }
  13.  
  14. //检查IP是否被允许
  15. if (!clientFactoryImpl.isAddressAllowed(clientSocket.getIP())) {
  16. Debug.debug("IP refused: " + clientSocket.getIP());
  17. return false;
  18. }
  19. // 处理socket
  20. return clientFactoryImpl.createPhysicalConnection(clientSocket,
  21. false, listenerOptions);
  22. }

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

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

关于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样式设置

    <table class="listTable"> <tr><th width="40px">序号</th>&l ...

  2. 模块:time,random,os,sys

    时间模块 import time # print(time.time()) #时间戳 # print(time.strftime('%Y-%m-%d %X')) #格式化字符 # print(time ...

  3. uva242,Stamps and Envelope Size

    这题紫薯上翻译错了 应该是:如果有多个并列,输出邮票种类最少的那个,如果还有并列,输出最大面值最小的那个 坑了我一个下午 dp[p][q]==1表示可以用不超过q张组成面额p 结合记忆化,p从1开始枚 ...

  4. 原生javascript跨浏览器常用事件处理

    var eventUntil = {             getEvent: function (event) {//获取事件                 return event ? eve ...

  5. [ACdream]女神教你字符串——导字符串

    Problem Description 正如大家知道的,女神喜欢字符串,而在字符串中,女神最喜欢回文字符串,但是不是所有的字符串都是回文字符串,但是有一些字符串可以进行“求导”来变成回文字符串. 字符 ...

  6. 【面向对象】详解之JavaScript篇

    [重点提前说:面向对象的思想很重要!] 最近开始接触学习后台的PHP语言,在接触到PHP中的面向对象相关思想之后,突然想到之前曾接触的JS中的面向对象思想,无奈记性太差,便去翻了翻资料,花了点时间梳理 ...

  7. 【前端】主流API-promise解析,js基础。

    前言 在js领域,promise出现的时间已经很久了,从jquery的$.get().done().fail() 这样的API开始,到现在的es6默认支持的new Promise(),它的出现无疑使异 ...

  8. 66、django之模型层(model)--多表相关操作(图书管理小练习)

    前面几篇随笔的数据库增删改查操作都是在单表的操作上的,然而现实中不可能都是单表操作,更多的是多表操作,一对一,一对多,多对多的表结构才是我们经常需要处理的,本篇将带我们了解多表操作的一些相关操作.也会 ...

  9. 有两组随机生成的(0~99999)Int32数据A和B,将A按顺序判断在B中是否存在并记录在Boolean型的C中

    http://www.cnblogs.com/asxinyu/p/4504487.html

  10. HDU 6069 Counting Divisors

    Counting Divisors Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Oth ...