手写ServerSocket实现与http通信

前言

今天突然想到手写一个可以和http通信的简易ServerSocket,但是在完成这个功能时遇到了一些困难,故在此做个分享

实现

直接上代码吧,第一个版本代码如下

public class MyServlet2 {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8081);

        while(true) {
            Socket clientSocket = serverSocket.accept();

            InputStream inputStream = clientSocket.getInputStream();
            byte[] bytes = new byte[1024];
            int length;
            String str = "";
            // 核心代码
            while((length = inputStream.read(bytes)) > 0) {
                str += new String(bytes, 0, length, "UTF-8");
            }
            System.out.println(str);
        }

    }

}

这个版本一开始看上去好像没什么问题,但是当我通过浏览器输入http://localhost:8081/123时发现线程被阻塞了,然后最终定位到线程阻塞在while((length = inputStream.read(bytes)) > 0) 这一行,然后我查看以前读取文件流的方式,看了很久也没发现异常,没办法了只能去查阅相关资料

最终定位到的问题是,http和Socket通信时,服务端不知道客户端什么时候发送数据结束,所以执行inputStream.read时会一直等待,所以会阻塞

然后查询相关资料可解决方案有以下几种
1、设置客户端超时时间,超过指定时间则break循环,但是这种方式肯定是不靠谱的,总不能不让客户端长时间传输数据吧
2、根据http协议自己判断客户端什么时候发送数据

http协议规范

这里先普及下http协议的几个规范
1、http协议http头的结束必定是以结束符号\r\n\r\n结束(即两个回车符号)
2、如果请求包含请求体则http会存在字段Content-Length,如Content-Length :20(注意中间还有个空格),20表示请求体的字节长度
3、http请求头是和请求体连在一起传输的

ps(据说还有请求体内容不确定的,本文暂不考虑这种情况吧)

根据以上规范,2.0版本的代码如下

/**
 * @description 实现一个简易的http和servlet通信功能
 * http协议规则:
 * http头传输结束符号\r\n\r\n(即两个回车符号)
 * 有消息体则存在Content-Length字段,长度为body的字节数量
 *
 */
public class MyServlet {

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

        ServerSocket serverSocket = new ServerSocket(8081);
        while(true) {
            Socket clientSocket = serverSocket.accept();

            InputStream inputStream = clientSocket.getInputStream();
            // 缓冲区设置比较小,便于测试
            byte[] bytes = new byte[10];
            int length;

            byte[] allBytesArray = new byte[]{};
            // 核心代码,需要判断什么时候客户端请求发送结束
            while((length = inputStream.read(bytes)) > 0) {
                // 数组合并,使用字节累加,直接转String的话中文会被分割,导致乱码
                byte[] joinedArray = new byte[allBytesArray.length + length];
                System.arraycopy(allBytesArray, 0, joinedArray, 0, allBytesArray.length);
                System.arraycopy(bytes, 0, joinedArray, allBytesArray.length, length);
                allBytesArray = joinedArray;

                String str = new String(allBytesArray, "UTF-8");
                // 是否有消息体
                boolean existBody = str.contains("Content-Length");

                if(!existBody && str.lastIndexOf("\r\n\r\n") != -1) {
                    // 没有body体,则根据http协议,http header头结尾为\r\n\r\n,结尾是\r\n\r\n时判断数据接收完成
                    break;
                }
                
                // 有请求体并且http已经接收完成
                if(existBody && str.contains("\r\n\r\n")) {
                    System.out.println("-------------------存在body");
                    // 先等http头传输完成,截取头内容
                    String header = str.substring(0,str.indexOf("\r\n\r\n"));
                    System.out.println("-------------------header:" + header);

                    // 读取Content-Length的长度
                    int startIndex = header.indexOf("Content-Length");
                    int start = header.indexOf(" ",startIndex);
                    int end = header.indexOf("\r\n",startIndex);
                    // 截取请求体长度
                    int contentLength = Integer.valueOf(header.substring(start+1,end));
                    System.out.println("-------------------contentLength:" + contentLength);
                    int total = str.getBytes().length;
                    // 当前字节长度
                    int currentBodyLength = total - header.getBytes().length - "\r\n\r\n".getBytes().length;
                    System.out.println("-------------------currentBodyLength:" + currentBodyLength);
                    if(currentBodyLength == contentLength) {
                        //当前长度达到Content-Length表示接收完成
                        break;
                    }
                }
            }

            OutputStream outputStream = clientSocket.getOutputStream();
            outputStream.
                    write(("HTTP/1.1 200 OK\r\n" +  //响应头第一行
                            "Content-Type: text/html; charset=utf-8\r\n" +  //简单放一个头部信息
                            "\r\n" +  //这个空行是来分隔请求头与请求体的
                            "<h1>hello world</h1>\r\n").getBytes());

            outputStream.close();
            inputStream.close();
            clientSocket.close();
        }
    }

}

经过测试,传递参数和不传递参数均可以正常使用。

本次2.0版本是基于BIO,并且只有单个线程处理,关闭流等方式也都是直接往外抛异常的,还可以使用线程池升级处理请求方式,或者使用Nio来实现会更好

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个关于网络编程的技术问题,我可以回答。 Java SocketServerSocket 可以用于实现客户端服务端的网络通信。具体实现步骤如下: 1. 服务端创建 ServerSocket 对象,并指定一个端口号。用于监听客户端的连接请求。 2. 客户端创建 Socket 对象,并指定服务端的 IP 地址和端口号。用于发起连接请求。 3. 服务端使用 accept() 方法接收客户端连接并返回 Socket 对象。可以在循环中一直等待客户端的连接请求。 4. 服务端和客户端通过 InputStream 和 OutputStream 进行数据的读写。 下面是 Java 代码示例: 服务端代码: ``` try { // 创建 ServerSocket 对象 ServerSocket serverSocket = new ServerSocket(8888); while (true) { System.out.println("等待客户端连接..."); // 监听客户端的连接请求 Socket socket = serverSocket.accept(); System.out.println("客户端已连接:" + socket.getInetAddress().getHostName()); // 读取客户端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println("客户端发送的数据:" + line); } // 向客户端发送数据 OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream, true); printWriter.println("Hello, Client!"); // 关闭资源 bufferedReader.close(); inputStream.close(); printWriter.close(); outputStream.close(); socket.close(); } } catch (IOException e) { e.printStackTrace(); } ``` 客户端代码: ``` try { // 创建 Socket 对象 Socket socket = new Socket("192.168.0.1", 8888); // 向服务端发送数据 OutputStream outputStream = socket.getOutputStream(); PrintWriter printWriter = new PrintWriter(outputStream, true); printWriter.println("Hello, Server!"); // 读取服务端发送过来的数据 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = bufferedReader.readLine()) != null) { System.out.println("服务端发送的数据:" + line); } // 关闭资源 bufferedReader.close(); inputStream.close(); printWriter.close(); outputStream.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } ``` 以上便是使用 Java SocketServerSocket 实现客户端服务端网络通信的代码示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值