zoukankan      html  css  js  c++  java
  • Java Socket Server的演进 (一)

    最近在看一些网络服务器的设计, 本文就从起源的角度介绍一下现代网络服务器处理并发连接的思路, 例子就用java提供的API。

    1.单线程同步阻塞式服务器及操作系统API

    此种是最简单的socket服务器了,完全不考虑多连接的问题,主线程一次只处理一个连接,其他的连接由操作系统保持,用的是java socket包的ServerSocket,其构造函数支持的backlog就是TCP连接的等待队列

    * The maximum queue length for incoming connection indications (a
    * request to connect) is set to the <code>backlog</code> parameter. If
    * a connection indication arrives when the queue is full, the
    * connection is refused.
    public ServerSocket(int port, int backlog) throws IOException {
        this(port, backlog, null);
        }

    server的样例代码,纯测试用途,不考虑优雅问题了:

    public class SocketServer{
        Logger log = getLogger("SocketServer");
        ServerSocket server = null;
        public  SocketServer() throws IOException {
            server = new  ServerSocket(8080,50);
            System.out.println("Server start... listen on:8080" + server.getInetAddress().toString());
        }
        public void service() throws InterruptedException {
            while(true)       {
                try {
                    log.info("wait connection...");
                    Socket socket = server.accept();
                    log.info(socket.toString());
                    InputStream is = socket.getInputStream();
                    Scanner scan = new Scanner(is);
                    byte[] buffer = new byte[1024];
    
                </span><span style="color: #0000ff">while</span><span style="color: #000000"> (scan.hasNextLine()){
                    System.out.println(</span>&quot;start read.&quot;<span style="color: #000000">);
                    String str </span>=<span style="color: #000000"> scan.nextLine();
                    System.out.println(str);
    
                }
                socket.getOutputStream().write(</span><span style="color: #0000ff">new</span> String(&quot;HTTP-Version Status-Code Reason-Phrase CRLF
    HTTP/1.1 200 OK
    &quot;<span style="color: #000000">).getBytes());
                Thread.sleep(</span>1000<span style="color: #000000">);
                log.info(</span>&quot;awake.&quot;<span style="color: #000000">);
                socket.close();
            } </span><span style="color: #0000ff">catch</span><span style="color: #000000"> (IOException e) {
                log.severe(e.getMessage());  </span><span style="color: #008000">//</span><span style="color: #008000">To change body of catch statement use File | Settings | File Templates.</span>
    

    }
    }
    }

     </span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> main(String[] args){
         </span><span style="color: #0000ff">try</span><span style="color: #000000">{
             SocketServer ss </span>= <span style="color: #0000ff">new</span><span style="color: #000000"> SocketServer();
             ss.service();
         }       </span><span style="color: #0000ff">catch</span><span style="color: #000000"> (Exception e){
             e.printStackTrace();
         }
    
    
     }</span></pre>
    

    由于socketserver的backlog,既等待队列设为50,所以下面的测试通过telnet客户端连接看阻塞式服务器对连接的处理。

    Telnet1 先连上测试服务器IP 1.132,并打入tt3:

    image_thumb6

    服务器的console上响应,显示读到的数据:

    image_thumb8

    Telnet2 在telnet1之后连上测试服务器IP 1.132,并打入tt4:

    image_thumb15

    服务器没有显示输出,但其实socket已经连上,服务器线程被阻塞在还没有处理完成的telnet1上,没有返回,所以虽然操作系统已经接受了telnet2的连接,但是应用程序无法处理。windows下可以netstat观察一下连接情况,以下图显示两个1.121的连接已经建立,处于ESTABLISHED状态。

    image_thumb4

    这时停掉telnet1, 既让socket断开,如下:

    image_thumb11

    这时可以看到telnet2之前输入的tt4在服务器端才有响应,说明之前telnet2的输入被缓冲在操作系统的缓冲区。

    image_thumb13

    实际的处理流程就是这个样子,incoming连接被排成队列一个一个由 while(true)中的socketServer.accept方法一个一个处理:

    image_thumb21

    这种只能处理一个连接的服务器程序明显是很鸡肋的,没有服务器会这样处理。那么为什么ServerSocket的构造函数支持这个backlog的缓冲队列呢? 下面在看看JVM代码中封装了什么

    Backlog属性的本地代码

    前面提到的backlog属性,JVM如何将这个队列与本地操作系统API结合呢,先看下构造ServerSocket的部分代码:

    public void bind(SocketAddress endpoint, int backlog) throws IOException {
    ...
        if (backlog < 1)
          backlog = 50;
        try {
            SecurityManager security = System.getSecurityManager();
            if (security != null)
            security.checkListen(epoint.getPort());
            getImpl().bind(epoint.getAddress(), epoint.getPort());
            getImpl().listen(backlog);
            bound = true;
        }
    

    ...
    }

    可以看到调用了SocketImpl的listen方法,这个listen方法又做了什么呢?先看一下,SocketImpl类有几个实现类,这里直接根据名字直接选了PlainSocketImpl类。

    image_thumb

    进来发现已经是native代码了:

    private native void socketListen(int count)
        throws IOException;

    想看JVM干了什么,只能找OpenJDK的source代码了。(代码在linux下获取比较简单,

    hg clone http://hg.openjdk.java.net/jdk7/jdk7 jdk7_tl;
    运行./get_source.sh,不细说了, 不过找这个native方法的时候遇到点问题,发现没有windows版的同名c代码,只有solaris版的,有点费解, 
    image_thumb2
    该方法源码为:
    /*
     * Class:     java_net_PlainSocketImpl
     * Method:    socketListen
     * Signature: (I)V
     */
    JNIEXPORT void JNICALL
    Java_java_net_PlainSocketImpl_socketListen (JNIEnv *env, jobject this,
                                                jint count)
    {
        /* this FileDescriptor fd field */
        jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID);
        /* fdObj's int fd field */
        int fd;
    
    </span><span style="color: #0000ff">if</span><span style="color: #000000"> (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG </span><span style="color: #800000">&quot;</span><span style="color: #800000">SocketException</span><span style="color: #800000">&quot;</span><span style="color: #000000">,
                        </span><span style="color: #800000">&quot;</span><span style="color: #800000">Socket closed</span><span style="color: #800000">&quot;</span><span style="color: #000000">);
        </span><span style="color: #0000ff">return</span><span style="color: #000000">;
    } </span><span style="color: #0000ff">else</span><span style="color: #000000"> {
        fd </span>= (*env)-&gt;<span style="color: #000000">GetIntField(env, fdObj, IO_fd_fdID);
    }
    
    </span><span style="color: #008000">/*</span><span style="color: #008000">
     * Workaround for bugid 4101691 in Solaris 2.6. See 4106600.
     * If listen backlog is Integer.MAX_VALUE then subtract 1.
     </span><span style="color: #008000">*/</span>
    <span style="color: #0000ff">if</span> (count == <span style="color: #800080">0x7fffffff</span><span style="color: #000000">)
        count </span>-= <span style="color: #800080">1</span><span style="color: #000000">;
    
    </span><span style="color: #0000ff">if</span> (JVM_Listen(fd, count) ==<span style="color: #000000"> JVM_IO_ERR) {
        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG </span><span style="color: #800000">&quot;</span><span style="color: #800000">SocketException</span><span style="color: #800000">&quot;</span><span style="color: #000000">,
                       </span><span style="color: #800000">&quot;</span><span style="color: #800000">Listen failed</span><span style="color: #800000">&quot;</span><span style="color: #000000">);
    }
    

    }

    可以看到后面调用了JVM_Listen这个方法,继续搜索这个方法在哪:

    image_thumb5

    OK,这个路径下hotspot/src/share/vm/prims/jvm.cpp 的包装方法看起来很像,找过来看一下内容:

    JVM_LEAF(jint, JVM_Listen(jint fd, jint count))
      JVMWrapper2("JVM_Listen (0x%x)", fd);
      //%note jvm_r6
      return os::listen(fd, count);
    JVM_END

    发现是调用os::listen的,再到这个文件的头上找os的引用:

    #include "runtime/os.hpp"

    因为我是windows系统,绕了一圈,还是直接msdn上找到winsock的listen方法,

    http://msdn.microsoft.com/en-us/library/windows/desktop/ms739168(v=vs.85).aspx

    image_thumb9

    backlog属性还是win32 API的底层设施支持的。

    本文出自 “祝坤荣” 博客,请务必保留此出处

    本文出自 “祝坤荣” 博客,请务必保留此出处
  • 相关阅读:
    Android 源代码在线查看
    Android天气预报程序开发
    为自己的网站写个api接口
    Windows Server 2012改造成Windows8的方法(新增解决网络卡)
    完整java开发中JDBC连接数据库代码和步骤
    RF频偏
    通信系统架构,RF架构
    RF 速率与引导码preamble关系
    ubuntu虚拟机共享无线网上网
    win7下AdHoc网络设置共享外网上网
  • 原文地址:https://www.cnblogs.com/zhukunrong/p/3588570.html
Copyright © 2011-2022 走看看