so部标协议模拟服务器,基于Java的Http服务器几种模式演进

  • Post author:
  • Post category:java


首先抛出问题:

程序1—错误版本

import java.io.IOException;

import java.io.InputStream;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;

public class HttpSimpleServer

{

public void startServer() throws IOException

{

ServerSocket ss = new ServerSocket(10021);

Socket so = ss.accept();

InputStream in = so.getInputStream();

PrintWriter pw = new PrintWriter(so.getOutputStream(),true);

byte[] bytes = new byte[1024];

int num = 0;

while((num = in.read(bytes))!=-1)

{

String str = new String(bytes,0,num);

if(str.trim().length() <= 0)

{

break;

}

System.out.print(str);

}

pw.println(”

今天天气真好

“);

so.close();

ss.close();

}

public static void main(String[] args) throws IOException

{

new HttpSimpleServer().startServer();

}

}

上面的代码是一个基于Java的简单的HTTP服务器,输入访问地址:http://localhost:10021/ 进行访问

0ed016ddb4533b56f806b958bcaf826c.png

服务端输出:

GET /favicon.ico HTTP/1.1

Host: localhost:10021

Connection: keep-alive

User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36

Accept-Encoding: gzip, deflate, sdch

Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

而浏览器一直卡着,看不到输出的信息。

当浏览器访问连接:http://localhost:10021/的时候,服务端将一直阻塞,浏览器收不到响应。

出现无响应的原因在于:

浏览器和你的http服务器建立连接后,先发送请求头信息,然后并不会断开连接,所以read方法就会一直阻塞,等待服务器关闭连接(只有关闭后才会返回-1)。我么知道TCP断开连接使用的是四次分手原则,之所以使用四次分手原则,是因为TCP是个全双工的管道,每一端既可以发送也可以接受,当一端调用socket.close()之后,会发送fin关闭连接的报文(表明自己不再发送数据了,但是可以接收数据),对方收到之后,发送ACK报文,确认自己已经收到所有信息,这样一端关闭了发送,一端关闭了接收,当被动关闭方发送完毕之后,发送fin给主动关闭方,从而全部关闭.

于是Http的TCP连接的主动关闭方一般都是服务端,是在服务端的Response通过TCP发送出去之后再调用socket.close()方法的。read方法会阻塞(流没结束并且没有断开信号),这是主要原因。

public abstract int read()

throws IOException

从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。

子类必须提供此方法的一个实现。

返回:

下一个数据字节;如果到达流的末尾,则返回 -1。

抛出:

其实浏览器此时正在等待你的响应,所以我们需要自己界定请求头的范围。请求头的结束标志是两个连续的换行(这个换行是有标准规定的,必须为\r\n而不是只使用\n),即\r\n\r\n。于是我们在收到这个字符串后就可以不再读取数据,而开始写入数据了。

其他就是该服务程序是一次性的,访问之后就不能再访问了,起码应该写成可以多次访问,进一步可以修改成可以同时多次访问,即多线程访问的。

还有问题就是返回信息没有HTTP响应头部,可能会出现乱码或者浏览器无法识别等问题

程序2–修正版本1:

import java.io.IOException;

import java.io.InputStream;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Scanner;

/**

* @author 作者 E-mail:

* @version 创建时间:2015-8-27 下午09:02:16 类说明

*/

public class HttpSimpleServer2

{

public void startServer() throws IOException

{

// 建立ServerSocket

ServerSocket serso = new ServerSocket(10021);

// 获取客户端对象

Socket so = serso.accept();

// 获取相关流对象

InputStream in = so.getInputStream();

PrintWriter pw = new PrintWriter(so.getOutputStream(), true);

Scanner sc = new Scanner(in);

sc.useDelimiter(“\r\n\r\n”);

if (sc.hasNext())

{

String header = sc.next();

System.out.println(header);

}

// HHTP响应头部信息

pw.print(“HTTP/1.0 200 OK\r\n”);

pw.print(“Content-type:text/html; charset=utf-8\r\n”);

pw.print(“\r\n”);

// HTTP响应内容

pw.println(”

good

“);

sc.close();

so.close();

serso.close();

}

public static void main(String[] args) throws IOException

{

new HttpSimpleServer2().startServer();

}

}

