最近在看一些网络服务器的设计, 本文就从起源的角度介绍一下现代网络服务器处理并发连接的思路, 例子就用java提供的API。
此种是最简单的socket服务器了,完全不考虑多连接的问题,主线程一次只处理一个连接,其他的连接由操作系统保持,用的是java socket包的ServerSocket,其构造函数支持的backlog就是TCP连接的等待队列
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]; 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(); } }
由于socketserver的backlog,既等待队列设为50,所以下面的测试通过telnet客户端连接看阻塞式服务器对连接的处理。
Telnet1 先连上测试服务器IP 1.132,并打入tt3:
服务器的console上响应,显示读到的数据:
Telnet2 在telnet1之后连上测试服务器IP 1.132,并打入tt4:
服务器没有显示输出,但其实socket已经连上,服务器线程被阻塞在还没有处理完成的telnet1上,没有返回,所以虽然操作系统已经接受了telnet2的连接,但是应用程序无法处理。windows下可以netstat观察一下连接情况,以下图显示两个1.121的连接已经建立,处于ESTABLISHED状态。
这时停掉telnet1, 既让socket断开,如下:
这时可以看到telnet2之前输入的tt4在服务器端才有响应,说明之前telnet2的输入被缓冲在操作系统的缓冲区。
实际的处理流程就是这个样子,incoming连接被排成队列一个一个由 while(true)中的socketServer.accept方法一个一个处理:
这种只能处理一个连接的服务器程序明显是很鸡肋的,没有服务器会这样处理。那么为什么ServerSocket的构造函数支持这个backlog的缓冲队列呢? 下面在看看JVM代码中封装了什么
前面提到的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类。
进来发现已经是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版的,有点费解,
该方法源码为:
/* * 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"); } }
可以看到后面调用了JVM_Listen这个方法,继续搜索这个方法在哪:
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
backlog属性还是win32 API的底层设施支持的。
本文出自 “祝坤荣” 博客,请务必保留此出处
Java Socket Server的演进 (一),布布扣,bubuko.com
原文:http://www.cnblogs.com/zhukunrong/p/3588570.html