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 所做的工作
- 填入 System.in 这个参数
往 Scanner 的构造方法中,填入System.in
这个参数,就是从控制台中读取用户输入的数据。
- 填入 File 对象
往 Scanner 的构造方法中,填入 File对象,就是从指定的目标文件中读取数据。
- 填写 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 – 回显程序的服务器端程序的编写)
最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!