Spring MVC源码分析—基于Java中Socket实现HTTP协议

来源:互联网 发布:java服务器性能监控 编辑:程序博客网 时间:2024/04/30 16:22

自己实现网络通讯


1.1 普通Socket用法

Java中的网络通讯是通过Socket实现的,Socket分为ServerSocket和Socket两大类,ServerSocket用于服务端,可以通过accept方法监听请求,监听到请求后返回Socket,Socket用于具体完整数据传输,客户端直接使用Socket发起请求并传输数据。

1.1.1 ServerSocket

ServerSocket的使用分为三步:

  • 创建ServerSocket。
  • 调用创建出来的ServerSocket的accept方法进行监听。
  • 使用accept方法返回的Socket与客户端进行通信。
ServerSocket代码:
package com.jangz.deepinspringmvc.socket;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class Server {public static void main(String[] args) {ServerSocket server;try {server = new ServerSocket(8080);Socket socket = server.accept();BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));String line = in.readLine();System.out.println("received from client: " + line);PrintWriter out = new PrintWriter(socket.getOutputStream());out.write("received data: " + line);out.flush();out.close();in.close();socket.close();server.close();} catch (IOException e) {e.printStackTrace();}}}

1.1.2 客户端Socket

Socket的使用也一样,首先创建一个Socket,Socket的构造方法非常多,我们使用Socket(String host, int port),把目标主机的地址和端口号传入即可。Socket的创建的过程就会跟服务端建立连接,创建完Socket后,再用其创建Writer和Reader来传输数据,数据传输完成后释放资源关闭连接就可以了。

客户端Socket代码:
package com.jangz.deepinspringmvc.socket;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;public class Client {public static void main(String[] args) {String msg = "Hello, server!";try {Socket socket = new Socket("127.0.0.1", 8080);PrintWriter writer = new PrintWriter(socket.getOutputStream());BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));writer.println(msg);writer.flush();String line = reader.readLine();System.out.println("received from server: " + line);writer.close();reader.close();socket.close();} catch (UnknownHostException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}


先启动Server,然后启动Client就可以完成一次通信。

实现效果如下:



1.1.3 NioSocket的用法

从JDK1.4开始,Java增加了新的io模式——nio(new IO),nio在底层采用了新的处理方式,极大地提高了IO的效率。我们使用的Socket也属于IO的一种,nio提供了相应的工具:ServerSocketChannel和SocketChannel,它们分别对应原来的ServerSocket和Socket。


NioSocket三个概念:Buffer、Channel和Selector。

针对NioSocket的处理模式:Buffer就是所要送的货物,Channel就是送货员(或者开往某个区域的配货车),Selector就是中转站的分拣员。


NioSocket中服务端的处理过程可以分为5步:

  • 创建ServerSocketChannel并设置相应参数
  • 创建Selector并注册到ServerSocketChannel上
  • 调用Selector的select方法等待请求
  • Selector接收到请求后使用selectedKeys返回SelectionKey集合
  • 使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作

NioServer代码实现:

package com.jangz.deepinspringmvc.socket;import java.io.IOException;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.nio.charset.Charset;import java.util.Iterator;public class NIOServer {public static void main(String[] args) {try {ServerSocketChannel channel = ServerSocketChannel.open();channel.socket().bind(new InetSocketAddress(8080));channel.configureBlocking(false);Selector selector = Selector.open();channel.register(selector, SelectionKey.OP_ACCEPT);// 创建处理器Handler handler = new Handler(1024);while (true) {if (selector.select(3000) == 0) {System.out.println("等待请求超时......");continue;}System.out.println("处理请求......");Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();while (keyIter.hasNext()) {SelectionKey key = keyIter.next();try {if (key.isAcceptable()) {handler.handleAccept(key);}if (key.isReadable()) {handler.handleRead(key);}} catch (IOException ex) {keyIter.remove();continue;}keyIter.remove();}}} catch (IOException e) {e.printStackTrace();}}private static class Handler {private int bufferSize = 1024;private String localCharset = "UTF-8";public Handler() {}public Handler(int bufferSize) {this(bufferSize, null);}public Handler(String localCharset) {this(-1, localCharset);}public Handler(int bufferSize, String localCharset) {if (bufferSize > 0) {this.bufferSize = bufferSize;}if (localCharset != null) {this.localCharset = localCharset;}}public void handleAccept(SelectionKey key) throws IOException {SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();channel.configureBlocking(false);channel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));}public void handleRead(SelectionKey key) throws IOException {SocketChannel channel = (SocketChannel) key.channel();ByteBuffer buffer = (ByteBuffer) key.attachment();buffer.clear();if (channel.read(buffer) == -1) {channel.close();} else {buffer.flip();String receivedStr = Charset.forName(localCharset).newDecoder().decode(buffer).toString();System.out.println("received frm client: " + receivedStr);String respStr = "received data: " + receivedStr;buffer = ByteBuffer.wrap(respStr.getBytes(localCharset));channel.write(buffer);channel.close();}}}}


自己动手实现HTTP协议


我们知道HTTP协议是应用层解析内容的,只需要按照它的报文的格式封装和解析数据就可以了,具体的传输还是使用的Socket。

因为HTTP协议是在接收到数据之后才会用到的,所以我们只需要修改NioServer中的Handler就可以了,在修改后的HttpServer中首先获取到请求报文并打印出报文的头部(包含首行)、请求的方法类型、Url和Http版本,最后将接收到的请求报文信息封装到响应报文的主体中返回给客户端。这里的HttpHandler使用了单独的线程来执行,而且把SelectionKey中操作类型的选择也放在了HttpHandler中,不过具体处理过程和前面的NioServer没有太大区别。

代码实现:

package com.jangz.deepinspringmvc.http;import java.io.IOException;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.nio.charset.Charset;import java.util.Iterator;public class HttpServer {public static void main(String[] args) {try {ServerSocketChannel channel = ServerSocketChannel.open();channel.socket().bind(new InetSocketAddress(80));channel.configureBlocking(false);Selector selector = Selector.open();channel.register(selector, SelectionKey.OP_ACCEPT);// 创建处理器while (true) {if (selector.select(3000) == 0) {continue;}// 获取待处理的SelectionKeyIterator<SelectionKey> keyIter = selector.selectedKeys().iterator();while (keyIter.hasNext()) {SelectionKey key = keyIter.next();new Thread(new HttpHandler(key)).start();// 处理完成后,从待处理的SelectionKey迭代器中移除当前所使用的keykeyIter.remove();}}} catch (IOException e) {e.printStackTrace();}}private static class HttpHandler implements Runnable {private int bufferSize = 1024;private String localCharset = "UTF-8";private SelectionKey key;public HttpHandler(SelectionKey key) {super();this.key = key;}public void handleAccept() throws IOException {SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();clientChannel.configureBlocking(false);clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));}public void handleRead() throws IOException {// 获取channelSocketChannel socketChannel = (SocketChannel) key.channel();// 获取buffer并重置ByteBuffer buffer = (ByteBuffer) key.attachment();buffer.clear();// 没有读到内容则关闭if (socketChannel.read(buffer) == -1) {socketChannel.close();} else {// 接受请求数据buffer.flip();String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();// 控制台打印请求报文头String[] requestMessage = receivedString.split("\r\n");for (String s : requestMessage) {System.out.println(s);// 遇到空行说明报文头已经打印完if (s.isEmpty()) {break;}}// 控制台打印首行信息String[] firstLine = requestMessage[0].split(" ");System.out.println();System.out.println("Method:\t" + firstLine[0]);System.out.println("url:\t" + firstLine[1]);System.out.println("HTTP Version:\t" + firstLine[2]);System.out.println();// 返回客户端StringBuilder sendString = new StringBuilder();sendString.append("HTTP/1.1 200 OK\r\n"); // 响应报文首行,200表示处理成功sendString.append("Content-Type:text/html;charset=" + localCharset + "\r\n");sendString.append("\r\n"); // 报文头结束后加一个空行sendString.append("<html><head><title>显示报文</title></head><body>");sendString.append("接收到请求报文是:<br/>");for (String s : requestMessage) {sendString.append(s + "<br/>");}sendString.append("</body></html>");buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));socketChannel.write(buffer);socketChannel.close();}}@Overridepublic void run() {try {// 接收到连接请求时if (key.isAcceptable()) {handleAccept();}if (key.isReadable()) {handleRead();}} catch (IOException ex) {ex.printStackTrace();}}}}

README.md

#### 启动代码后,在浏览器中输入http://localhostGET /favicon.ico HTTP/1.1<br/>Host: localhost<br/>Connection: keep-alive<br/>User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36<br/>Accept: image/webp,image/apng,image/*,*/*;q=0.8<br/>Referer: http://localhost/<br/>Accept-Encoding: gzip, deflate, br<br/>Accept-Language: zh-CN,zh;q=0.8<br/><br/><br/>Method:GET<br/>url:/favicon.ico<br/>HTTP Version:HTTP/1.1<br/>


Ok, u're so smart. You have realized successfully C/S communication and HTTP protocol. 微笑

Come on & Regards!

原创粉丝点击