b3e003cca6ac1259d5896aecc7853594.png

服务端输出:

GET / HTTP/1.1

Host: localhost:10021

Connection: keep-alive

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.152 Safari/537.36

Accept-Encoding: gzip, deflate, sdch

Accept-Language: zh-CN,zh;q=0.8,en;q=0.6

缺点:①不能多线程访问 ②只能访问一次,程序就运行结束了

程序3—修正版本2:

import java.io.IOException;

import java.io.InputStream;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Scanner;

import java.util.concurrent.TimeUnit;

/**

* 现在这个请求仍然是阻塞的,单线程的,同一时刻只能有一个程序进行访问

* @author 作者 E-mail:

* @version 创建时间:2015-8-27 下午09:08:26 类说明

*/

public class HttpSimpleServer3

{

public void startServer() throws IOException, InterruptedException

{

// 建立ServerSocket 这里默认的backlog 是50 可以有50个请求在排队等待

ServerSocket serso = new ServerSocket(10021);

while (true)

{

// 获取客户端对象

Socket so = serso.accept();

// 获取相关流对象

InputStream in = so.getInputStream();

PrintWriter pw = new PrintWriter(so.getOutputStream(), true);

Scanner sc = new Scanner(in);

sc.useDelimiter(“\r\n\r\n”);

if (sc.hasNext())

{

String header = sc.next();

System.out.println(header);

}

// HHTP响应头部信息

pw.print(“HTTP/1.0 200 OK\r\n”);

pw.print(“Content-type:text/html; charset=utf-8\r\n”);

pw.print(“\r\n”);

// HTTP响应内容

pw.println(”

good

“);

pw.flush();

Thread.sleep(100000); //单线程程序在多个同时访问的时候就会受限

sc.close();

//在sc关闭之前是写不出去的,因为没有flush

so.close();

}

}

public static void main(String[] args) throws IOException, InterruptedException

{

new HttpSimpleServer3().startServer();

}

}

使用while循环持续监听client连接

缺点:仍旧不能多线程访问

程序4:—-修正版本3

import java.io.IOException;

import java.io.InputStream;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Scanner;

/**

* @author 作者 E-mail:

* @version 创建时间:2015-8-27 下午09:16:50 类说明

*/

class Runner implements Runnable

{

private final Socket socket;

public Runner(Socket socket)

{

this.socket = socket;

}

@Override

public void run()

{

// 获取相关流对象

try

{

InputStream in = socket.getInputStream();

PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);

Scanner sc = new Scanner(in);

sc.useDelimiter(“\r\n\r\n”);

if (sc.hasNext())

{

String header = sc.next();

System.out.println(header);

}

// HTTP响应头部信息

pw.print(“HTTP/1.0 200 OK\r\n”);

pw.print(“Content-type:text/html; charset=utf-8\r\n”);

pw.print(“\r\n”);

// HTTP响应内容

pw.println(”

good

“);

pw.flush();

try

{

Thread.sleep(100000);

}

catch(InterruptedException e)

{

e.printStackTrace();

}

sc.close(); //服务器端的关闭请求。

socket.close();

}

catch(IOException e1)

{

// TODO Auto-generated catch block

e1.printStackTrace();

}

}

}

//实现了多线程的访问,但是效率明显有点低哈哈哈哈

public class HttpSimpleServer4

{

public void startServer() throws IOException

{

// 建立ServerSocket 这里默认的backlog 是50 可以有50个请求在排队等待

ServerSocket serso = new ServerSocket(10021);

while (true)

{

// 获取客户端对象

Socket so = serso.accept();

Runnable runner = new Runner(so);

Thread thread = new Thread(runner);

// run就相当于在本线程当中调用,start才是启动的新线程

thread.start();

}

}

public static void main(String[] args) throws IOException,

InterruptedException

{

new HttpSimpleServer4().startServer();

}

}

实现了多线程访问,

缺点:是阻塞式的,每个访问都要启用一个线程,没有数据输入线程就会阻塞,线程重复创建和销毁

程序5—修正版本4

