• JavaEE——No.2 套接字编程(TCP)


    JavaEE传送门

    JavaEE

    JavaEE——网络通信基础

    JavaEE——No.1 套接字编程(UDP)



    套接字编程

    TCP 的 socket

    TCP socket 要掌握的类:

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

    ServerSocket API

    构造方法:

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

    方法:

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

    Socket API

    构造方法:

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

    方法:

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

    后两种方法, 通过 socket 可以获取到两个流对象, 分别用来读和写


    回显服务器

    服务器

    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;
    
    /**
     * User: gu'jiu
     * Description:服务器
     */
    public class TcpEchoServer {
        //代码中会涉及到多个 socket 对象
        private ServerSocket listenSocket = null;
    
        public TcpEchoServer(int port) throws IOException {
            listenSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动! ");
            while(true) {
                //先调用 accept 来接受客户端的连接
                Socket clientSocket = listenSocket.accept();
                //再处理这个连接
                processConnection(clientSocket);    
            }
        }
    
        private void processConnection(Socket clientSocket) throws IOException {
            System.out.printf("[%s, %d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            try(InputStream inputStream = clientSocket.getInputStream();
                OutputStream outputStream = clientSocket.getOutputStream()) {
                while(true) {
                    //读取请求并解析
                    Scanner scanner = new Scanner(inputStream);
                    if(!scanner.hasNext()) {
                        //读完了, 连接可以断开了
                        System.out.printf("[%s, %d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                                clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next();
                    //根据请求计算相应
                    String response = process(request);
                    //把响应写回给客户端
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    printWriter.flush();
    
                    System.out.printf("[%s: %d] req: %s; reqs: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 关闭 socket
                clientSocket.close();
            }
    
        }
    
        //回显
        public static String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoServer server = new TcpEchoServer(9092);
            server.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    部分代码解释

    1. 通过 clientSocket 来获取到 inputStream 和 outputStream.

    • 由于我们所说的 TCP 是面向字节流的, 和之前所介绍到的文件操作是完全相同的. 所以我们也是通过前面所学习过的这些字节流这些类, 来去完成我们数据的一个读写操作.

    • 这里我们的流对象就不再代表着磁盘文件了, 而是代表着 socket. 换句话说, 我们从这个 inputStream 读取数据, 就是从我们的网卡上读取数据; 从outputStream 写数据, 就是在向网卡中写数据.

    • cilentSocket 代表着的是服务器的网卡, 向 clientSocket 读写数据, 就是相当于向网卡中读写数据, 也就相当于在客户端中读写数据.

    1. socket 也是一个文件, 我们之前说过, 一个进程能同时打开的文件个数是有上限的.

      • listenSocket 是在 TCP 服务器中, 只有唯一一个对象, 就不太会把文件描述符表沾满(随着进程的退出, 自动释放)
      • 而 clientSocket 是在循环中, 每个客户端连上都要分配一个. 如果不关闭, 就会持续消耗文件描述符, 因此就需要把不再使用的 clientSocket 及时释放掉.

    客户端

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    
    /**
     * User: gu'jiu
     * Description:客户端
     */
    public class TcpEchoClient {
        //客户端需要使用这个 socket 对象来建立链接
        Socket socket = null;
    
        public TcpEchoClient(String serverIP, int serverPort) throws IOException {
            //和服务器建立链接, 需要知道服务器的 ip 和端口号
            socket = new Socket(serverIP, serverPort);
        }
    
        public void start() {
            Scanner scanner = new Scanner(System.in);
            try(InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()) {
                while(true) {
                    //从控制台读取数据, 构造成一个请求
                    System.out.println("------------------");
                    System.out.print("-> ");
                    String request = scanner.next();
                    //发送请求给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush();
                    //从服务器读取响应
                    Scanner respScanner = new Scanner(inputStream);
                    String response = respScanner.next();
                    //把响应显示到界面上
                    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", 9092);
            client.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    运行结果展示

    启动服务器

    此时没有客户端连接, accept( ) 是阻塞的状态

    启动客户端

    启动客户端之后的服务器 (accept( ) 阻塞结束)

    在客户端输入"HelloGujiu"

    关闭客户端后的服务器


    改进版

    一个服务器一般情况下, 都给多个客户端提供服务.

    我们勾选一下 "Allow multiple instances"

    JavaEE——No.1 套接字编程(内含打开方式)

    这时我们启动两个客户端, 但是这时的服务器并没有出现两次 “客户端上线!”

    为什么会出现这种情况呢?

    • 如果没有客户端建立连接, 服务器就会阻塞到 accept

    • 如果有一个客户端过来了, 此时就会进入 start() 方法

    • 此时代码就阻塞在 .hasNext() 这里

    • 于是, 我们就无法第二次调用到 accept, 也就无法处理第二个客户端了

    为什么UDP 没有这个问题, TCP 有这个问题呢?

    • UDP 客户端直接发消息即可

    • TCP 建立连接之后, 要处理客户端的多次请求, 才导致无法快速的调到 accept (长连接)

      如果 TCP 每个连接只处理一个客户端的请求, 也能够保证快速调到 accept (短连接)

    解决方案

    使用线程池

    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.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * User: gu'jiu
     * Description:服务器
     */
    public class TcpEchoServer {
        //代码中会涉及到多个 socket 对象
        private ServerSocket listenSocket = null;
    
        public TcpEchoServer(int port) throws IOException {
            listenSocket = new ServerSocket(port);
        }
    
        public void start() throws IOException {
            System.out.println("服务器启动! ");
            ExecutorService service = Executors.newCachedThreadPool();
            while(true) {
                //先调用 accept 来接受客户端的连接
                Socket clientSocket = listenSocket.accept();
                //再处理这个连接
                service.submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            processConnection(clientSocket);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    
        private void processConnection(Socket clientSocket) throws IOException {
            System.out.printf("[%s, %d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
            try(InputStream inputStream = clientSocket.getInputStream();
                OutputStream outputStream = clientSocket.getOutputStream()) {
                while(true) {
                    //读取请求并解析
                    Scanner scanner = new Scanner(inputStream);
                    if(!scanner.hasNext()) {
                        //读完了, 连接可以断开了
                        System.out.printf("[%s, %d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                                clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next();
                    //根据请求计算相应
                    String response = process(request);
                    //把响应写回给客户端
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    printWriter.flush();
    
                    System.out.printf("[%s: %d] req: %s; reqs: %s\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 关闭 socket
                clientSocket.close();
            }
    
        }
    
        public static String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpEchoServer server = new TcpEchoServer(9092);
            server.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    🌷(( ◞•̀д•́)◞⚔◟(•̀д•́◟ ))🌷

    以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!
    在这里插入图片描述
    加粗样式

    这里是Gujiu吖!!感谢你看到这里🌬
    祝今天的你也
    开心满怀,笑容常在。
  • 相关阅读:
    kubernetes集群搭建Zabbix监控平台
    【学习笔记】:Maven初级
    Qlik GetFieldSelections 详解(用于返回当前选择项的字符串)
    JDBC实现数据库批量插入
    Docker的私有仓库部署——Harbor
    Redis 分布式锁
    OPC UA 服务器的Web访问
    docker容器操作
    蛋白结构合理性评估软件-PROCHECK的安装与使用
    useragent在线查找
  • 原文地址:https://blog.csdn.net/m0_58592142/article/details/127425114