基于Java的Http服务器几种模式演进
首先抛出问题:
程序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("<font color='red' size='7'> 今天天气真好</font>");
- so.close();
- ss.close();
- }
- public static void main(String[] args) throws IOException
- {
- new HttpSimpleServer().startServer();
- }
- }
上面的代码是一个基于Java的简单的HTTP服务器,输入访问地址:http://localhost:10021/ 进行访问
服务端输出:
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
。 - 抛出:
IOException
- 如果发生 I/O 错误。
其实浏览器此时正在等待你的响应,所以我们需要自己界定请求头的范围。请求头的结束标志是两个连续的换行(这个换行是有标准规定的,必须为\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("<font color='red' size='7'>good</font>");
- sc.close();
- so.close();
- serso.close();
- }
- public static void main(String[] args) throws IOException
- {
- new HttpSimpleServer2().startServer();
- }
- }
服务端输出:
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("<font color='red' size='7'>good</font>");
- 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("<font color='red' size='7'>good</font>");
- 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("<font color='red' size='7'>good</font>");
- // 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<SelectionKey> readKeys = selector.selectedKeys();
- Iterator<SelectionKey> 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();
- }
- }
基于Java的Http服务器几种模式演进的更多相关文章
- 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.Net Core应用(基于IIS和Windows服务两种模式)
一. 简介 二. 文件系统发布至IIS 三. Web部署发布至IIS 四. FTP发布至IIS 五. Windows服务的形式发布 ! 作 者 : Yaopengfei(姚鹏飞) 博客地址 ...
- Java实现http服务器(一)
基于Java实现Http服务器有多种多样的方法 一种轻量级的方式是使用JDK内置的com.sun.net.httpserver包下和sun.net.httpserver包下类提供的方法构建,该方法轻便 ...
- 基于JAVA语言的多线程技术
1.简介 多线程技术属于操作系统范围内的知识: 进程与线程 可以这么理解,一个应用程序就是一个进程,在一个进程中包含至少一个线程:进程就是线程的容器,真正工作.处理任务的是线程. 进程是操作系统分配资 ...
- Spring核心技术(十二)——基于Java的容器配置(二)
使用@Configuration注解 @Configuration注解是一个类级别的注解,表明该对象是用来指定Bean的定义的.@Configuration注解的类通过@Bean注解的方法来声明Bea ...
- 基于Java Mina框架的部标808服务器设计和开发
在开发部标GPS平台中,部标808GPS服务器是系统的核心关键,决定了部标平台的稳定性和行那个.Linux服务器是首选,为了跨平台,开发语言选择Java自不待言. 我们为客户开发的部标服务器基于Min ...
- 基于Java Mina框架的部标jt808服务器设计和开发
在开发部标GPS平台中,部标jt808GPS服务器是系统的核心关键,决定了部标平台的稳定性和行那个.Linux服务器是首选,为了跨平台,开发语言选择Java自不待言.需要购买jt808GPS服务器源码 ...
随机推荐
- jQuery 简单归纳总结
jQuery语法是为HTML元素的选取编制的,能够对元素运行某些操作. 基础语法是:$(selector).action() +美元符号定义 jQuery +选择符(selector)"查询 ...
- boost库在工作(37)网络UDP服务端之七
前面介绍的都是网络TCP的服务器和客户端,其实还有UDP的服务器和客户端,同时也有同步和异步之分.UDP与TCP最大的区别,就是TCP是基于连接的,而UDP是无连接的.这里所谓的连接是指对方中断服务时 ...
- 如何获取Android系统中申请对象的信息
最近一直在做有关内存方面的优化工作,在做优化的过程,除了关注内存的申请量以及GC的情况之外,我们经常需要想方法找出是那些对象占用了大量内存,以及他们是如何导致GC的,这意味着我们需要获取对象申请的信息 ...
- iOS常见的几种延时执行的方法
1.performSelector [self performSelector:@selector(delayMethod) withObject:nil/*可传任意类型参数*/ afterDelay ...
- 转--DataTable 修改列名 删除列 调整列顺序
DataTable myDt =dt; //删除列 myDt.Columns.Remove("minArea"); myDt.Columns.Remove("maxAre ...
- PHP 给前面或者后面添加0补位
相信大家一定遇到这样的问题,因为PHP是弱类型的,所以进行排序的时候,有时候很胃疼 所以这里就需要将位数进行统一后进行处理 一般都是将末尾添加0进行补位 方法1 : str_pad — 使用另一个字 ...
- python基础知识九
sys模块 sys模块包含系统对应的功能.我们已经学习了sys.argv列表,它包含命令行参数. 命令行参数 例14.1 使用sys.argv #!/usr/bin/python # Filename ...
- 对于百川SDK签名验证的问题
SDK是要在wantu.taobao.com生成的.而生成这个SDK其实是要上传一个apk,而这个上传其实就是取他的签名而已.验证就是那张yw222那张图片.重点是你上传的apk的签名是不是跟你的生成 ...
- UITabBar-UITabBarItem图片的背景颜色属性和文字的颜色大小设置
UITabBarItem设置的图片选中状态下默认的是蓝色,如何改变它的颜色为图片自带的颜色呢? typedef NS_ENUM(NSInteger, UIImageRenderingMode) { / ...
- C++文件操作详解(ifstream、ofstream、fstream)
C++ 通过以下几个类支持文件的输入输出: ofstream: 写操作(输出)的文件类 (由ostream引申而来) ifstream: 读操作(输入)的文件类(由istream引申而来) fstre ...