草稿

import java.io.IOException;

import java.io.InputStream;

import java.io.PrintWriter;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

import java.util.Scanner;

import java.util.Set;

/**

* @author 作者 E-mail:

* @version 创建时间:2015-8-27 下午09:37:25 类说明

*/

interface Handler

{

void doHandle(SelectionKey key);

}

class AcceptHandler implements Handler

{

@Override

public void doHandle(SelectionKey key)

{

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

SocketChannel socketChannel = null;

// 在非阻塞模式下,serverSocketChannel.accept()有可能返回null

// 判断socketChannel是否为null,可以使程序更加健壮,避免NullPointException

try

{

socketChannel = serverSocketChannel.accept();

socketChannel.configureBlocking(false);

if (socketChannel == null)

return;

System.out.println(“接收到客户链接,来自:” + socketChannel.socket().getInetAddress() + “:” + socketChannel.socket().getPort());

RequestHandler requestHandler = new RequestHandler(socketChannel);

socketChannel.register(key.selector(), SelectionKey.OP_READ, requestHandler);

}

catch(IOException ex)

{

ex.printStackTrace();

}

}

}

class RequestHandler implements Handler

{

private SocketChannel socketChannel = null;

public RequestHandler(SocketChannel socketChannel)

{

this.socketChannel = socketChannel;

}

@Override

public void doHandle(SelectionKey key)

{

try

{

ByteBuffer buffer = ByteBuffer.allocate(1024);

socketChannel.read(buffer);

if(buffer.position()!=0)

{

System.out.println(new String(buffer.array()));

}

else

{

// 这里可能出现问题

System.out.println(buffer.toString());

}

buffer.flip();

// System.out.println(buffer)

// buffer.wrap(array)

// // HTTP响应头部信息

// S

// pw.print(“HTTP/1.0 200 OK\r\n”);

// pw.print(“Content-type:text/html; charset=utf-8\r\n”);

// pw.print(“\r\n”);

// // HTTP响应内容

// pw.println(”

good

“);

// pw.flush();

}

catch(IOException ex)

{

ex.printStackTrace();

}

}

}

public class HttpSimpleServer5

{

private Selector selector = null;

private ServerSocketChannel serverSocketChannel = null;

private int port = 10021;

public HttpSimpleServer5() throws IOException

{

// 创建一个Selector对象

selector = Selector.open();

// 创建一个ServerSocketChannel对象

serverSocketChannel = ServerSocketChannel.open();

// 使ServerSocketChannel工作处于非阻塞模式

serverSocketChannel.configureBlocking(false);

// 使得在同一个主机上关闭了服务器程序,紧接着再启动该服务器程序时

// 可以顺利的绑定到相同的端口

serverSocketChannel.socket().setReuseAddress(true);

// 把服务器进程与一个本地端口绑定

serverSocketChannel.socket().bind(new InetSocketAddress(10021));

}

void startServer() throws IOException

{

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, new AcceptHandler());

while (selector.select() > 0)

{

// 获得Selector的selector-keys集合

Set readKeys = selector.selectedKeys();

Iterator it = readKeys.iterator();

while (it.hasNext())

{

SelectionKey key = it.next();

it.remove();

// 由Handler处理连接就绪事件

final Handler handler = (Handler) key.attachment();

handler.doHandle(key);

}

}

}

public static void main(String[] args) throws IOException

{

new HttpSimpleServer5().startServer();

}

}

CentOS 6 搭建SVN支持httpd和svnserve独立服务器两种模式 以及邮件配置

Linux下SVN服务器同时支持Apache的http和svnserve独立服务器两种模式且使用相同的访问权限账号 服务器操作系统:CentOS 6.x 1.在服务器上安装配置SVN服务: 2.配置S …

ftp服务器三种模式

一.匿名开放模式(最不安全) 1.[root@localhost ~]# vim  /etc/vsftpd/vsftpd.conf  (主配置) anonymous_enable=YES    //允 …

基于Java的二叉树的三种遍历方式的递归与非递归实现

