【网络编程通关之路】 Tcp 基础回显服务器(Java实现)及保姆式知识原理详解 ! ! !

润信云 技术支持
  1. 一. Tcp 回显服务器的普通实现

  2. 1. Tcp 的特点

  3. Tcp 是 可靠 传输, Udp是 不可靠 传输。

  4.  Tcp是面向 字节流, Udp是面向 数据报

  5.  Tcp 是 有连接 , Udp是 无连接 。

  6.  Tcp 是 全双工 , Udp也是 全双工 。
    关于以上这些 特点 还有 回显服务器 的详解全部都收录到了 网络编程通关之路】 Udp 基础回响服务器(Java实现)及你不知道知识原理详解 ! ! ! 这篇文章中 。

  7. 2. Tcp使用的相关类

  8. Tcp 与Udp一样, 在进行网络通信的过程中,需要调用系统 Socket api , 但在 Java标准库 中已经封装好了相关的类, 通过这几个类, 在 JVM 中来调用系统原生的 Socket api 来 操作网卡 , 进行网络通信。

  9. 关于Tcp 来实现回显

  10. ServerSocket 类 :

    image.png

  11. 实例化ServerSocket 对象时, 只用于服务器一端 , 并且进行 端口号 连接 进程 的过程。

  12. 用于建立 服务器 与 客户端 连接,并且返回 Socket 对象。

    accept() 方法

鱼式疯言

从上面可以确定 Tcp的 有连接 的特点之一。


ServerSocket的构造方法 的连接是 端口号与进程的连接 , 相当于 “接好了电话线” 。


accept() 的连接是 服务器与客户端的连接 , 相当于 客户端那边给服务器 “打电话”, 而服务器这边就用 accept() 来接电话。完成连接。

 

Socket 类 :

image.png

用于客户端与服务器连接:

Socket 的构造方法, 在 实例化Socket 对象 时, 添加 IP地址 和 端口号, 就意味着 建立了连接 。


操作 Socket对象 来获取各种 IP地址 和 输入和输出流对象 。

获取IP地址

getInetAddress()

获取输入字节流方法
getInputStream()

获取输出字节流方法

getOutputStream()

鱼式疯言

1.由于我们 Tcp 特点 之一就是 面向字节流 , 并且是 全双工 。

所以我们既可以获取到 输入字节流对象 ,也可以获取到 输出字节流对象 。

2.ServerSocket 类一般只适用于 服务器一端的端口号和进程的连接 , 而 Socket 一般只适用于 客户端一端 与 服务器 的连接, 但是都适用于 服务器和客户端 的 获取输入流与输出流 的操作。

3.在 同一台主机 上,同一个协议 , 一个 端口号 直接连接 一个进程 。

但是在不同协议中, 一个端口号 可以连接 不同协议 下的进程。

4.无论是 ServerSocket 类 还是 Socket 类 , 都是属于 java.net 下的包的类。


回显服务器的普通实现代码展示

<1>. 服务器实现

package echoTCP;

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;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;


public class MyTcpEchoServer {

    ServerSocket serverSocket = null;

