Java网络编程:(socket API编程:TCP协议的 socket API – 回显程序的客户端程序的编写)
观前提醒
如果你是第一次点击这篇博客进来的,需要你先看完这两篇博客,以及它里面提到的博客,再过来看这篇博客:
Java网络编程(4):(socket API编程:TCP协议的 socket API – 回显程序)
Java网络编程:(socket API编程:TCP协议的 socket API – 回显程序的服务器端程序的编写)
1. 准备工作
定义一个类:TCPEchoClient
当然,你也可以自定义类名
1.1 创建客户端的 Socket 对象
我们说过:
ServerSocket :是用于创建 TCP 的 服务端Socket,专门给TCP服务器用的
Socket :是用于创建 TCP 的客户端Socket,或服务端中接收到客户端建立连接(accept⽅法)的请求后,返回的服务端Socket,服务器 和 客户端都会使用
我们这篇博客,是关于客户端程序的编写,所以,我们需要先创建一个 客户端的 Socket 对象。
代码:
// 创建客户端 Socket对象
private Socket socket = null;
1.2 通过构造方法,初始化客户端的 Socket对象
我们这里的客户端,要访问服务器,也就是:服务器的IP 和 服务器的端口号,是我们客户端发送请求数据的 目的IP 和 目的端口号。
这里的构造方法,要确定这个客户端,要访问哪个服务器,也就是确定目的IP,目的端口,所以,要传入的信息,有两个:
- 要访问的服务器的 IP
- 要访问的服务器的 端口号
我们通过 TCPEchoClient 这个类的构造方法,在它的构造方法里面,调用 Socket 类的构造方法 Socket(serverIp,serverPort)
,绑定 服务器IP 和 服务器的端口号。
代码:
public TCPEchoClient(String serverIp,int serverPort) throws IOException {
socket = new Socket(serverIp,serverPort);
}
由于,TCP 协议,是有连接的,它提供的 Socket类,自然提供了带有 服务器IP 和 服务器端口号 这两个参数的构造方法。
我们就可以直接把字符串类型的 服务器IP 和 整型类型的服务器端口号,设置给 Socket类 的构造方法。
TCP特性: 有连接
构造方法的这一步:
调用构造方法之后,就创建了一个 Socket对象。
这个 Socket对象,会在底层,和服务器端,建立 TCP 的连接。
所谓连接,就是记录了对端的信息。
像 服务器的IP 和 服务器端口号 这样的信息,不需要自己创建变量来保存了,TCP内部,就直接记住了。
也就是说,我们可以调用某一个方法,就可以直接拿到目标服务器的 IP 和 端口号了。
UDP,就不会保存对端的信息,需要我们手动创建变量,保存这些信息:
1.3 创建 start 方法,用来启动客户端程序
通过start 方法,用来启动客户端程序。
代码:
public void start(){
System.out.println("启动客户端程序!");
}
接下来,就是在 start 方法里面,实现客户端程序的全部功能。
1.4 获取 Socket 的两个流对象
后续,我们在发送从控制台获取的请求数据时,还有读取服务器端返回的响应时,需要用到两个流对象。
这边同样,使用 try with resource 语法,获取两个流对象。
代码:
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 后续操作......
} catch (IOException e) {
throw new RuntimeException(e);
}
我们同样,使用 字符流 的方式,使用这两个类,所以,我们给他们套进 Scanner 和 PrintWriter 。
代码:
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 方便后续操作,套一层壳
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
// 后续操作......
} catch (IOException e) {
throw new RuntimeException(e);
}
1.4 编写一个死循环,客户端发送多个请求
对于客户端来说,建立一次连接,它不仅仅只能发送一次请求的,而是可以发送多个请求,所以,需要使用循环,让它能够发送多个请求。
并且,这个循环,是一个死循环,因为你不知道,当前客户端,会发送多个请求,不知道它什么时候,停止请求的发送。
代码:
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 方便后续操作,套一层壳
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true){
// 客户端发送多个请求,使用循环
// 后续工作......
}
} catch (IOException e) {
throw new RuntimeException(e);
}
1.5 结束准备工作
到这,就结束了准备工作,接下来,就是在 while 循环里面,进行客户端程序的编写了。
代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class TCPEchoClient {
// 创建客户端 Socket对象
private Socket socket = null;
public TCPEchoClient(String serverIp,int serverPort) throws IOException {
// TCP 协议,是有连接的,它提供的 Socket类,自然提供了带有 服务器IP 和 服务器端口号 这两个参数的构造方法。
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("启动客户端程序!");
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 方便后续操作,套一层壳
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true){
// 客户端发送多个请求,使用循环
// 后续工作......
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2. 读取输入,发送请求,接收响应
这一步,我们从控制台中,读取用户输入的请求内容,并将请求,发送给服务器。
2.1 从控制台读取用户输入
从控制台读取用户输入的请求内容,我们需要使用到 Scanner ,先创建一个 Scanner 对象:
我们这个 Scanner 对象,创建在 try 语句外面,是因为:如果创建在 try 里面,那么,这个对象的作用域,就只在 try 代码块里面,不太方便。
然后,在 while 循环里面,进行读取操作。
2.2 发送给服务器
将从控制台读取到的内容,发送给客户端。
代码:
while(true){
// 客户端发送多个请求,使用循环
// 1.从控制台读取用户输入的内容
System.out.println("请输入要发送的内容:");
String request = scannerRead.next();
// 2.将读取到的内容,发送给服务器
printWriter.println(request);
}
当然,我们也可以不用 println 的方式,进行发送,也可以采用 字节流 的方式,进行发送:
但是,后续是使用 println 的方式(字符流的方式),因为利用了缓冲区,这个后面细说。
2.3 读取服务器返回的响应
发送请求之后,等待服务器读取请求,计算响应,并返回响应。
返回响应时,我们需要读取服务器返回的响应。
同样,我们可以使用 Scanner 的方式,进行读取,也可以使用 字节流的方式,进行读取。
Scanner 方式,读取响应
// 3.读取服务器返回的响应
// Scanner 的方式
String response = scanner.next();
调用 next 方法,就可以读取,并返回一个字符串。
字节流(inputStream)方式,读取响应
// 3.读取服务器返回的响应
// 字节流的方式
byte[] byteResponse = new byte[1024];
int byteNum = inputStream.read(byteResponse);
String response = new String(byteResponse,0,byteNum);
注意:
这一条语句
String response = new String(byteResponse,0,byteNum);
// 0 表示,从 字符串的0下标 开始构造字符串
要注意一点:
第三个参数,必须是 read 方法,读取服务器返回响应之后,返回的整数 byteNum。
字节输入流(InputStream)提供的这个 read 方法中,有两个返回参数:
- 读取到的字节数据,存放字节数组(代码中的 byteResponse )中,这也叫做输出型参数
- 返回读取到的有效字节个数,比如:本次读取,共有效读取了 6 个字节个数,这个返回值就是 6 。
而我们将读取到的响应数据,转化为字符串,便于显示时,需要的字节个数是有效的字节个数,不是整一个字节数组的长度。
如果你使用了整一个字节数组的长度来构造 字符串,会将空白的字符,一起构造,最后的结果就是这样的:
将空白的字符全都打印出来了,这是没有意义的数据,不符合要求的。
所以,一定要主要,此处构造 String 的第三个参数,要是本次读取响应后,读取到的有效字节个数。
2.4 打印日志,显示响应数据
我们可以将响应数据,进行打印,查看服务器给我们返回的响应是什么样的。
我们这里使用 C语言 中的 printf ,格式化输出:
// 4.将服务器返回的响应,打印到控制台
System.out.println("服务器返回的响应数据:"+response);
2.5 为 TCPEchoClient 类(服务器端程序),编写main方法,启动服务器端程序
代码:
public static void main(String[] args) throws IOException {
TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
2.6 结束程序编写
到这里,我们的程序就编写完毕了,但是,当我们使用 Scanner 和 PrintWriter,进行数据的 读取和发送 的时候,再联合 服务器端程序,各自运行的时候,会发现有问题。
是什么问题,在这篇博客中 Java网络编程(4):(socket API编程:TCP协议的 socket API – 回显程序),再讲解。
同时,我们这里现在的程序,就是用 Scanner 和 PrintWriter,进行数据的 读取和发送 ,当然,其中也包含了 字节流方式进行数据的 读取和发送 的方法步骤。
整体工作流程
我们这里的代码截图,就只显示,使用 Scanner 和 PrintWriter,进行数据的 读取和发送 的方式了。
但是,字节流(InputStream 和 OutputStream)的方式,在后面给出的源码中,会保存,并注释掉。
客户端和服务器端,两者的工作流程:
3. 总结编写步骤
3.1 准备工作
- 创建客户端的 Socket对象,并通过构造方法,初始化 客户端的 Socket对象,保存服务器端的IP地址和端口号
- 创建 start 方法,获取 客户端的 Socket对象 的两个流对象
- 客户端会不断发送请求,编写死循环
3.2 读取输入,发送请求,接收响应
- 通过 Scanner对象,从控制台读取用户输入的请求,通过 Scanner 或 字节流 的方式,发送请求给客户端
- 服务器得出响应后,将响应返回给客户端,客户端通过 PrintWriter 或 字节流对象 的方式,读取响应
- 最后打印日志,显示响应数据
为 TCPEchoClient 类(服务器端程序),编写main方法,启动服务器端程序
编写main方法,创建一个 TCPEchoClient 类 的对象,调用 start方法,启动服务器端程序。
4. 目前的客户端代码:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class TCPEchoClient {
// 创建客户端 Socket对象
private Socket socket = null;
public TCPEchoClient(String serverIp,int serverPort) throws IOException {
// TCP 协议,是有连接的,它提供的 Socket类,自然提供了带有 服务器IP 和 服务器端口号 这两个参数的构造方法。
socket = new Socket(serverIp,serverPort);
}
public void start(){
System.out.println("启动客户端程序!");
Scanner scannerRead = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 方便后续操作,套一层壳
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while(true){
// 客户端发送多个请求,使用循环
// 1.从控制台读取用户输入的内容
System.out.println("请输入要发送的内容:");
String request = scannerRead.next();
// 2.将读取到的内容,发送给服务器
// 采用字符流的方式
printWriter.println(request);
// 采用字节流的方式(inputStream)
// byte[] byteRequest = request.getBytes();
// outputStream.write(byteRequest);
// 3.读取服务器返回的响应
// Scanner 的方式
String response = scanner.next();
// 字节流的方式
// byte[] byteResponse = new byte[1024];
// int byteNum = inputStream.read(byteResponse);
// String response = new String(byteResponse,0,byteNum);
// 4.将服务器返回的响应,打印到控制台
System.out.println("服务器返回的响应数据:"+response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
5. 总结:
这篇博客,是对客户端程序的编写。
看完这篇博客后,如果你看累了,可以休息会,如果你仍有余力,回到这篇博客:
Java网络编程:(socket API编程:TCP协议的 socket API – 回显程序)
,继续学习,关于 TCP协议提供的 Socket API下,回显程序的编写。
最后,如果这篇博客有帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!