二叉树的遍历方式包括前序遍历.中序遍历和后序遍历,其实现方式包括递归实现和非递归实现. 前序遍历:根节点 | 左子树 | 右子树 中序遍历:左子树 | 根节点 | 右子树 后序遍历:左子树 | 右子树 …

第三节:Windows平台部署Asp&period;Net Core应用&lpar;基于IIS和Windows服务两种模式&rpar;

一. 简介 二. 文件系统发布至IIS 三. Web部署发布至IIS 四. FTP发布至IIS 五. Windows服务的形式发布 ! 作       者 : Yaopengfei(姚鹏飞) 博客地址 …

Java实现http服务器(一)

基于Java实现Http服务器有多种多样的方法 一种轻量级的方式是使用JDK内置的com.sun.net.httpserver包下和sun.net.httpserver包下类提供的方法构建,该方法轻便 …

基于JAVA语言的多线程技术

1.简介 多线程技术属于操作系统范围内的知识: 进程与线程 可以这么理解,一个应用程序就是一个进程,在一个进程中包含至少一个线程:进程就是线程的容器,真正工作.处理任务的是线程. 进程是操作系统分配资 …

Spring核心技术(十二)——基于Java的容器配置&lpar;二&rpar;

使用@Configuration注解 @Configuration注解是一个类级别的注解,表明该对象是用来指定Bean的定义的.@Configuration注解的类通过@Bean注解的方法来声明Bea …

基于Java Mina框架的部标808服务器设计和开发

在开发部标GPS平台中,部标808GPS服务器是系统的核心关键,决定了部标平台的稳定性和行那个.Linux服务器是首选,为了跨平台,开发语言选择Java自不待言. 我们为客户开发的部标服务器基于Min …

基于Java Mina框架的部标jt808服务器设计和开发

在开发部标GPS平台中,部标jt808GPS服务器是系统的核心关键,决定了部标平台的稳定性和行那个.Linux服务器是首选,为了跨平台,开发语言选择Java自不待言.需要购买jt808GPS服务器源码 …

随机推荐

oracle数据库导入导出

简单记录下数据泵导出导入expdp .impdp 和 普通导出导入 exp.imp 一.数据泵导出数据库(按用户)步骤: 1.以oracle用户登录oracle所在服务器,创建数据库备份文件目录 &g …

adb 卸载APP命令和杀死APP命令

使用adb 卸载APP命令 在cmd命令行下,直接 输入 adb uninstall 包名 比如 adb uninstall com.ghstudio.BootStartDemo 杀死APP命令 先用 …

浅谈Android应用性能之内存

本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 …

【笔记】UML核心元素

1.参与者 定义:在系统之外与系统交互的某人或某物. 特点:1.可以非人:2.与系统直接交互:3.主动发出动作并获得反馈:4.涉众(stakerholder)的代表 具有两个版型: 1.业务主角(bu …

robotframework的学习笔记(十五)—-robotframework标准库Collections

Collections库是RobotFramework用来处理列表和字典的库,官方文档是这样介绍的:A test library providing keywords for handling lis …

WIN10下Prolific USB-to-Serial Comm Port驱动

最近在安装Prlific的时候,通过电脑自动安装启动后,发现系统无法识别,如下图所示: 还以为是驱动比较老,没有及时更新导致的,去官网下载最新的驱动,发现了这个列表: 这个驱动不支持win10. 后来 …

Basestation函数解析(二)

—恢复内容开始— 这部分从Basestation的RecvDataThread开始,流程为 RecvDataThread->RecvData->Decoder->PostDa …

bzoj 3924 &lbrack;Zjoi2015&rsqb;幻想乡战略游戏——动态点分治(暴力移动找重心)

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3924 度数只有20,所以从一个点暴力枚举其出边,就能知道往哪个方向走. 知道方向之后直接走到 …

JSP九大内置对象,七大动作,三大指令

JSP之九大内置对象 隐藏对象入门探索 Servlet 和JSP中输出数据都需要使用out对象.Servlet 中的out对象是通过getWriter()方法获取的.而JSP中没有定义out对象却可以 …

C&num;的params参数遇到null

params参数支持数组作为参数传入,但并不支持List 定义一个使用params的参数 private static void UseParam(params int[] args) { if (a …