最近在看一些网络服务器的设计, 本文就从起源的角度介绍一下现代网络服务器处理并发连接的思路, 例子就用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\r\nHTTP/1.1 200 OK\r\n&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:

服务器的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属性的本地代码

前面提到的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;
</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这个方法,继续搜索这个方法在哪:

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的演进 (一)的更多相关文章

  1. Java Socket TCP编程(Server端多线程处理)

    package com; import java.io.*; import java.net.Socket; /** * Socket Client * <p> * Created by ...

  2. Caused by: java.lang.ClassNotFoundException: org.springframework.web.socket.server.standard.ServerEndpointExporter

    Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/web/socket/ ...

  3. java网络编程socket\server\TCP笔记(转)

    java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04|  分类: Socket |  标签:java  |举报|字号 订阅     1 TCP的开销 a ...

  4. JAVA通信系列一:Java Socket技术总结

    本文是学习java Socket整理的资料,供参考. 1       Socket通信原理 1.1     ISO七层模型 1.2     TCP/IP五层模型 应用层相当于OSI中的会话层,表示层, ...

  5. java socket收发http协议内容

    来自:https://www.oschina.net/code/snippet_2009881_48232 import java.io.BufferedReader; import java.io. ...

  6. java socket编程(网络编程)

    一,网络编程中两个主要的问题 一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输. 在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可 ...

  7. JAVA Socket 编程学习笔记(一)

    1. Socket 通信简介及模型 Java Socket 可实现客户端--服务器间的双向实时通信.java.net包中定义的两个类socket和ServerSocket,分别用来实现双向连接的cli ...

  8. 简单的java socket 示例

    一.搭建服务器端 a).创建ServerSocket对象绑定监听端口. b).通过accept()方法监听客户端的请求. c).建立连接后,通过输入输出流读取客户端发送的请求信息. d).通过输出流向 ...

  9. JAVA Socket超时浅析

    JAVA Socket超时浅析 套接字或插座(socket)是一种软件形式的抽象,用于表达两台机器间一个连接的"终端".针对一个特定的连接,每台机器上都有一个"套接字&q ...

随机推荐

  1. Bomb

    Description The counter-terrorists found a time bomb in the dust. But this time the terrorists impro ...

  2. javascript高级特性

    01_javascript相关内容02_函数_Arguments对象03_函数_变量的作用域04_函数_特殊函数05_闭包_作用域链&闭包06_闭包_循环中的闭包07_对象_定义普通对象08_ ...

  3. 探 寻 宝 藏--- DP

    题目描述 传说HMH大沙漠中有一个M*N迷宫,里面藏有许多宝物.某天,Dr.Kong找到了迷宫的地图,他发现迷宫内处处有宝物,最珍贵的宝物就藏在右下角,迷宫的进出口在左上角.当然,迷宫中的通路不是平坦 ...

  4. html5 canvas绘画时钟

    本示例使用HTML5 canvas,模拟显示了一个时钟, 请使用支持HTML5的浏览器预览效果: HTML部分: <!DOCTYPE html> <html lang="e ...

  5. 解决php中json_decode的异常JSON_ERROR_CTRL_CHAR

    该字符中含了ASCII码ETB控制符,即\x17导致json解析失败 (截图中显示ETB是因为用了Sublime text2) 解决方法如下:去掉0-31的控制符再进行decode $result = ...

  6. C++中未初始化的bool值的问题

    原创文件,欢迎阅读,禁止转载. 问题描述 你见过一个这样的bool值吗,判断 var 和 !var 都是成立的,今天被我遇到了,是在一个坑里遇到的.今天调试了一个程序,发送一个网络消息,结果总是得不到 ...

  7. 近期C#项目中总结

    1. 读写文件操作 using (file = new System.IO.StreamReader(inputfile)) { using (outfile = new System.IO.Stre ...

  8. 基于VC的ACM音频编程接口压缩Wave音频(二)

    (二)获取CODECs 的 信 息  ACM 的API 函 数 定 义 在 头 文 件msacm.h 中, 除 此 之 外, 对ACM 编 程 还 必 须 包 含 头 文 件mmsystem.h,mm ...

  9. js中的变量小例子

    s中的变量function foo(){ n=99;}alert(n);//undefined,因为没有调用该函数 function foo(){ n=99;}foo();alert(n);//99, ...

  10. ASP.Net MVC跳转,分为form的submit提交跳转和ajax跳转

    1,用jquery ajax跳转的话,需要在前台用window.location("跳转网址")来跳转,在success后使用 2,用原声的form的submit来跳转,如下图 3 ...