Socket编程入门——实现HTTP服务

什么是socket

socket意为网络插槽,当客户端和服务端之间交互的时候,会在客户端和服务端同时形成socket,通过socket来进行交互,发送信息通过socket,接受信息也是从socket中读。socket相当于一个文件,多数操作系统就是将socket实现成一个文件。
在这里插入图片描述
线程模型
当有一个请求过来时(相当于一个tcp连接),操作系统接到请求的时候,会先将请求放在一个叫pendingQueue的队列中,如果这个队列满了,就会发生拒绝,实际项目中有时就会发现系统拒绝了一个连接。系统收到请求会一直放在这个队列里,直到Accept调用,它会从队列中拿出一个请求,在内核空间形成一个socket文件,然后对应内核中的socket会生成一个类似资源的句柄(FD),这个派发线程会无限的accept,拿请求形成socket生成FD。最终派发线程会将资源派发给工作线程,工作线程从socket中读用户发送的内容,然后处理,最后返回。返回不是原路返回的,而是工作线程直接将返回内容写入socket,内核中发现socket有写入,就会将写入信息返回用户。
在这里插入图片描述

socket简单的实现

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class socket1 {
    public static void main(String[] args) {
        try{
            ServerSocket serverSocket = new ServerSocket(8080);
            for(;;){
                //阻塞操作,内核中pendingQueue中没有请求时会阻塞,等待有请求后拿到请求继续
                Socket socket = serverSocket.accept();
                System.out.println("收到请求");
                //从socket中读信息
                DataInputStream inputStream = new DataInputStream(socket.getInputStream());
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder requestBuilder = new StringBuilder();
                String line = "";
                while(!(line = bufferedReader.readLine()).isEmpty()){
                    requestBuilder.append(line+'\n');
                }
                System.out.println(requestBuilder.toString());
                //返回信息
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                //按照http协议返回信息(后边继续学习http协议)
                bufferedWriter.write("HTTP/1.1 200 OK\n\nhello world\n");
                bufferedWriter.flush();
                socket.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

浏览器访问:
在这里插入图片描述
控制台输出的请求信息:
在这里插入图片描述

socket多线程模型及request/response简单封装

1.Request简单封装,主要用于读取请求内容

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpParser;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Request {
    static Pattern methodRegex = Pattern.compile("(GET|PUT|POST|DELETE|OPTIONS|TRACE|HEAD)");
    private final String body;
    private final String method;
    private final HashMap<String, String> headers;

    public Request(Socket socket) throws IOException {
        DataInputStream iptStream = new DataInputStream(socket.getInputStream());
        //读取请求方法
        // GET /path HTTP/1.1
        String methodLine = HttpParser.readLine(iptStream, "UTF-8");
        Matcher matcher = methodRegex.matcher(methodLine);
        matcher.find();
        String method = matcher.group();
        //读取请求头
        // Content-Type:xxxx
        // Length : xxx
        Header[] headers = HttpParser.parseHeaders(iptStream, "UTF-8");
        HashMap headMap = new HashMap<String, String>();
        for(Header h : headers) {
            headMap.put(h.getName(), h.getValue());
        }
        //读取请求体body
        BufferedReader bufferReader = new BufferedReader(new InputStreamReader(iptStream));
        StringBuilder body = new StringBuilder();
        char[] buffer = new char[1024];
        while(iptStream.available() > 0) {
            bufferReader.read(buffer);
            body.append(buffer);
        }
        this.body = body.toString();
        this.method = method;
        this.headers = headMap;
    }
    public String getBody() {
        return body;
    }
    public HashMap<String, String> getHeaders() {
        return headers;
    }
}

2.Response简单封装,主要用于返回数据

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.HashMap;

public class Response {
    Socket socket;
    private int status;
    static HashMap<Integer, String> codeMap;
    
    public Response(Socket socket) {
        this.socket = socket;
        if(codeMap == null) {
            codeMap = new HashMap<>();
            codeMap.put(200, "OK");
        }
    }
    public void send(String msg) throws IOException {
        String resp = "HTTP/1.1 " + this.status + " " + this.codeMap.get(this.status) + "\n";
        resp += "\n";
        resp += msg;
        this.sendRaw(resp);
    }
    public void sendRaw(String msg) throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bufferedWriter.write(msg);
        bufferedWriter.flush();
        socket.close();
    }
}

3.socket服务类

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    ServerSocket serverSocket;
    IHandlerInterface httpHandler;
    public Server(IHandlerInterface httpHandler){
        this.httpHandler = httpHandler;
    }
    public void listen(int port) throws IOException {
        serverSocket = new ServerSocket(port);
        while(true) {
            this.accept();
        }
    }
    void accept() throws IOException {
        Socket socket = serverSocket.accept();
        //接收到请求后,改为多线程方式来处理请求
        new Thread(() -> {
            try {
                this.handler(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
    private void handler(Socket socket) throws IOException {
        Request request = new Request(socket);
        Response response = new Response(socket);
        this.httpHandler.handler(request, response);
    }
}

4.main启动类

import java.io.IOException;

public class TestMain {
    public static void main(String[] args) throws IOException {
        Server server = new Server((req, resp) -> resp.send("<html><body><h1>Hello world!</h1></body></html>"));
        server.listen(8080);
    }
}

简单认识NIO

NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。通过Channel注册到Selector上的状态来实现一种客户端与服务端的通信。
传统IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,
在这里插入图片描述

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.util.Iterator;
import java.util.Set;

public class NioServer {
    ServerSocketChannel ssc;
    public void listen(int port) throws IOException {
        ssc = ServerSocketChannel.open();
        ssc.bind(new InetSocketAddress(port));
        // true阻塞(Reactive) false非阻塞 (Reactor)
        ssc.configureBlocking(false);
        Selector selector = Selector.open();
        ssc.register(selector, ssc.validOps(), null);
        ByteBuffer buffer = ByteBuffer.allocate(1024*16);
        for(;;) {
            Set selectedKeys = selector.selectedKeys();
            Iterator it = selectedKeys.iterator();

            while(it.hasNext()) {
                SelectionKey key = (SelectionKey)it.next();
                if(key.isAcceptable()) {
                    SocketChannel channel = ssc.accept();
                    if(channel == null) {
                        continue;
                    }
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                } else {
                    SocketChannel channel = (SocketChannel)key.channel();
                    buffer.clear();
                    channel.read(buffer);
                    String request = new String(buffer.array());
                    System.out.println(request);
                    buffer.clear();
                    buffer.put("HTTP/1.1 200 ok\n\nHello NIO!!".getBytes());
                    buffer.flip();
                    channel.write(buffer);
                    channel.close();
                }
            }
        }
    }
    public static void main(String[] argv) throws IOException {
        NioServer server = new NioServer();
        server.listen(8001);
    }
}
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值