最近在看一些网络服务器的设计, 本文就从起源的角度介绍一下现代网络服务器处理并发连接的思路, 例子就用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. mysql用户的创建

    MySQL是采用c/s方式的,需要客户端登录服务器,那么可以登录账号叫做用户,这些用户的信息都存储在mysql数据库(mysql安装时默认有的一个数据库)中的user表中, 比如用户的名称,用户的密码 ...

  2. hasOwnProperty和in

    返回一个布尔值,指出一个对象是否具有指定名称的属性. hasOwnProperty 此方法无法检查该对象的原型链中是否具有该属in 可以检查原型链中是否具有该属

  3. HTML5+CSS3学习笔记(二) 页面布局:HTML5新元素及其特性

    HTML5的语义化标签以及属性,可以让开发者非常方便地实现清晰的web页面布局,加上CSS3的效果渲染,快速建立丰富灵活的web页面显得非常简单. 本次学习HTML5的新标签元素有: <head ...

  4. 解析 Json 相关

    statusJson sj = new statusJson() { ShipmentNum = "555555", Status1 = "05", Wareh ...

  5. http statusCode(状态码) 200、300、400、500序列

    201-206都表示服务器成功处理了请求的状态代码,说明网页可以正常访问.        200(成功)  服务器已成功处理了请求.通常,这表示服务器提供了请求的网页.        201(已创建) ...

  6. laravel中如何写验证码文件,并防止中文乱码.

    本例为生成中文验证码,专为laravel而生. //控制器: public function getVcode(Request $request){ $width=845; $height=125; ...

  7. 站点图标favicon.ico

    favicon.ico图标: 网站的favicon.ico需要一次额外的http请求,无论你是否有在html里面添加 link链接 <link rel="shortcut icon&q ...

  8. i love matlab

    新手上路~话说图像修复熟么的真的很有意思~忽的想起NG讲的一个笑话:它让我赚了很多钱,买了车子,买了房子,so what's that? It's Matlab <( ̄3 ̄)> 前两天一直 ...

  9. log4j写入数据库

    转发自http://www.cnblogs.com/adolfmc/p/3432720.html Log4j 配置数据库连接池(将日志信息保存到数据库) org.apache.log4j.jdbc.J ...

  10. 基于AutoCAD的ObjectARX之NET扩展(mcnetarx)-AcdbEntMake

    1.创建一个结果缓冲区. 2.调用AcdbEntMake创建对象. 示例: ' 创建文字实体 Dim rb As ResultBuffer = New ResultBuffer rb.Add(New ...