首页 > 其他 > 详细

Java Socket Server的演进 (一)

时间:2014-03-09 04:15:29      阅读:478      评论:0      收藏:0      [点我收藏+]

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

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

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

bubuko.com,布布扣
* 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);
    }
bubuko.com,布布扣

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

bubuko.com,布布扣
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];

                while (scan.hasNextLine()){
                    System.out.println("start read.");
                    String str = scan.nextLine();
                    System.out.println(str);

                }
                socket.getOutputStream().write(new String("HTTP-Version Status-Code Reason-Phrase CRLF\r\nHTTP/1.1 200 OK\r\n").getBytes());
                Thread.sleep(1000);
                log.info("awake.");
                socket.close();
            } catch (IOException e) {
                log.severe(e.getMessage());  //To change body of catch statement use File | Settings | File Templates.
            }
        }
    }

     public static void main(String[] args){
         try{
             SocketServer ss = new SocketServer();
             ss.service();
         }       catch (Exception e){
             e.printStackTrace();
         }


     }
bubuko.com,布布扣

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

 

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

Backlog属性的本地代码

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

bubuko.com,布布扣
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;
    }

...
}
bubuko.com,布布扣

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

bubuko.com,布布扣

进来发现已经是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版的,有点费解, 
bubuko.com,布布扣
该方法源码为:
bubuko.com,布布扣
/*
 * 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;

    if (IS_NULL(fdObj)) {
        JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
                        "Socket closed");
        return;
    } else {
        fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID);
    }

    /*
     * Workaround for bugid 4101691 in Solaris 2.6. See 4106600.
     * If listen backlog is Integer.MAX_VALUE then subtract 1.
     */
    if (count == 0x7fffffff)
        count -= 1;

    if (JVM_Listen(fd, count) == JVM_IO_ERR) {
        NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException",
                       "Listen failed");
    }
}
bubuko.com,布布扣

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

bubuko.com,布布扣

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

bubuko.com,布布扣

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

 

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

Java Socket Server的演进 (一),布布扣,bubuko.com

Java Socket Server的演进 (一)

原文:http://www.cnblogs.com/zhukunrong/p/3588570.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!