• 【网络编程】UDP数据报套接字编程和TCP流套接字编程


    1. 网络编程基础

    1.1 为什么需要网络编程?

    这是因为在我们本地资源中,里面的内容并不丰富,而且下载的资源大多最终还是来源于网络。
    而在网络世界中,里面有大量的资源。例如:当我们在网页中打开一个视频,听一段音乐,看一篇文章等,这些都属于网络资源(能从网络中获得的数据资源)。
    这些资源都是通过网络编程进行数据传输,所以网络编程的需要是必不可少的。

    1.2 网络编程是什么?

    网络编程就是指网络主机通过不同的进程,以编程的方式进行网络通信(或称网络数据传输)。
    1

    1.3 概念

    1. 发送端:数据发送方的进程,称发送端。发送端的主机即网络通信中的源主机。
    2. 接收端:数据接受方的进程,称接受端。接受端的主机即网络通信中的目的主机。
    3. 收发端:发送端和接受端简称收发端。
      注意:
      发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
      如上图:进程1请求获得资源时,进程1就是发送端,进程2是接收端,进程2响应时,进程2就是发送端,进程1就是接收端。
    4. 服务端:提供服务的一端,称为服务端。
    5. 客户端:获取服务的一端,称为客户端。
      如上图:主机1发出请求,主机2做出响应,那么我们就可以称主机1为客户端,主机2为服务端。

    2. Socket套接字

    概念
    Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。
    分类

    1. 流套接字:使用传输层TCP协议。
      TCP的特点:
      有连接
      可靠传输
      面向字节流
      有接收缓冲区,也有发送缓冲区
      大小不限
    2. 数据报套接字:使用传输层UDP协议
      UDP的特点:
      无连接
      不可靠传输
      面向数据报
      有接收缓冲区,无发送缓冲区
      大小受限:一次最多传输64k
    3. 原始套接字:原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。

    3. UDP数据报套接字编程

    3.1 DatagramSocket API

    DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

    DatagramSocket 构造方法:

    方法作用
    DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
    DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)
            //创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口
            DatagramSocket socket1 = new DatagramSocket();
            //创建一个UDP数据报套接字的Socket,绑定到本机9090端口
            DatagramSocket socket2 = new DatagramSocket(9090);
    
    • 1
    • 2
    • 3
    • 4

    DatagramSocket 方法:

    方法作用
    void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
    void send(DatagramPacketp)从此套接字发送数据报包(不会阻塞等待,直接发送)
    void close()关闭此数据报套接字

    3.2 DatagramPacket API

    DatagramPacket是UDP Socket发送和接收的数据报。

    DatagramPacket 构造方法:

    方法作用
    DatagramPacket(byte[] buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
    DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号

    DatagramPacket 方法:

    方法作用
    InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
    int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
    byte[] getData()获取数据报中的数据

    3.3 InetSocketAddress API

    构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

    InetSocketAddress ( SocketAddress 的子类 )构造方法:

    方法作用
    InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号

    4. UDP构建服务端客户端(一发一收)

    知道上面的,就可以创建一个简单的服务端客户端,一发一收,就是客户端请求服务端,请求什么,客户端收到什么。

    4.1 创建服务端UdpServer.java:

    public class UdpServer {
        //创建服务器socket,不实例化
        private DatagramSocket socket = null;
        public UdpServer(int port) throws SocketException {
            服务器socket绑定固定的端口
            socket = new DatagramSocket(port);
        }
        public void start() throws IOException {
            System.out.println("服务器启动!");
            //服务器放入死循环中,我们并不知道客户端什么时候访问客户端,需要不停的接受客户端的UDP数据报
            while(true){
                //1.创建数据报,读取客户端发送数据
                DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
                socket.receive(requestPacket);
                //转义
                String request = new String(requestPacket.getData(),0,requestPacket.getLength());
                //2.响应
                String response = process(request);
                //3.返回客户端
                //计算机内部处理数据的基本单位是字节。通过将接收到的数据转义为字节,长度要用字节长度
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                        requestPacket.getSocketAddress());
                socket.send(responsePacket);
                System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),
                        request,response);
            }
    
        }
        //一发一收
        public String process(String request){
            return request;
        }
        public static void main(String[] args) throws IOException {
            //实例化服务端,端口号为9092
            UdpServer udpServer = new UdpServer(9092);
            //启动服务端
            udpServer.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

    4.2 创建客户端UdpClient.java:

    public class UdpClient {
        private DatagramSocket socket = null;
        private String serverIp;
        private int serverPort;
        public UdpClient(String ip, int port) throws SocketException {
            serverIp = ip;
            serverPort = port;
            socket = new DatagramSocket();
        }
        public void start() throws IOException {
            System.out.println("客户端启动");
            Scanner sc = new Scanner(System.in);
            while(true){
                //1.输入
                System.out.println("-> ");
                String request = sc.next();
                //2.发送
    //            InetSocketAddress inetAddress = new InetSocketAddress(serverIp,serverPort);
    //            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
    //                    inetAddress);
    //			  计算机内部处理数据的基本单位是字节。通过将接收到的数据转义为字节,长度要用字节长度
                DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                        InetAddress.getByName(serverIp),serverPort);
                socket.send(requestPacket);
    
                //3.读取
                DatagramPacket responsePacket = new DatagramPacket(new byte[4096],0,4096);
                socket.receive(responsePacket);
                String response = new String(responsePacket.getData(),0,responsePacket.getLength());
                //4.显示
                System.out.println(response );
            }
    
        }
        public static void main(String[] args) throws IOException {
            UdpClient udpClient = new UdpClient("127.0.0.1",9092);
            udpClient.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

    运行结果:
    33
    44

    5. UDP构建服务端客户端(翻译)

    4中服务器并没有什么功能,那么我们也可以为服务器赋予一些功能,例如:翻译。
    我们只需要基于4代码的基础上,对服务器进行修改。

    创建翻译服务端UdpServer.java:

    public class UdpServer {
        //创建服务器socket,不实例化
        private DatagramSocket socket = null;
        //翻译集合
        private Map<String,String> dict = new HashMap<>();
        public UdpServer(int port) throws SocketException {
            //服务器socket绑定固定的端口
            socket = new DatagramSocket(port);
    
            //添加单词
            dict.put("cat","小猫");
            dict.put("dog","小狗");
            dict.put("bird","鸟");
        }
        public void start() throws IOException {
            System.out.println("服务器启动!");
            //服务器放入死循环中,我们并不知道客户端什么时候访问客户端,需要不停的接受客户端的UDP数据报
            while(true){
                //1.创建数据报,读取客户端发送数据
                DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
                socket.receive(requestPacket);
                //转义,计算机内部处理数据的基本单位是字节。通过将接收到的数据转义为字节,服务器可以更容易地处理和解析数据,不会出错
                String request = new String(requestPacket.getData(),0,requestPacket.getLength());
                //2.响应
                String response = process(request);
                //3.返回客户端
                DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                        requestPacket.getSocketAddress());
                socket.send(responsePacket);
                System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),
                        request,response);
            }
    
        }
        //一发一收
        public String process(String request){
            return dict.getOrDefault(request,"我还是个小菜鸡");
        }
    
        public static void main(String[] args) throws IOException {
            //实例化服务端,端口号为9092
            UdpServer udpServer = new UdpServer(9092);
            //启动服务端
            udpServer.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

    运行结果:
    在这里插入图片描述
    在这里插入图片描述
    其实,相对于4只是修改了一些方法和属性,那么,我们也可以重新写一个类继承4中的服务器,这样会更加的简单。

    6. TCP流套接字编程

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

    6.1 ServerSocket API

    ServerSocket 构造方法:

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

    ServerSocket 方法:

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

    6.2 Socket API

    Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
    不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

    Socket 构造方法:

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

    Socket 方法:

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

    6.3 TCP中的长短连接

    TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

    1. 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
    2. 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

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

    1. 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
    2. 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
    3. 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

    7. TCP构建服务端客户端(一发一收)

    7.1 创建服务端TcpServer.java

    public class TcpServer {
        private ServerSocket serverSocket = null;
        private ExecutorService service = Executors.newCachedThreadPool();
        public TcpServer(int port) throws IOException {
            serverSocket = new ServerSocket(port);
        }
        public void start() throws IOException {
            System.out.println("服务器启动!");
            while(true){
                Socket socket = serverSocket.accept();
                service.submit(new Runnable() {
                    @Override
                    public void run() {
                        processConnection(socket);
                    }
                });
    //            Thread thread = new Thread(() -> {
    //                processConnection(socket);
    //            });
    //            thread.start();
            }
        }
    
        public void processConnection(Socket socket){
            System.out.printf("客户端上线: [%s:%d]\n",socket.getInetAddress().toString(),socket.getPort());
            try(InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()){
                while(true){
                    Scanner sc = new Scanner(inputStream);
                    if(!sc.hasNext()){
                        System.out.printf("客户端下线: [%s:%d]\n",socket.getInetAddress().toString(),socket.getPort());
                        break;
                    }
                    String request = sc.next();
                    String response = process(request);
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(response);
                    writer.flush();
                    System.out.printf("[%s:%d] req: %s, resp: %s\n",socket.getInetAddress().toString(),socket.getPort(),
                            request,response);
                }
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public String process(String request) {
            return request;
        }
    
        public static void main(String[] args) throws IOException {
            TcpServer tcpServer = new TcpServer(9090);
            tcpServer.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

    7.2 创建客户端TcpClient.java

    public class TcpClient {
        private Socket socket = null;
        public TcpClient(String ip, int port) throws IOException {
            socket = new Socket(ip,port);
        }
        public void start(){
            System.out.println("客服端启动!");
            Scanner sc = new Scanner(System.in);
            try(InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()){
                while(true){
                    System.out.println("-> ");
                    String request = sc.next();
                    PrintWriter writer = new PrintWriter(outputStream);
                    writer.println(request);
                    writer.flush();
                    Scanner scanner = new Scanner(inputStream);
                    String response = scanner.next();
                    System.out.println(response);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) throws IOException {
            TcpClient tcpClient = new TcpClient("127.0.0.1",9090);
            tcpClient.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

    Tcp简单翻译和Udp修改规则相同。

    8. 启动多个客户端

    1.这里是引用
    2.在这里插入图片描述
    3. 在这里插入图片描述
    4.
    在这里插入图片描述

  • 相关阅读:
    信道数据传输速率、信号传播速度——参考《天勤计算机网络》
    java中feign远程调用底层是用Hystrix作为熔断器吗?
    让环境自己说话,论环境自描述的重要性
    2014年3月13日 Go生态洞察:并发模式与管道取消技术
    微信小程序 getUserProfile 获取用户信息 iv encryptedData 参数
    Windows访问centOS的Tomcat
    【MyBatis源码分析】四、XML解析与核心对象的构建
    qt绘图事件
    火山引擎数智平台:CDP产品要能与多方联动
    radware负载均衡器配置adc
  • 原文地址:https://blog.csdn.net/weixin_73392477/article/details/133470131