• java网络编程


    网络编程套接字

    网络编程套接字: 是操作系统给应用程序提供的API(socket API), socket API是应用层和传输层沟通的渠道. 传输层的核心协议有 UDP协议, TCP协议, 对应的也有
    流套接字:使用传输层TCP协议,
    数据报套接字:使用传输层UDP协议
    两者的区别也不小:

    1. TCP : 面向连接, 可靠传输, 面向字节流, 全双工
    2. UDP : 无连接, 不可靠传输, 面向字节报, 全双工
    3. 连接: 有连接就像打电话, 需要两方都在线,才能进行沟通. 无连接就像发微信, 不用双方都在线, 可以直接发送数据
    4. 传输: 可靠传输就是在传输过程中, 发送方知道接收方是否已经接收信息(就像打电话,已读). 不可靠传输就是发出数据后,发送方不知道接收方是否已经接收到数据(就像微信)
    5. 面向字节流 : 以字节为单位传输,(类似于 IO 操作的字节流)
    6. 面向数据报 : 以数据报为单位进行传输. 一次发送/接收必须是一个完整的数据报
    7. 全双工: 链路双向通信

    UDP

    UDP API比较简单, 主要涉及到两个类:

    1. DatagramSocket 类, 一个DatagramSocket对象对应一个操作系统当中的 socket 文件. 操作系统中的文件可能表示了一些硬件设备/网络资源. socket文件, 对应着"网卡"这种硬件设备. 可认为socket文件时操作网卡的遥控器. 方法: receive(套接字接收数据报) send(套接字发送数据报) close(关闭数据报套接字)
    2. DatagramPacket , 是一个UDP数据报, 是UDP传输数据的基本单位.

    UDP回显服务

    回显服务就是请求是啥,返回的响应就是啥. 整个回显服务可以分成两部分:

    1. UdpEchoServer 服务器
    2. UdpEchoClient 客户端

    服务器部分

    1. 创建UDP服务器时,首先需要首先打开一个socket文件(即要先构造一个socket实例).
    //  创建 UDP 服务器,首先要打开一个 socket 文件
        private DatagramSocket socket = null;
    
    • 1
    • 2
    1. 绑定对应进程的端口号
     public UdpEchoServer(int port) throws SocketException {
            socket = new DatagramSocket(port);
        }
    
    • 1
    • 2
    • 3
    1. 多个进程不能绑定同一个端口号, 但是一个进程可以绑定多个端口号

    启动服务器:

    1. UDP不需要建立连接, 所以服务器直接接收客户端发来的数据. 通过DatagramPacket构造一个对象, 然后通过socket.receive()将数据填充到DatagramPacket对象里.
    2. 将请求进行解析. 把DatagramPacket对象转化成一个String, 并根据请求计算响应(此处完成的时回显服务, 所以直接返回数据)
    3. 将响应返回给客户端. 将响应构造成DatagramPacket对象, 通过sent方法返回.
    4. 在发送数据的时候, 必须指定数据要发送给谁, 也就是要指定IP和端口
      服务器代码:
    public class UdpEchoServer {
        //  创建 UDP 服务器,首先要打开一个 socket 文件
        private DatagramSocket socket = null;
    
        public UdpEchoServer(int port) throws SocketException {
            socket = new DatagramSocket(port);
        }
    
        //启动服务器
        public void start() throws IOException{
            System.out.println("服务器启动");
            while(true){
                //1. 读取客户端发来的请求
                DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
                socket.receive(requestPacket);
                //2. 对请求进行解析, 把DatagrapPacket 转成一个 String
                String request = new String(requestPacket.getData(),0,requestPacket.getLength());
                //3. 根据请求 处理响应,单独搞个方法
                String response = process(request);
                // 4. 把响应构造成 DatagramPacket 对象
                //     构造响应对象, 要搞清楚, 对象要发给谁  谁给发的请求,就把响应发给谁
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length
                     ,requestPacket.getSocketAddress());
                // 5. 把这个DatagramPacket 对象返回给客户端
                socket.send(responsePacket);
                System.out.printf("[%s:%d] req=%s; resp=%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),
                        request,response);
            }
        }
    
        //通过这个方法,实现根据请求计算响应 这个过程
        //回显服务器 ,不涉及到其他逻辑
        //其他服务器, 可以在process里 加上其他逻辑处理
        public String process(String req){
            return req;
        }
    
        public static void main(String[] args) throws IOException {
            //真正启动服务器 ,端口号是随便写  范围 0->65535
            // 一般来说 1024 以下的端口  系统保留
            //因此自己写代码 尽量选择 1024以上 65535以下
            UdpEchoServer server = new UdpEchoServer(8000);
            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

    客户端部分

    客户端的端口号, 一般无需自己指定. 操作系统会自己分配端口号.

    1. 客户端从控制台读取用户的请求数据
    2. 根据用户的请求数据, 构造一个DatagramPacket对象:
      a. 数据的内容 request 字符串
      b. 数据要发送到哪里 即要发送的IP + 端口号
    3. 将数据报发送给服务器
    4. 从服务器读取响应的数据
    5. 将响应数据转化成字符串, 并展示到控制台中
      客户端代码:
    public class UdpEchoClient {
        private DatagramSocket socket = null;
    
        public UdpEchoClient() throws SocketException {
            // 客户端的端口号,一般由操作系统自动分配
            socket = new DatagramSocket();
        }
    
        public void start() throws IOException {
            Scanner scanner = new Scanner(System.in);
            while (true){
                // 1. 让客户端从控制台读取一个请求数据
                System.out.print("> ");
                String request = scanner.next();
                // 2. 把这个字符串请求发送给服务器, 构造 DatagramPacket
                // 构造的 Packet 既要包含 要传输的数据  又要包含把数据发到哪里
                DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName("127.0.0.1"),8000);
                // 3. 把数据报 发给服务器
                socket.send(requestPacket);
                // 4. 从服务器读取响应数据
                DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
                socket.receive(responsePacket);
                // 5. 把响应数据获取出来,转成转成字符串
                String response = new String(responsePacket.getData(),0,responsePacket.getLength());
    
                System.out.printf("res: %s; resp: %s\n",request,response);
    
            }
        }
    
        public static void main(String[] args) throws IOException {
            UdpEchoClient client = new UdpEchoClient();
            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

    展示效果:
    启动服务器:
    在这里插入图片描述
    启动客户端:
    在这里插入图片描述
    效果:
    在这里插入图片描述
    在这里插入图片描述

    加上业务逻辑

    通过上述基础的回显服务,我们可以加上一些业务逻辑,例如实现一个字典翻译:

    1. 客户端的代码不变
    2. 调整服务端的process代码
      关键 :继承服务端, 重写process方法. 通过哈希表来构造词典
      代码部分:
    public class UdpDictServer extends UdpEchoServer{
        private Map<String,String> dict = new HashMap<>();
    
        public UdpDictServer(int port) throws SocketException {
            super(port);
    
            // 数据可以无限的构造下去
            dict.put("cat","小猫");
            dict.put("dog"," 小狗");
        }
    
        // 和 UdpEchoServer 相比, 只是 process 不同,就重写这个方法就可
        @Override
        public String process(String req){
            return dict.getOrDefault(req,"该词未收录");
        }
    
        public static void main(String[] args) throws IOException {
            UdpEchoServer server = new UdpDictServer(8000);
            server.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    展示效果:
    在这里插入图片描述

    TCP

    TCP是以字节流为单位来传输的, TCP主要涉及到两个类:

    1. ServerSocket (主要给服务器使用)
    2. Socket(服务器 和 客户端都使用)

    回显服务

    服务器部分

    建立连接
    因为TCP是有连接的, 所以我们要先通过 socket的accept()方法来建立连接.

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
        	//如果当前没有客户端佬建立连接, 就会阻塞等待
            Socket clientSocket = serverSocket.accept();
            processConnect(clientSocket);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    处理请求

        // 通过这个方法 给当前连上的客户端 提供服务
        // 一个连接过来 服务方式 两种
        // 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
        // 2. 一个连接进行多次数据交互 (n个请求  n个响应)  长连接
        //此处来写 长连接版本
        public void processConnect(Socket clientSocket) throws IOException{
            System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
            try (InputStream inputStream = clientSocket.getInputStream();
                 OutputStream outputStream = clientSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);
                PrintWriter printWriter = new PrintWriter(outputStream);
    
                // 长连接的写法,需要通过 循环 来获取多次交互情况
                while(true){
                    if(!scanner.hasNext()){
                        // 当客户端断开连接的时候, hasNext 返回 false
                        System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    // 1. 读取请求并解析
                    String request = scanner.next();
                    // 2. 根据请求计算响应
                    String response = process(request);
                    // 3. 把响应写回客户端
                    printWriter.println(response);
                    // 刷新缓冲区,避免数据没有发出去
                    printWriter.flush();
                    System.out.printf("[%s:%d] req: %s; resp: %s\n",
                            clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                            request,response);
                }
            }finally {
                //加在此处
                clientSocket.close();
            }
        }
    
    • 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

    服务端整体代码:

    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("服务器启动");
            while (true) {
                //如果当前没有客户端佬建立连接, 就会阻塞等待
                Socket clientSocket = serverSocket.accept();
                processConnect(clientSocket);
            }
        }
    
        // 通过这个方法 给当前连上的客户端 提供服务
        // 一个连接过来 服务方式 两种
        // 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
        // 2. 一个连接进行多次数据交互 (n个请求  n个响应)  长连接
        //此处来写 长连接版本
        public void processConnect(Socket clientSocket) throws IOException{
            System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
            try (InputStream inputStream = clientSocket.getInputStream();
                 OutputStream outputStream = clientSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);
                PrintWriter printWriter = new PrintWriter(outputStream);
    
                // 长连接的写法,需要通过 循环 来获取多次交互情况
                while(true){
                    if(!scanner.hasNext()){
                        // 当客户端断开连接的时候, hasNext 返回 false
                        System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    // 1. 读取请求并解析
                    String request = scanner.next();
                    // 2. 根据请求计算响应
                    String response = process(request);
                    // 3. 把响应写回客户端
                    printWriter.println(response);
                    // 刷新缓冲区,避免数据没有发出去
                    printWriter.flush();
                    System.out.printf("[%s:%d] req: %s; resp: %s\n",
                            clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                            request,response);
                }
            }finally {
                //加在此处
                clientSocket.close();
            }
        }
        public String process(String req){
            return req;
        }
    
        public static void main(String[] args) throws IOException{
            TcpEchoServer server = new TcpEchoServer(8000);
            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

    客户端部分

    使用Socket构造对象. 客户端的socket不用绑定端口号:

    1. TCP的ServerSocket, 构造方法指定的端口, 是服务器要绑定的端口
    2. TCP的Socket, 构造方法指定的端口, 是要连接的服务器的端口
      客户端代码:
    public class TcpEchoClient {
        private Socket socket = null;
        public TcpEchoClient() throws IOException {
            // new对象 需要和服务器建立连接
            //建立连接  需要知道服务器在哪里
            socket = new Socket("127.0.0.1",8000);
        }
    
        public void start() throws IOException {
            // 实现长连接,一个连接处理 N 个 请求和响应
            Scanner scanner = new Scanner(System.in);
            try(InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()){
                Scanner scannerNet  = new Scanner(inputStream);
                PrintWriter printWriter = new PrintWriter(outputStream);
                while(true){
                    // 1.从控制台读取用户的输入
                    System.out.println("> ");
                    String request = scanner.next();
                    // 2. 将请求发送给服务器
                    printWriter.println(request);
                    printWriter.flush();
                    // 3. 从服务器读取响应
                    String response = scannerNet.next();
                    // 4. 将结果显示到界面上
                    System.out.printf("req: %s; resp: %s \n", request,response);
                }
            }
        }
    
        public static void main(String[] main) throws IOException {
            TcpEchoClient tcpEchoClient = new TcpEchoClient();
            tcpEchoClient.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

    效果展示
    服务端:
    在这里插入图片描述
    客户端:
    在这里插入图片描述

    多客户端多线程版本

    上面的版本只能一次连接一个客户端, 我们可以用多线程来完成连接多个客户端版本:
    只修改start部分:

    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("服务器启动");
            while(true){
               
                Socket clientSocket = serverSocket.accept();
              
                //[版本2] 多线程版本, 主线程负责拉客, 新线程负责通信
                //虽然有提升, 但是涉及到频繁创建销毁线程,在高并发情况下,负担较重
                Thread t = new Thread(() ->{
                    try {
                        processConnect(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
                t.start();
            }
        }
    
        // 通过这个方法 给当前连上的客户端 提供服务
        // 一个连接过来 服务方式 两种
        // 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
        // 2. 一个连接进行多次数据交互 (n个请求  n个响应)  长连接
        //此处来写 长连接版本
        public void processConnect(Socket clientSocket) throws IOException{
            System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
            try (InputStream inputStream = clientSocket.getInputStream();
                 OutputStream outputStream = clientSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);
                PrintWriter printWriter = new PrintWriter(outputStream);
    
                // 长连接的写法,需要通过 循环 来获取多次交互情况
                while(true){
                    if(!scanner.hasNext()){
                        // 当客户端断开连接的时候, hasNext 返回 false
                        System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    // 1. 读取请求并解析
                    String request = scanner.next();
                    // 2. 根据请求计算响应
                    String response = process(request);
                    // 3. 把响应写回客户端
                    printWriter.println(response);
                    // 刷新缓冲区,避免数据没有发出去
                    printWriter.flush();
                    System.out.printf("[%s:%d] req: %s; resp: %s\n",
                            clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                            request,response);
                }
            }finally {
                //加在此处
                clientSocket.close();
            }
        }
        public String process(String req){
            return req;
        }
    
        public static void main(String[] args) throws IOException{
            TcpEchoServer server = new TcpEchoServer(8000);
            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:
    在这里插入图片描述
    客户端2:
    在这里插入图片描述
    服务端:
    在这里插入图片描述

    多线程由线程池实现

    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("服务器启动");
            ExecutorService service = Executors.newCachedThreadPool();
            while(true){
               
                Socket clientSocket = serverSocket.accept();
              
                // 使用线程池,来解决频繁创建销毁线程的问题
                //此处不太适合使用"固定个数的"
                service.submit(new Runnable(){
    
                    @Override
                    public void run() {
                        try{
                            processConnect(clientSocket);
                        }catch(IOException e){
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    
        // 通过这个方法 给当前连上的客户端 提供服务
        // 一个连接过来 服务方式 两种
        // 1. 一个连接只进行一次数据交互(一个请求 一个响应) 短 连接
        // 2. 一个连接进行多次数据交互 (n个请求  n个响应)  长连接
        //此处来写 长连接版本
        public void processConnect(Socket clientSocket) throws IOException{
            System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
            try (InputStream inputStream = clientSocket.getInputStream();
                 OutputStream outputStream = clientSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);
                PrintWriter printWriter = new PrintWriter(outputStream);
    
                // 长连接的写法,需要通过 循环 来获取多次交互情况
                while(true){
                    if(!scanner.hasNext()){
                        // 当客户端断开连接的时候, hasNext 返回 false
                        System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
                        break;
                    }
                    // 1. 读取请求并解析
                    String request = scanner.next();
                    // 2. 根据请求计算响应
                    String response = process(request);
                    // 3. 把响应写回客户端
                    printWriter.println(response);
                    // 刷新缓冲区,避免数据没有发出去
                    printWriter.flush();
                    System.out.printf("[%s:%d] req: %s; resp: %s\n",
                            clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                            request,response);
                }
            }finally {
                //加在此处
                clientSocket.close();
            }
        }
        public String process(String req){
            return req;
        }
    
        public static void main(String[] args) throws IOException{
            TcpEchoServer server = new TcpEchoServer(8000);
            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
  • 相关阅读:
    Flutter学习9 - http 中 get/post 请求示例
    一个详细例子说明vue components 组件间调用方法,传值问题的示例
    sql语句之字符串截取函数(substring_index)
    找不到类org.springframework.cloud.client.loadbalancer.LoadBalanced
    力扣(LeetCode)261. 以图判树(2022.09.18)
    大词表语言模型在续写任务上的一个问题及对策
    【网络安全技术】——期末复习(冲刺篇)
    jq——基础操作——jq操作回顾(都忘光了。。。)
    【开发指南】AR Foundation 开发环境部署
    【SA8295P 源码分析】130 - GMSL2 协议分析 之 I2C/UART 双向控制通道原理分析
  • 原文地址:https://blog.csdn.net/m0_62476684/article/details/126413037