当前位置: 首页 > news >正文

Java网络编程:(socket API编程:TCP协议的 socket API -- 服务器端处理请求的三个步骤)

Java网络编程:(socket API编程:TCP协议的 socket API – 服务器端处理请求的三个步骤)

文章目录

    • Java网络编程:(socket API编程:TCP协议的 socket API -- 服务器端处理请求的三个步骤)
  • 观前提醒
  • 1. 程序准备代码:
  • 2. 根据处理请求的三个步骤,编写代码
    • 2.1 读取请求,并解析请求数据
      • 输入流读取请求
        • 注意:
      • Scanner 读取请求
      • 填入不同参数,Scanner 所做的工作
      • 针对本程序(读取请求)
      • 读取请求前,可以进行判断
      • 总结
    • 2.2 根据请求,计算响应(服务器最关键的逻辑)
    • 2.3 把响应返回给客户端
      • 纯字节流方式(write)
      • PrintWriter 写数据
      • 遗留的问题:
      • 使用原理
    • 2.4 打印日志,显示信息
  • 3. 关于这个程序的一些问题:
    • 1. 一次请求相当于一次连接?
  • 4. 总结

观前提醒

本篇博客,是从 Java网络编程:(Java网络编程:(socket API编程:TCP协议的 socket API – 回显程序的服务器端程序的编写) 这篇博客分离出来的,需要你看完这篇博客,再过来看这篇博客。

1. 程序准备代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;public class TCPEchoServer {private ServerSocket serverSocket = null;public TCPEchoServer(int port) throws IOException {this.serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("启动服务器!");//        服务器需要不断的读取客户端的请求数据,用一个死循环实现这一点while(true){// 对于 TCP 来说,需要先处理客户端发来的连接// clientSocket 这个变量,就表示和服务器和客户端进行交流的一个对象,// 后续,就通过读写 clientSocket ,和客户端进行通信Socket clientSocket = serverSocket.accept();
//            处理一个客户端的请求和响应processConnect(clientSocket);}}//    处理一个客户端的连接
//    一个客户端,可能会发送多个请求,就要处理多个请求,得到多个响应private void processConnect(Socket clientSocket) {
//        打印一个日志,输出 clientSocket 的 IP和端口号System.out.printf("[%s : %d] 客户端连接成功,客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){//            一个客户端,可能会发送多个请求,就要处理多个请求,得到多个响应
//            所以,需要使用 while 循环,处理多次
//            不知道客户端到底会发送多少个请求,所以,使用死循环while(true){
//            这里的工作分为三个步骤:
//                1. 读取请求,并解析请求数据//                2. 根据请求,计算响应(服务器最关键的逻辑)//                3. 把响应返回给客户端}} catch (IOException e) {throw new RuntimeException(e);}}}

2. 根据处理请求的三个步骤,编写代码

2.1 读取请求,并解析请求数据

输入流读取请求

读取请求这块,TCP 这里,是通过 InputStream 这个输入流对象,提供的 read 方法来读取的。
在这里插入图片描述
这一条语句,尤其需要注意:

String request = new String(requestByte,0,byteNum);
注意:

第三个参数,必须是读取请求后,读取到的有效字节个数(代码中的 byteNum),不能是 requestByte.length!!!

原因分析:
requestByte.length:表示的是你定义 requestByte 这个字节数组的长度,如果你用这个参数构造 String,会将空白的字符,一起构造了。
我们需要的是有效的字节个数

使用 read 方法,读取请求之后,会返回一个整数,这个整数,就是读取到的实际字节个数,就是有效的字节个数

假定,我们程序是写完了的:如果你使用 requestByte.length 这个,最后的结果会是这样的:
在这里插入图片描述
会将空余的字符,都打印出来了,这是不符合实际要求的。

这个操作,我们可以使用一个更直接的方式:Scanner

Scanner 读取请求

Scanner 这个类,不仅可以从控制台读取用户输入的数据,也能读取文件中的数据,还能读取网络中的请求。

为什么呢?
其实,Scanner 的构造方法,填入的是一个 InputStream 对象。

平常,我们想要从控制台这边读取用户输入的数据的时候,是这样子写的:
在这里插入图片描述
但当我们按住左下角的Ctrl + 鼠标左键,点击 in 的时候,会发现:
在这里插入图片描述
in,实际上就是一个 InputStream类 实例出来的对象

所以, Scanner 的构造方法填入的是一个 InputStream 对象

填入不同参数,Scanner 所做的工作

  1. 填入 System.in 这个参数
    往 Scanner 的构造方法中,填入 System.in 这个参数,就是控制台中读取用户输入的数据。
    在这里插入图片描述
  2. 填入 File 对象
    往 Scanner 的构造方法中,填入 File对象,就是指定的目标文件中读取数据。
    在这里插入图片描述
  3. 填写 InputStream 对象(本程序使用的参数)
    往 Scanner 的构造方法中,填写从 Socket 获取到的 InputStream 对象,就是从指定的网络输入流中,读取数据

如果是获取到客户端(clientSocket)的输入流对象,就可以通过 Scanner,读取从客户端获取到的输入流对象中的字节数据。
在这里插入图片描述

针对本程序(读取请求)

对于我们这个回显程序的读取来说,我们可以将我们从 clientSocket 中,获取到的 InputStream 输入流对象,输入给 Scanner 的构造方法中:
在这里插入图片描述
这个代码,就是把 从 clientSocket 这个 Socket对象 中获取到的 输入流对象(inputStream)传给了 Scanner对象(scanner),后续,我们就可以从 scanner 里面,直接读取到客户端发送过来的请求里面的内容。

相当于,我们不使用 InputStream类 提供的 read方法,而是将
输入流对象(inputStream),传给了 Scanner对象(scanner),使用 Scanner类 来读取请求中的内容
在这里插入图片描述
并且,可以直接转化为字符串,一步到位。

读取请求前,可以进行判断

我们在读取客户端的请求前,可以判断一下:是否还有请求可以读,如果有,就继续读,如果没有,就跳出循环,结束本次对客户端的请求处理。

并且打印一个日志,显示 服务器 和 客户端,断开了连接。

代码:

if (!scanner.hasNext()){
//客户端没有请求发送过来了,就不需要继续读取请求,并处理了
//可以断开和客户端的连接(结束循环)//打印一个日志,显示 服务器 和 客户端,断开了连接
System.out.printf("[%s : %d] 断开连接,客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());break;
}

在这里插入图片描述
客户端没有请求发送过来了,就可以结束循环,结束 processConnect方法,断开和客户端的连接了。

总结

这一步操作,我们主要讲了,如何改变读取的方式采用 Scanner 的方式,对客户端发送过来的请求进行读取。

这一种方式,能够简化代码,更加快捷的将请求数据转化为字符串,方便后续操作。

到目前为止的代码(仅截取 processConnect 方法内的代码):

//    处理一个客户端的连接
//    一个客户端,可能会发送多个请求,就要处理多个请求,得到多个响应private void processConnect(Socket clientSocket) {
//        打印一个日志,输出 clientSocket 的 IP和端口号System.out.printf("[%s : %d] 客户端连接成功,客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){Scanner scanner = new Scanner(inputStream);
//            一个客户端,可能会发送多个请求,就要处理多个请求,得到多个响应
//            所以,需要使用 while 循环,处理多次
//            不知道客户端到底会发送多少个请求,所以,使用死循环while(true){
//            这里的工作分为三个步骤:
//                1. 读取请求,并解析请求数据////                使用 InputStream 提供的 read 方法读取数据
//                byte[] requestByte = new byte[1024];
//                inputStream.read(requestByte);
////                为了后续的处理更加方便,需要手动转换为 String
//                String request = new String(requestByte,0,requestByte.length);//                使用 Scanner 读取请求if (!scanner.hasNext()){
//                    客户端没有请求发送过来了,就不需要继续读取请求,并处理了
//                    可以断开和客户端的连接(结束循环)break;}String request = scanner.next();//                2. 根据请求,计算响应(服务器最关键的逻辑)//                3. 把响应返回给客户端}} catch (IOException e) {throw new RuntimeException(e);}}

2.2 根据请求,计算响应(服务器最关键的逻辑)

我们这个程序,是回显程序,客户端的请求是什么,返回给客户端的响应就是什么。

我们这里,用一个 process 方法,将赋值的过程,进行封装,方便后续进行更改计算响应的逻辑。
在这里插入图片描述

2.3 把响应返回给客户端

纯字节流方式(write)

我们需要将响应,根据 Socket对象(clientSocket),获取到的字节输出流对象(outputStream)提供的 write 方法,写回到客户端中,所以,我们的写法是:
在这里插入图片描述
这种方式,是以字节流的方式,进行发送的。

PrintWriter 写数据

但是,我们还有一种写法:使用 PrintWriter,这和上面,使用 Scanner 的方式类似。

我们将 OutputStream 这个字节输出流,所实例化的对象 outputStream,传给 PrintWriter 这个类实例出来的对象 ,writer。
在这里插入图片描述
后续,我们就可以使用 writer 这个对象,将响应,写回(发送)给客户端

并且,是以字符流的方式,进行发送的。
也就是,直接将 String 类型的响应数据,返回给客户端,不需要再获取到 String 类型的响应数据中的字节数据。

使用 writer 这个对象,将响应,写回(发送)给客户端:
在这里插入图片描述

遗留的问题:

这个问题,出自于 writer.println(response); 这一步代码,至于是什么问题,当我们后续启动程序的时候,就知道了。

使用原理

这种使用方法,其实和我们将数据输出到控制台是差不多的。

我们使用打印语句:System.out.println();中间的out,本质上,是一个 OutputStream 实例化的对象
在这里插入图片描述
所以,使用 PrintWriter 的时候,和使用 Scanner 读取请求的时候,是差不多的。

PrintWriter参数,填写从 Socket 获取到的 OutputStream 对象,就是从指定的网络输出流中,写入数据

2.4 打印日志,显示信息

我们可以打印日志,显示 客户端发送的请求 和 服务器端返回的响应信息。

使用 C语言 的 printf 方法,进行格式化的打印输出。

//                4.打印日志,显示 客户端发送的请求 和 服务器端返回的响应信息
System.out.printf("[%s : %d] 请求(req):%s,响应信息(resp):%s \n",
clientSocket.getInetAddress(),
clientSocket.getPort(),
request,response);

3. 关于这个程序的一些问题:

1. 一次请求相当于一次连接?

答:不是的!!!

我们这个程序,首先,需要先和客户端建立连接,之后,这一次的连接中,客户端可以发送多个请求

好比我们现实生活中打电话:
打一次电话,相当于一次连接,这一次连接,你可以只说一句话,就挂断电话(断开连接)。
可以说很多句话,才挂断电话

打一次电话,就说一句话就挂断电话,相当于一次连接,只有一次请求,这种方式,也叫做:短连接
打一次电话,说很多句话,才挂断电话,相当于一次连接多次请求,这种方式,也叫做:长连接

现实生活中,更多的是 长连接,因为短链接,比较消耗性能
这也是服务器程序,主流的写法。

4. 总结

这篇博客,主要是按照三个步骤,对客户端发送过来的请求,进行处理,这篇博客看完之后,返回这篇博客:
Java网络编程:(socket API编程:TCP协议的 socket API – 回显程序的服务器端程序的编写)

最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!


文章转载自:

http://FA6tBqmL.mgzjz.cn
http://DjrWCng6.mgzjz.cn
http://7mtdaQT8.mgzjz.cn
http://DpH7tG73.mgzjz.cn
http://5Um8fsLs.mgzjz.cn
http://MwycsENP.mgzjz.cn
http://VEy8QlFm.mgzjz.cn
http://WzZ0AON2.mgzjz.cn
http://nbpHQRPk.mgzjz.cn
http://ZHZTOMOi.mgzjz.cn
http://iBQNeub1.mgzjz.cn
http://QtU5NlGa.mgzjz.cn
http://d3cMuP7M.mgzjz.cn
http://HE8tjQN9.mgzjz.cn
http://QunlJ3Ra.mgzjz.cn
http://brr5O6WN.mgzjz.cn
http://sFsyYS5N.mgzjz.cn
http://bebHjeAU.mgzjz.cn
http://6Sr1g0TJ.mgzjz.cn
http://ZbMinRbO.mgzjz.cn
http://J5ToFMtl.mgzjz.cn
http://JCQubzNo.mgzjz.cn
http://c9hOymxN.mgzjz.cn
http://NLyU8QrT.mgzjz.cn
http://fxnVv6bh.mgzjz.cn
http://kA4NgbjM.mgzjz.cn
http://YhzJshAR.mgzjz.cn
http://6RBZq8TE.mgzjz.cn
http://P5c5CjxC.mgzjz.cn
http://XrNiJYaZ.mgzjz.cn
http://www.dtcms.com/a/384613.html

相关文章:

  • 新能源汽车总装车间案例:四台S7-1200通过无线网桥同步控制16组ET 200SP的秘诀
  • k8s事件驱动运维利器 shell operator
  • GitHub Actions 部署配置
  • java后端工程师进修ing(研一版‖day45)
  • k8s核心资料基本操作
  • Redis 在电商系统中的应用:高并发场景下的架构艺术
  • RK3588:MIPI底层驱动学习——芯外拾遗第一篇:从四个模块到整个“江湖”
  • K8S里的“豌豆荚”:Pod
  • OpenStack 管理与基础操作学习笔记(一):角色、用户及项目管理实践
  • 大数据毕业设计选题推荐-基于大数据的金融数据分析与可视化系统-Spark-Hadoop-Bigdata
  • Python爬虫实战:研究Pandas,构建期货数据采集和分析系统
  • 软考中级习题与解答——第六章_计算机硬件基础(3)
  • Nvidia显卡架构解析与cuda应用生态浅析
  • AppStore 如何上架?iOS 应用发布全流程、uni-app 打包上传 ipa、App Store 审核与多工具组合实战指南
  • 贪心算法应用:卫星链路调度问题详解
  • 基于https的数据加密技术
  • 自学嵌入式第四十一天:单片机-中断
  • 二分图 系列
  • DDAC工作流的PyCharm项目前置准备清单
  • 【Kubernetes】K8s 集群外服务配置 Service 访问
  • RESTFul API接口设计指南_V2
  • Linux第十七讲:应用层自定义协议与序列化
  • ESLint 自定义规则开发
  • 三维地震数据体:形态、处理流程与勘探应用笔记
  • HTTP标头全解析:保护你的Web应用!
  • 机器人控制器开发(定位——cartographer ros2 使用2)
  • 元学习原理与实验实战:让机器学会快速学习
  • [Cesium] 基于Cesium的二次开发的库
  • 红外IR的运用
  • 基于51单片机可燃气体报警、风扇、继电器断闸