《用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的处理逻辑后面的文章里细讲。

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

时间: 2024-08-02 14:10:42

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

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

最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个新的程序就诞生了.如此这般做了几回,就萌生了一个想法:是不是可以做一个通用的服务器程序,每次只要实现很少的代码就可以构建的一个新的服务器程序? 巧的是在用C++写代码的时候,我刚好碰到过一个叫做Push Framework的开源项目(在这里可以找到:www.pushframework.com),就是

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

在讲监听器时说过处理的新的socket要尽快返回,监听器调用的是ClientFactory的createPhysicalConnection方法,那么就来看这个方法: public boolean createPhysicalConnection(PushClientSocket socket, boolean isObserver, ListenerOptions listenerOptions) { PhysicalConnectionPool thePhysicalConnectionPo

五:用JAVA写一个阿里云VPC Open API调用程序

用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软件管理自动化管理网络资源是一件显著提升运维效率和网络生产力的事情.产品经理教你写代码系列文章的目标是不懂代码的网络工程师能一步一步的学会用API管理网络. 另外通过文章标题大家也可以看出来,产品经理教你写代码肯定是一个业余班,里面的代码很多写的都不规范,可能也有很多Bug.专业选手可以参考的有限,请

用JAVA写一个视频播放器

前言 跳过废话,直接看正文 当年入坑java是因为它的跨平台优势.那时我认为,"编写一次,处处运行."这听上去多么牛逼,应该是所有语言发展的终极之道,java势必会一统天下. 然而事实证明,那时的我还是太年轻. 正所谓鱼和熊掌不可兼得,若要享受跨平台带来的方便,便不可避免地要接受性能上的不足.事实上,java一直在致力于提高虚拟机的性能(JIT等技术),但面对对实时计算性能要求很高或涉及到用硬件优化的任务(视频的硬件编码.解码)时,仍远远比不上c或c++.因此,很少能够看到有人用jav

用java写一个远程视频监控系统,实时监控(类似直播)我想用RPT协议,不知道怎么把RPT协议集成到项目中

我最近在用java写一个远程视频监控系统,实时监控(类似直播)我想用RPT协议,不知道怎么把RPT协议集成到项目中,第一次写项目,写过这类项目的多多提意见,哪方面的意见都行,有代码或者demo的求赏给我,谢谢

DuiVision开发教程(2)-如何写一个简单的界面程序

基于DuiVision界面库开发的界面程序主要包括如下几部分内容: 1.资源定义,包括图片资源.各个窗口界面的xml定义文件 2.事件处理类代码,用于处理界面响应消息 3.其他业务逻辑代码 下面举例说明如何写一个简单的界面程序. 第一步:使用VC向导创建一个有两个tab页面的DuiVision工程 向导生成的解决方案文件如下: 默认有两个工程,分别是DuiVision库和应用程序工程.自动生成的代码目录中bin目录下的内容那个如下,bkimg目录存放窗口背景图片,skins目录存放图片资源,xm

用JAVA写一个函数,功能如下: 任意给定一组数, 找出任意数相加之后的结果为35(任意设定)的情况

用JAVA写一个函数.功能如下:任意给定一组数,例如{12,60,-8,99,15,35,17,18},找出任意数相加之后的结果为35(任意设定)的情况. 可以递归算法来解: package test1; import java.util.Arrays; public class demo { public static void main(String[] args) { String str = "12,60,-8,99,15,35,17,18,8,10,11,12"; int s

感觉Java写一个窗口真心简单,很易上手

上学期学习了Java ,感觉Java写一个窗口真心简单,很易上手,也就难怪很多开发人员选择Java作为自己的开发编程语言.但是由于自身对windows的热爱,让我觉得c.c++语言才是我亲睐的编程语言,虽然难度大些,但是这才能体现能力所在.其实之前一直想自学一下win32,但是由于时间的显示和种种原因而耽搁了,于是今年暑假决心深入学习win32. 在学习过程中呢,我会在此留下自己的学习心得,当做自己的笔记.初学者可以借鉴,高手可以多多指教,呵呵…… 好了,今天开始做第一课的笔记吧: 学习Win3

一个误解: 单个服务器程序可承受最大连接数“理论”上是“65535”

from:http://www.cnblogs.com/tianzhiliang/archive/2011/06/13/2079564.html 一个误解: 单个服务器程序可承受最大连接数“理论”上是“65535” 2011-06-13 11:47 by 田志良, 5321 阅读, 8 评论, 收藏, 编辑 请注意,这里有两个词分别被我标记上了引号,一个是“理论”,一个是“65535”.强调“理论”这个词,是想特别明确误解者的意思:就是说,这个值是不可能被打破的,是铁板钉丁的.而65535这个数