Java网络编程:(socket API编程:TCP协议的 socket API -- 回显程序的客户端程序的编写)

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,目的端口,所以,要传入的信息,有两个:

  1. 要访问的服务器的 IP
  2. 要访问的服务器的 端口号

我们通过 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 方法中,有两个返回参数:

  1. 读取到的字节数据,存放字节数组(代码中的 byteResponse )中,这也叫做输出型参数
  2. 返回读取到的有效字节个数,比如:本次读取,共有效读取了 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 准备工作

  1. 创建客户端的 Socket对象,并通过构造方法,初始化 客户端的 Socket对象,保存服务器端的IP地址和端口号
  2. 创建 start 方法,获取 客户端的 Socket对象 的两个流对象
  3. 客户端会不断发送请求,编写死循环

3.2 读取输入,发送请求,接收响应

  1. 通过 Scanner对象,从控制台读取用户输入的请求,通过 Scanner 或 字节流 的方式,发送请求给客户端
  2. 服务器得出响应后,将响应返回给客户端,客户端通过 PrintWriter 或 字节流对象 的方式,读取响应
  3. 最后打印日志,显示响应数据

为 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下,回显程序的编写。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值