【JavaEE初阶】 TCP服务器与客户端的搭建,JavaEE初阶,TCP服务器与客户端的搭建指南

马肤

温馨提示:这篇文章已超过405天没有更新,请注意相关的内容是否还可用!

摘要:本教程介绍了JavaEE初阶中TCP服务器与客户端的搭建过程。通过简单的步骤,让读者了解如何创建TCP服务器并使其能够接收客户端连接。教程还涵盖了如何编写TCP客户端以连接到服务器并进行通信。本内容对于初学者了解Java网络编程基础非常有帮助。

文章目录

  • 🌲前言
  • 🌴ServerSocket API
  • 🎄Socket API
  • 🍀TCP中的长短连接
  • 🎍建立TCP回显客户端与服务器
    • 🚩TCP搭建服务器
    • 🚩TCP搭建客户端
    • 🚩通信过程展示:
    • 🌳多个客户端对一个服务器
      • 🚩拓展(IO多路复用/IO多路转接)
      • ⭕总结

        🌲前言

        TCP服务器与客户端的搭建需要借助以下API

        TCP之间通信通过流进行传输,无论是服务器还是客户端:读取内容用输入流,写入内容用输出流

        🌴ServerSocket API

        ServerSocket 是创建TCP服务端Socket的API。

        ServerSocket 构造方法

        方法签名方法说明
        ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

        ServerSocket 方法

        方法签名方法说明
        Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
        void close()关闭此套接字

        🎄Socket API

        Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

        不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

        Socket 构造方法

        方法签名方法说明
        Socket(String host, intport)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

        Socket 方法

        方法签名方法说明
        InetAddress getInetAddress()返回套接字所连接的地址
        InputStream getInputStream()返回此套接字的输入流
        OutputStream getOutputStream()返回此套接字的输出流

        🍀TCP中的长短连接

        博主在前面的博文里面说到,TCP是面向连接的通信方式,TCP发送数据时,需要先建立连接,而这个连接又分为长短连接:

        • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。

        • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据

          对比以上长短连接,两者区别如下:

          • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。

          • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。

          • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等

            拓展

            • 基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。

            • 由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在一个线程中运行。一次阻塞等待对应着一次请求、响应,不停处理也就是长连接的特性:一直不关闭连接,不停的处理请求

            • 实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。

              博主下面实现的TCP服务器与客户端属于长连接

              🎍建立TCP回显客户端与服务器

              什么叫回显客户端与与服务器呢?

              其实就是:客户端向服务端发送请求,一般来说我们的服务端会对我们发送的请求进行处理,我们这里为了简单,就省略里面的处理过程,只实现将请求重新发回客户端,不做任何处理。

              🚩TCP搭建服务器

              我们分为以下几步来实现:

              1. 创建TcpEchoServer类来表示我们的服务器,并创建ServerSocket对象,初始值为null

              2. 在TcpEchoServer的构造方法里进行ServerSocket对象的实例化

              3. 用一个start()方法表示启动程序

              4. 在该方法内我们首先要使用accept()进行连接,并用Socket对象进行接收

              5. 我们再用一个processConnection(Socket clientSocket)方法处理我们的连接

              由于我们的TCP传输是以流的形式传播的,所以我们这里用到了读写数据流的方法来进行书写,不会这一部分的小伙伴,可以去看看博主所写《【JavaEE初阶】 文件内容的读写 —— 数据流》进行查看学习

              接下来我们书写这个processConnection(Socket clientSocket)方法

              1. 读取请求,构造输入流的Scanner,并判断后面如果没有数据就关闭连接

              2. 然后我们将读取的数据交给我们的 response()构造响应

              3. 响应后的数据写入该套接字的输出流中,最后flush(),进行刷新,确保写入

              为了释放资源,我们每一次交互完毕都需要对我们的套接字进行关闭,这里我们使用fially来进行处理

              代码实现如下:

              import java.io.IOException;
              import java.io.InputStream;
              import java.io.OutputStream;
              import java.io.PrintWriter;
              import java.net.ServerSocket;
              import java.net.Socket;
              import java.util.Scanner;
              public class TcpEchoServer {
                  private ServerSocket serverSocket = null;
                  public TcpEchoServer(int port) throws IOException {
                      serverSocket = new ServerSocket(port);
                  }
                  public void start() throws IOException {
                      System.out.println("启动服务器");
                      Socket socket = serverSocket.accept();
                      processConnection(socket);
                  }
                  // 使用这个方法来处理一个连接.
                  // 这一个连接对应到一个客户端. 但是这里可能会涉及到多次交互.
                  private void processConnection(Socket clientSocket) {
                      System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                      // 基于上述 socket 对象和客户端进行通信
                      try (InputStream inputStream = clientSocket.getInputStream();
                           OutputStream outputStream = clientSocket.getOutputStream()) {
                          // 由于要处理多个请求和响应, 也是使用循环来进行.
                          while (true) {
                              // 1. 读取请求
                              Scanner scanner = new Scanner(inputStream);
                              if (!scanner.hasNext()) {
                                  // 没有下个数据, 说明读完了. (客户端关闭了连接)
                                  System.out.printf("[%s:%d] 客户端下线! \n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                                  break;
                              }
                              // 注意!! 此处使用 next 是一直读取到换行符/空格/其他空白符结束, 但是最终返回结果里不包含上述 空白符 .
                              String request = scanner.next();
                              // 2. 根据请求构造响应
                              String response = process(request);
                              // 3. 返回响应结果.
                              //    OutputStream 没有 write String 这样的功能. 可以把 String 里的字节数组拿出来, 进行写入;
                              //    也可以用字符流来转换一下.
                              PrintWriter printWriter = new PrintWriter(outputStream);
                              // 此处使用 println 来写入. 让结果中带有一个 \n 换行. 方便对端来接收解析.
                              printWriter.println(response);
                              // flush 用来刷新缓冲区, 保证当前写入的数据, 确实是发送出去了.
                              printWriter.flush();
                              System.out.printf("[%s:%d] req: %s; resp: %s \n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                                      request, response);
                          }
                      } catch (IOException e) {
                          e.printStackTrace();
                      } finally {
                          // 更合适的做法, 是把 close 放到 finally 里面, 保证一定能够执行到!!
                          try {
                          	clientSocket.close();
                              clientSocket.close();
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
                      }
                  }
                  public String process(String request) {
                      return request;
                  }
                  public static void main(String[] args) throws IOException {
                      TcpEchoServer server = new TcpEchoServer(9090);
                      server.start();
                  }
              }
              

              服务端启动展示

              【JavaEE初阶】 TCP服务器与客户端的搭建,JavaEE初阶,TCP服务器与客户端的搭建指南 第1张

              🚩TCP搭建客户端

              搭建客户端我们也可以分为以下几步:

              1. 创建TcpEchoClient类表示我们的客户端,创建Soket对象用于与客户端通信·

              2. 再TcpEchoClient构造方法里进行实例化Socket的对象

              3. 创建start()方法用于我们的操作

              4. 读取键盘所要输入的数据

              5. 将所读的数据通过输出流进行写入

              6. 读取响应的输入流,进行打印

              7. main函数中进行启动

              代码实现如下:

              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 {
                  private Socket socket = null;
                  public TcpEchoClient(String serverIp, int serverPort) throws IOException {
                      // Socket 构造方法, 能够识别 点分十进制格式的 IP 地址. 比 DatagramPacket 更方便.
                      // new 这个对象的同时, 就会进行 TCP 连接操作.
                      socket = new Socket(serverIp, serverPort);
                  }
                  public void start() {
                      System.out.println("客户端启动!");
                      Scanner scanner = new Scanner(System.in);
                      try (InputStream inputStream = socket.getInputStream();
                           OutputStream outputStream = socket.getOutputStream()) {
                          while (true) {
                              // 1. 先从键盘上读取用户输入的内容
                              System.out.print("> ");
                              String request = scanner.next();
                              if (request.equals("exit")) {
                                  System.out.println("goodbye");
                                  break;
                              }
                              // 2. 把读到的内容构造成请求, 发送给服务器.
                              PrintWriter printWriter = new PrintWriter(outputStream);
                              printWriter.println(request);
                              // 此处加上 flush 保证数据确实发送出去了.
                              printWriter.flush();
                              // 3. 读取服务器的响应
                              Scanner respScanner = new Scanner(inputStream);
                              String response = respScanner.next();
                              // 4. 把响应内容显示到界面上
                              System.out.println(response);
                          }
                      } catch (IOException e) {
                          e.printStackTrace();
                      }
                  }
                  public static void main(String[] args) throws IOException {
                      TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
                      client.start();
                  }
              }
              

              客户端启动展示:

              【JavaEE初阶】 TCP服务器与客户端的搭建,JavaEE初阶,TCP服务器与客户端的搭建指南 第2张

              🚩通信过程展示:

              【JavaEE初阶】 TCP服务器与客户端的搭建,JavaEE初阶,TCP服务器与客户端的搭建指南 第3张

              【JavaEE初阶】 TCP服务器与客户端的搭建,JavaEE初阶,TCP服务器与客户端的搭建指南 第4张

              🌳多个客户端对一个服务器

              在博主写的【JavaEE初阶】 UDP服务器与客户端的搭建中多个客户端面对一个服务器的时候,我们只需要进行设置以下即可,但是了,在我们上述写的TCP代码中是不可行的,就算可以启动,但是后续的客户端会出现阻塞,并不会执行相应响应

              这是因为这里是可连接的,当一个客户端与服务器进行建立后,后面的线程就只能等待。

              就相当于你正在和你的女朋友打电话,这时候你兄弟打电话来了,你就只能让你兄弟先等着,等你和你女朋友打完电话后,才可以接你兄弟的电话

              那我们的解决方法是什么呢?

              这里用的解决方法是对服务器用多线程进行处理,使得服务器可以和多台客户端进行通信。由于我们在实际开发环境中客户端非常的多,而我们频繁创建销毁线程会增加开销,所以我们这里有用了一个线程池来实现

              对上述服务器修改的代码如下:

                  public void start() throws IOException {
                      System.out.println("启动服务器");
                      // 此处使用 CachedThreadPool, 使用 FixedThreadPool 不太合适 (线程数不太应该是有固定的....)
                      ExecutorService threadPool = Executors.newCachedThreadPool();
                      while (true) {
                          // 使用这个 clientSocket 和具体的客户端进行交流.
                          Socket clientSocket = serverSocket.accept();
                          // 此处使用多线程来处理.
                          // 这里的多线程版本的程序, 最大的问题就是可能会涉及到频繁申请释放线程.
              //            Thread t = new Thread(() -> {
              //                processConnection(clientSocket);
              //            });
              //            t.start();
                          // 使用线程池.
                          threadPool.submit(() -> {
                              processConnection(clientSocket);
                          });
                      }
                  }
              

              🚩拓展(IO多路复用/IO多路转接)

              上述代码解决了多个客户端与一个服务器通信的问题,但是呢,在实际应用中,要是客户端太多,且客户端都不退出通信,这时候服务器被占满了,后续就无法处理个更多客户端

              这时候呢,有两种解决方法:

              1. 增加服务器的数量

              但是呢,这种方法虽然好,但是缺点是需要钱来购买服务器

              1. 所以我们一般采用另一种更省钱的做法,这时候操作系统就提供了一种API,也就是IO多路复用/IO多路转接

              简单理解就是客户端在进行IO操作时可能有很多的空闲时间,这时候该线程处于空闲的状态,这时候就可以让该线程去做一些其他的操作,等空闲时间一过,又立即回来继续执行

              这样即省钱,又可以容纳更多的客户端进行通信。这里博主只是扩展一下哎,所以就不做过多赘述了。

              ⭕总结

              关于《【JavaEE初阶】 TCP服务器与客户端的搭建》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!


0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码