    // 进行连接
    public MyTcpEchoServer(int port) {
        try {
            // 进行连接
            serverSocket = new ServerSocket(port);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public  void start() {
        // 开始交互

        System.out.println("服务器开始运行...");
        while(true) {
        // 使用线程池





            Socket socket = null;

            try {
                socket = serverSocket.accept();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            // 进行接收请求并响应
            ProcessServerConnect(socket);



        }







    }

    private void ProcessServerConnect(Socket socket) {
            try(InputStream inputSTream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()
            ) {


                while(true) {

                    System.out.printf("客户端上线 : [客户端ip = %s,
                    客户端端口号 = %d]\n"
                    ,socket.getInetAddress(),
                    socket.getPort());
                    
                    // 得到请求
                    Scanner scanner = new Scanner(inputSTream);
                    if(!scanner.hasNext()) {
                        break;
                    }

                    // 得到请求
                    String request = scanner.nextLine();

                    // 计算响应
                    // 执行业务
                   String response =  process(request);

                   // 返回响应
                   // 让客户端得到业务的执行结果
                   PrintWriter printWriter = new 
                   PrintWriter(outputStream);

                   // 开始返回
                   printWriter.println(response);
                   // 进行刷新
                    printWriter.flush();


//                    打印日志
                    System.out.printf("客户端下线 :  [ip = %s , 
                    port = %d], 请求 = %s , 响应 = %s\n",
                            socket.getInetAddress(),
                            socket.getPort(),request,response);


                }



            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                System.out.printf("任务执行结束: [%s,%d]\n",
                socket.getInetAddress(),
                socket.getPort());
                try {
//                    关闭 socket 流
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

    }

    private String process(String request) {
        return  request;
    }


    public static void main(String[] args) {
        MyTcpEchoServer myTcpEchoServer = new MyTcpEchoServer(9090);
        myTcpEchoServer.start();
    }
}

image.png

服务器一端的实现的整体流程可分为以下几步:

  1. 1.创建 ServerSocket 对象 , 在构造方法中参数列表添加 端口号 , 建立端口号与进程的 连接 

       // 进行连接
                serverSocket = new ServerSocket(port);
    1. 2.调用 accept 方法让服务器与客户端进行,并返回一个 Socket 对象。

       Socket socket = null;
    
            try {
                socket = serverSocket.accept();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    1. 3.获取到输入流,利用 Scanner 包装输入流 得到请求 。

    // 得到请求
    Scanner scanner = new Scanner(inputSTream);
    if(!scanner.hasNext()) {
        break;
    }
    
    // 得到请求
    String request = scanner.nextLine();
    1. 4.计算响应 并把 输出流对象 包装到 printWriter 输出到客户端

    // 计算响应
        // 执行业务
       String response =  process(request);
    
       // 返回响应
       // 让客户端得到业务的执行结果
       PrintWriter printWriter = new 
       PrintWriter(outputStream);
    
       // 开始返回
       printWriter.println(response);
    // 进行刷新
        printWriter.flush();
    1. 5.打印日志,并关闭 Socket 对象

    //                    打印日志
                     System.out.printf("客户端下线 :  [ip = %s , 
                     port = %d], 请求 = %s , 响应 = %s\n",
                             socket.getInetAddress(),
                             socket.getPort(),request,response);
    
    
                 }
    
    
    
             } catch (IOException e) {
                 throw new RuntimeException(e);
             } finally {
                 System.out.printf("任务执行结束: [%s,%d]\n",
                 socket.getInetAddress(),
                 socket.getPort());
                 try {
    //                    关闭 socket 流
                     socket.close();
                 } catch (IOException e) {
                     throw new RuntimeException(e);
                 }
             }

    鱼式疯言

    补充细节

    1. 1.对于大部分 IO流 来说, 并不是 输入或输出一个/ 一段 就会加载到 硬盘 , 而是会存放到一个叫 缓冲区的地方, 当数据积攒到 一定的量 , 才会 加载过去 。


    2. 所以我们为了每次只加载 一个 / 一段数据 , 可以调用 flush 这个方法 来将 缓冲区 的数据全部

    3. 刷新出去。                     

     // 进行刷新
          printWriter.flush();

    2.小编在这里利用了 Scanner 和 printWriter 来包装输入和输出流。 但是小伙伴们也可以利用小编之前提及过的 IO流的操作方式 来进行哦, 效果是一样的 。

    关于IO的流的操作文章, 可以多学习学习并且回去哦 ? ? ? ?

    3.对于上述 回显服务器 来说, 服务端一端 accept() 只有当服务器每次发来请求时,才会进行 连接, 否则进行 阻塞等待

    <2>. 客户端实现

    package echoTCP;
    
    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 MyTcpEchoClient {
        Socket clientSocket = null;
    
        // 进行连接
        public MyTcpEchoClient(String ip , int port) {
            try {
                // 进行连接
                clientSocket = new Socket(ip, port);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        public  void start() {
            // 开始交互
    
    
                System.out.println("客户端开始运行...");
    
    
                    // 发送请求并接受响应
                    ProcessServerConnect(clientSocket);
    
    
        }
    
        private void ProcessServerConnect(Socket socket) {
            try(InputStream inputSTream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()
            ) {
    
                while(true) {
    
    
    
                    Scanner in = new Scanner(System.in);
                    System.out.print("请输入你的需求-> ");
    
    
    //                输入请求并发送
                    String request = in.nextLine();
    
                    PrintWriter printWriter = new 
                    PrintWriter(outputStream);
                    printWriter.println(request);
                    // 进行刷新
                    printWriter.flush();
    
    
                    // 接收响应并输出
                    Scanner scanner = new Scanner(inputSTream);
                    if(!scanner.hasNext()) {
                        break;
                    }
    
                    String response = scanner.nextLine();
    
                    System.out.println("response = "+ response);
    
                }
    
    
    
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                System.out.printf("任务执行结束: 
                [%s,%d]\n",
                socket.getInetAddress(),
                socket.getPort());
                try {
    //                    关闭 socket 流
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
    
        }
    
    
        public static void main(String[] args) {
            MyTcpEchoClient myTcpEchoClient = new 
            MyTcpEchoClient("127.0.0.1",9090);
            myTcpEchoClient.start();
        }
    }

    image.png

    具体客户端一端的整体实现流程梳理

      1.实例化Socket 对象, 并把 IP地址 和 端口号 作为 Socket 构造方法的参数进行 客户端与服务端 的连接。

      Socket clientSocket = null;
      
      // 进行连接
      public MyTcpEchoClient(String ip , int port) {
          try {
              // 进行连接
              clientSocket = new Socket(ip, port);
          } catch (IOException e) {
              throw new RuntimeException(e);
          }
      }

      2.输入请求 并发送给 客户端

                      Scanner in = new Scanner(System.in);
                      System.out.print("请输入你的需求-> ");
      
      
      //                输入请求并发送
                      String request = in.nextLine();
      
                      PrintWriter printWriter = new 
                      PrintWriter(outputStream);
                      printWriter.println(request);
                      // 进行刷新
                      printWriter.flush();

      3.接收服务器返回的 响应并输出

       // 接收响应并输出
          Scanner scanner = new Scanner(inputSTream);
          if(!scanner.hasNext()) {
              break;
          }
      
          String response = scanner.nextLine();
      
          System.out.println("response = "+ response);

      4.宣布服务器执行结束, 并 关闭Socket对象

       finally {
                  System.out.printf("任务执行结束: 
                  [%s,%d]\n",
                  socket.getInetAddress(),
                  socket.getPort());
                  try {
      //                    关闭 socket 流
                      socket.close();
                  } catch (IOException e) {
                      throw new RuntimeException(e);
                  }
              }

      鱼式疯言

      补充细节 :

      1.对应上述代码中的 hasNext() 方法来说, 出现了 换行 才会 break 结束 出去, 而当 服务器/ 客户端 没有 发来数据时, 是会进入 阻塞等待 的, 不会break 直接跳出。
           if(!scanner.hasNext()) {
                        break;
                    }

        二. Tcp 回显服务器的多线程实现

        1. 代码展示

        服务器源码

        package echoTCP;
        
        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;
        import java.util.concurrent.Executor;
        import java.util.concurrent.Executors;
        
        
        public class MyTcpEchoServer {
        
            ServerSocket serverSocket = null;
        
            // 进行连接
            public MyTcpEchoServer(int port) {
                try {
                    // 进行连接
                    serverSocket = new ServerSocket(port);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        
            public  void start() {
                // 开始交互
        
                System.out.println("服务器开始运行...");
        
        
        
        //        使用多线程
        
                while(true) {
                    Thread t = new Thread(()->{
        
                        Socket socket = null;
                        try {
                            socket = serverSocket.accept();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
        
                        // 进行接收请求并响应
                        ProcessServerConnect(socket);
        
        
                    });
                    t.start();
                }
        
        
        
        
            }
        
            private void ProcessServerConnect(Socket socket) {
                    try(InputStream inputSTream = socket.getInputStream();
                        OutputStream outputStream = socket.getOutputStream()
                    ) {
        
        
                        while(true) {
        
                            System.out.printf("客户端上线 : [客户端ip = %s,客户端端口号 = %d]\n",socket.getInetAddress(),socket.getPort());
                            // 得到请求
                            Scanner scanner = new Scanner(inputSTream);
                            if(!scanner.hasNext()) {
                                break;
                            }
        
                            // 得到请求
                            String request = scanner.nextLine();
        
                            // 计算响应
                            // 执行业务
                           String response =  process(request);
        
                           // 返回响应
                           // 让客户端得到业务的执行结果
                           PrintWriter printWriter = new PrintWriter(outputStream);
        
                           // 开始返回
                           printWriter.println(response);
                           // 进行刷新
                            printWriter.flush();
        
        
        //                    打印日志
                            System.out.printf("客户端下线 :  [ip = %s , port = %d], 请求 = %s , 响应 = %s\n",
                                    socket.getInetAddress(),socket.getPort(),request,response);
        
        
                        }
        
        
        
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } finally {
                        System.out.printf("任务执行结束: [%s,%d]\n",socket.getInetAddress(),socket.getPort());
                        try {
        //                    关闭 socket 流
                            socket.close();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
        
            }
        
            private String process(String request) {
                return  request;
            }
        
        
            public static void main(String[] args) {
                MyTcpEchoServer myTcpEchoServer = new MyTcpEchoServer(9090);
                myTcpEchoServer.start();
            }
        }

        客户端源码 :

        package echoTCP;
        
        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 MyTcpEchoClient {
            Socket clientSocket = null;
        
            // 进行连接
            public MyTcpEchoClient(String ip , int port) {
                try {
                    // 进行连接
                    clientSocket = new Socket(ip, port);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        
            public  void start() {
                // 开始交互
                    System.out.println("客户端开始运行...");
        
        
                        // 发送请求并接受响应
                        ProcessServerConnect(clientSocket);
        
        
            }
        
            private void ProcessServerConnect(Socket socket) {
                try(InputStream inputSTream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream()
                ) {
        
                    while(true) {
        
        
        
                        Scanner in = new Scanner(System.in);
                        System.out.print("请输入你的需求-> ");
        
        
        //                输入请求并发送
                        String request = in.nextLine();
        
                        PrintWriter printWriter = new PrintWriter(outputStream);
                        printWriter.println(request);
                        // 进行刷新
                        printWriter.flush();
        
        
                        // 接收响应并输出
                        Scanner scanner = new Scanner(inputSTream);
                        if(!scanner.hasNext()) {
                            break;
                        }
        
                        String response = scanner.nextLine();
        
                        System.out.println("response = "+ response);
        
        
        
                    }
        
        
        
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } finally {
                    System.out.printf("任务执行结束: [%s,%d]\n",socket.getInetAddress(),socket.getPort());
                    try {
        //                    关闭 socket 流
                        socket.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
        
            }
        
        
            public static void main(String[] args) {
                MyTcpEchoClient myTcpEchoClient = new MyTcpEchoClient("127.0.0.1",9090);
                myTcpEchoClient.start();
            }
        }

        image.png

        image.png

        image.png


        2. 原理剖析

        对于多线程的回显服务器的实现, 最主要是解决 多个 客户端同时给 一个 服务器 发送请求 的问题。

        对于服务器来说 外边套着一个循环 , 里面又加了 一层循环 , 这样的话就会导致 两层循环 的加入

        当 第一个客户端进入内循环 时, 它的任务还 没结束, 第二个客户端就向服务器 发送请求 , 这时由于内部的还 一直循环, 服务器就无法进入内循环 , 只能让第二个客户端一直停留在 缓冲区
        这时我们就引入 多线程 的方式, 将每一个服务器都 分布同时 执行循环, 这样就能保证不会让 多个 服务器一直阻塞到 缓冲区 。

        关于代码的实现实现流程, 只是在普通回显服务器的基础上加了 一行多线程 的代码。 其他都是一样的, 小编在这里就 不赘述了 。

        三. Tcp 回显服务器的线程池实现

        1. 代码展示

        服务器一端

        package echoTCP;
        
        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;
        import java.util.concurrent.Executor;
        import java.util.concurrent.Executors;
        
        
        public class MyTcpEchoServer {
        
            ServerSocket serverSocket = null;
        
            // 进行连接
            public MyTcpEchoServer(int port) {
                try {
                    // 进行连接
                    serverSocket = new ServerSocket(port);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        
            public  void start() {
                // 开始交互
        
                System.out.println("服务器开始运行...");
                while(true) {
                // 使用线程池
                Executor executor = Executors.newCachedThreadPool();
        
                executor.execute(() -> {
        
        
                    Socket socket = null;
        
                    try {
                        socket = serverSocket.accept();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
        
                    // 进行接收请求并响应
                    ProcessServerConnect(socket);
        
        
                });
                }
        
        
        
        
        
        
        
        
        
            }
        
            private void ProcessServerConnect(Socket socket) {
                    try(InputStream inputSTream = socket.getInputStream();
                        OutputStream outputStream = socket.getOutputStream()
                    ) {
        
        
                        while(true) {
        
                            System.out.printf("客户端上线 : [客户端ip = %s,客户端端口号 = %d]\n",socket.getInetAddress(),socket.getPort());
                            // 得到请求
                            Scanner scanner = new Scanner(inputSTream);
                            if(!scanner.hasNext()) {
                                break;
                            }
        
                            // 得到请求
                            String request = scanner.nextLine();
        
                            // 计算响应
                            // 执行业务
                           String response =  process(request);
        
                           // 返回响应
                           // 让客户端得到业务的执行结果
                           PrintWriter printWriter = new PrintWriter(outputStream);
        
                           // 开始返回
                           printWriter.println(response);
                           // 进行刷新
                            printWriter.flush();
        
        
        //                    打印日志
                            System.out.printf("客户端下线 :  [ip = %s , port = %d], 请求 = %s , 响应 = %s\n",
                                    socket.getInetAddress(),socket.getPort(),request,response);
        
        
                        }
        
        
        
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } finally {
                        System.out.printf("任务执行结束: [%s,%d]\n",socket.getInetAddress(),socket.getPort());
                        try {
        //                    关闭 socket 流
                            socket.close();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
        
            }
        
            private String process(String request) {
                return  request;
            }
        
        
            public static void main(String[] args) {
                MyTcpEchoServer myTcpEchoServer = new MyTcpEchoServer(9090);
                myTcpEchoServer.start();
            }
        }

        image.png

        客户端一端 :

        package echoTCP;
        
        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 MyTcpEchoClient {
            Socket clientSocket = null;
        
            // 进行连接
            public MyTcpEchoClient(String ip , int port) {
                try {
                    // 进行连接
                    clientSocket = new Socket(ip, port);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        
            public  void start() {
                // 开始交互
                    System.out.println("客户端开始运行...");
        
        
                        // 发送请求并接受响应
                        ProcessServerConnect(clientSocket);
        
        
            }
        
            private void ProcessServerConnect(Socket socket) {
                try(InputStream inputSTream = socket.getInputStream();
                    OutputStream outputStream = socket.getOutputStream()
                ) {
        
                    while(true) {
        
        
        
                        Scanner in = new Scanner(System.in);
                        System.out.print("请输入你的需求-> ");
        
        
        //                输入请求并发送
                        String request = in.nextLine();
        
                        PrintWriter printWriter = new PrintWriter(outputStream);
                        printWriter.println(request);
                        // 进行刷新
                        printWriter.flush();
        
        
                        // 接收响应并输出
                        Scanner scanner = new Scanner(inputSTream);
                        if(!scanner.hasNext()) {
                            break;
                        }
        
                        String response = scanner.nextLine();
        
                        System.out.println("response = "+ response);
        
        
        
                    }
        
        
        
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } finally {
                    System.out.printf("任务执行结束: [%s,%d]\n",socket.getInetAddress(),socket.getPort());
                    try {
        //                    关闭 socket 流
                        socket.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
        
            }
        
        
            public static void main(String[] args) {
                MyTcpEchoClient myTcpEchoClient = new MyTcpEchoClient("127.0.0.1",9090);
                myTcpEchoClient.start();
            }
        }

        image.png

        image.png

        代码说明

        关于线程池的代码实现: 主要还是在原来 普通 回显服务器的基础上, 加上了线程池的 newCachedThreadPool 对象 。

        这个线程池对象在 任务繁忙 时, 最多可以创建出 Integer.MAX_VALUES 个 非核心线程

        本文链接:https://blog.runxinyun.com/post/95.html 转载需授权!

        分享到:
        版权声明
        网站名称: 润信云资讯网
        本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
        不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。
        我们非常重视版权问题,如有侵权请邮件与我们联系处理。敬请谅解!邮件:7104314@qq.com
        网站部分内容来源于网络,版权争议与本站无关。请在下载后的24小时内从您的设备中彻底删除上述内容。
        如无特别声明本文即为原创文章仅代表个人观点,版权归《润信云资讯网》所有,欢迎转载,转载请保留原文链接。
        0 104

        留言0

        评论

        ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。