【网络通信】详解网络通信、实现 CS / BS架构 通信

文章目录

2种基本通信架构:


网络通信三要素

IP地址

IP:"互联网协议地址",是分配给上网设备的唯一标识

被广泛采用的IP地址形式 :
IPv4:使用32位地址,通常以点分十进制表示。

IPv6:使用128位地址

冒分十六进制:IPv6分成8段,每段每四位编码成一个十六进制位表示, 每段之间用冒号(:)分开。

IP域名用于在互联网上我们能识别和定位网站的名称。例如:www.baidu.com

DNS域名解析:

是互联网中用于将域名转换为对应IP地址的分布式命名系统。它充当了互联网的"电话簿",将易记的域名映射到数字化的IP地址,使得用户可以通过域名来访问网站和其他网络资源。

  1. 公网IP:是可以连接到互联网的IP地址。
  2. 内网IP:也叫局域网IP,是只能组织机构内部使用的IP地址;范围为192.168.0.0--192.168.255.255。
  3. 本机IP:127.0.0.1、localhost,只会寻找当前程序所在的主机。

IP常用指令

  • ipconfig:查看本机IP地址。
  • ping IP地址:检查网络是否连通。

InetAddress:代表IP地址。常用方法:

端口

用来唯一标识 正在计算机设备上运行的应用程序,被规定为一个 16 位的二进制,范围是 0~65535。

端口分类 概念
周知端口 0~1023,被预先定义的知名应用占用
(如:HTTP占用 80,FTP占用21)
注册端口 1024~49151,分配给用户进程或某些应用程序
动态端口 49152到65535,它一般不固定分配某种进程,而是动态分配

注意:

自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则报错。

通信协议【CS架构】

通信协议:是指网络上通信的设备,事先规定的连接规则 ,以及传输数据的规则

(1)OSI网络参考模型:全球网络互联标准。

(2)TCP/IP网络模型:事实上的国际标准。

(1)UDP

UDP:是用户数据报协议。通常用在:视频直播、语音通话。

特点:无连接、不可靠通信

  1. 无连接:不事先建立连接。发送方不管对方是否在线,数据在中间丢失也不管
  2. 不可靠通信:接收方收到数据也不返回确认

Java提供了一个java.net.DatagramSocket 类来实现UDP通信。

一发一收

【客户端】:

java 复制代码
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("===客户端启动==");
        
        // 1、创建DatagramSocket对象(客户端对象)
        DatagramSocket socket = new DatagramSocket(); // 随机端口

        // 2、创建DatagramPacket对象封装需要发送的数据(数据包对象)   
        byte[] bytes = "我是客户端".getBytes();
        /**
         *   public DatagramPacket(byte[] buf, int length,
         *                         InetAddress address, int port)
         * 参数一:发送的数据,字节数组
         * 参数二:发送的字节长度。
         * 参数三:目的地的IP地址。
         * 参数四:服务端程序的端口号
         */
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8080);

        // 3、使用DatagramSocket对象的send方法,传入数据包的数据
        socket.send(packet);
        
        // 4、释放资源
        socket.close();
    }
}

【服务端】:

java 复制代码
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("==服务端启动了===");
        // 1、创建DatagramSocket对象并指定端口(服务端对象)
        DatagramSocket socket = new DatagramSocket(8080);

        // 2、创建DatagramPacket对象接收数据(数据包对象)
        byte[] buf = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        // 3、接收数据,将数据封装到数据包对象的字节数组中去
        socket.receive(packet);

        // 4、看看数据是否收到了
        int len = packet.getLength();   // 获取当前收到的数据长度。
        String data = new String(buf, 0 , len);
        System.out.println("服务端收到了:" + data);

        // 获取对方的ip对象和程序端口
        String ip = packet.getAddress().getHostAddress();
        int port = packet.getPort();
        System.out.println("对方ip:" + ip + "   对方端口:" + port);

        socket.close();
    }
}

多发多收

【客户端】:

java 复制代码
public class UDPClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("===客户端启动==");
        // 1、创建DatagramSocket对象(发送端对象)
        DatagramSocket socket = new DatagramSocket(); // 随机端口

        Scanner sc = new Scanner(System.in);
        
        // 使用while死循环不断的接收用户的数据输入
        while (true) {
            // 2、创建DatagramPacket对象封装需要发送的数据(数据包对象)  
            System.out.println("请说:");
            String msg = sc.nextLine();

            // 如果用户输入的是 exit,则退出
            if ("exit".equals(msg)) {
                System.out.println("===客户端退出==");
                socket.close();
                break;
            }
            
            // 把数据封装成DatagramPacket
            byte[] bytes = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
                    InetAddress.getLocalHost(), 8080);

            // 3、让发送端对象发送数据包的数据
            socket.send(packet);
            
            // 4、释放资源
            socket.close();
        }

    }
}

【接收端】:接收端只负责接收数据包,无所谓是哪个发送端的数据包

java 复制代码
public class UDPServerDemo2 {
    public static void main(String[] args) throws Exception {
        // 目标:完成UDP通信多发多收:服务端开发。
        System.out.println("==服务端启动了===");
        // 1、创建DatagramSocket对象并指定端口(接收端对象)
        DatagramSocket socket = new DatagramSocket(8080);

        // 2、创建DatagramPacket对象接收数据(数据包对象)
        byte[] buf = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        while (true) {
            // 3、接收数据,将数据封装到数据包对象的字节数组中去
            socket.receive(packet); // 等待式接收数据。

            // 4、看看数据是否收到了
            int len = packet.getLength();   // 获取当前收到的数据长度。
            String data = new String(buf, 0 , len);
            System.out.println("服务端收到了:" + data);

            // 获取对方的ip对象和程序端口
            String ip = packet.getAddress().getHostAddress();
            int port = packet.getPort();
            System.out.println("对方ip:" + ip + "   对方端口:" + port);

            System.out.println("----------------------------------------------");
        }
    }
}

(2)TCP

TCP:是传输控制协议。特点:面向连接、可靠通信

可以保证在不可靠的信道上实现可靠的数据传输。通常用在:文件下载、支付。

  1. 三次握手建立可靠连接:确保通信的双方收发消息都是没问题的(全双工)

  2. 四次挥手断开连接:确保通信的双方收发消息都已经完成。

Java提供了一个java.net.Socket类来实现TCP通信。

一发一收

【客户端】:通过 java.net.Socket 类来实现

java 复制代码
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动....");
        // 1、创建客户端的Socket对象,请求与服务端的Socket连接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、使用socket对象调用getOutputStream()方法得到字节输出流
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeInt(1);
        dos.writeUTF("我想你了,你在哪儿?");

        // 4、释放资源
        socket.close();
    }
}

【服务端】:通过java.net.ServerSocket类来实现的

java 复制代码
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动了...");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(9999);
        
        // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
        Socket socket = ss.accept();
        
        // 3、获取输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();
        
        // 4、把字节输入流包装成特殊数据输入流
        DataInputStream dis = new DataInputStream(is);
        
        // 5、读取数据
        int id = dis.readInt();
        String msg = dis.readUTF();
        System.out.println("id=" + id + ",收到的客户端msg=" + msg);
        
        // 6、客户端的ip和端口
        System.out.println("客户端的ip=" + socket.getInetAddress().getHostAddress());
        System.out.println("客户端的端口=" + socket.getPort());
    }
}

多发多收

【客户端】:

java 复制代码
public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动....");
        // 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、从socket通信管道中得到一个字节输出流。
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流。
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);
        // while死循环,让用户不断输入消息
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("退出成功!");

                dos.close(); // 关闭输出流
                socket.close(); // 关闭socket
                break;
            }

            dos.writeUTF(msg); // 发送数据
            dos.flush();
        }
    }
}

【服务端】:

java 复制代码
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动了...");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(9999);
        
        // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
        Socket socket = ss.accept();
        
        // 3、获取输入流,读取客户端发送的数据
        InputStream is = socket.getInputStream();
        
        // 4、把字节输入流包装成特殊数据输入流
        DataInputStream dis = new DataInputStream(is);
        // while死循环,控制服务端程序收完消息后,继续去接收下一个消息。
        while (true) {
            // 5、读取数据
            String msg = dis.readUTF(); // 等待读取客户端发送的数据
            System.out.println("收到的客户端msg=" + msg);
            // 6、客户端的ip和端口
            System.out.println("客户端的ip=" + socket.getInetAddress().getHostAddress());
            System.out.println("客户端的端口=" + socket.getPort());
            System.out.println("--------------------------------------------------");
        }
    }
}

与多个客户端同时通信

(1)主线程定义了循环负责接收客户端Socket管道连接

(2)每接收到一个Socket通信管道后分配一个独立的线程负责处理它

【客户端】:

java 复制代码
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动....");
        // 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
        Socket socket = new Socket("127.0.0.1", 9999);

        // 2、从socket通信管道中得到一个字节输出流。
        OutputStream os = socket.getOutputStream();

        // 3、特殊数据流。
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("请说:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("退出成功!");
                dos.close(); // 关闭输出流
                socket.close(); // 关闭socket
                break;
            }

            dos.writeUTF(msg); // 发送数据
            dos.flush();
        }
    }
}

【服务端】:

java 复制代码
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动了...");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(9999);

        while (true) {
            // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了:" + socket.getInetAddress().getHostAddress());
            // 3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息。
            new ServerReader(socket).start();
        }
    }
}

【线程】:

java 复制代码
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ServerReader extends Thread{
    private Socket socket;
    public ServerReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 获取输入流,读取客户端发送的数据
            InputStream is = socket.getInputStream();
            // 把字节输入流包装成特殊数据输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                // 读取数据
                String msg = dis.readUTF(); // 等待读取客户端发送的数据
                System.out.println("收到的客户端msg=" + msg);
                // 客户端的ip和端口
                System.out.println("客户端的ip=" + socket.getInetAddress().getHostAddress());
                System.out.println("客户端的端口=" + socket.getPort());
                System.out.println("--------------------------------------------------");
            }
        } catch (Exception e) {
            System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
        }
    }
}

BS架构

BS架构的基本原理

  • 客户端使用浏览器发起请求(不需要开发客户端)
  • 服务端必须按照HTTP协议响应数据。

注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据。

HTTP协议规定:响应给浏览器的数据格式必须满足如下格式

【服务端】:

java 复制代码
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动了...");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(8080);

        // 创建线程池
        ExecutorService pool = new ThreadPoolExecutor(3, 10, 10, TimeUnit.SECONDS
        , new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        while (true) {
            // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了:" + socket.getInetAddress().getHostAddress());
            // 3、把这个客户端管道包装成一个任务交给线程池处理
            pool.execute(new ServerReaderRunnable(socket));
        }
    }
}

【线程】:

java 复制代码
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 给当前对应的浏览器管道响应一个网页数据回去。
            OutputStream os = socket.getOutputStream();
            // 通过字节输出流包装写出去数据给浏览器
            // 把字节输出流包装成打印流。
            PrintStream ps = new PrintStream(os);
            // 写响应的网页数据出去
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=utf-8");
            ps.println(); // 必须换一行
            ps.println("<html>");
            ps.println("<head>");
            ps.println("<meta charset='utf-8'>");
            ps.println("<title>");
            ps.println("lm");
            ps.println("</title>");
            ps.println("</head>");
            ps.println("<body>");
            ps.println("<h1 style='color:red;font-size=20px'>lm</h1>");
            // 响应一个黑马程序员的log展示
            ps.println("<img src='https://www.baidu.com/images/logo.png'>");
            ps.println("</body>");
            ps.println("</html>");
            ps.close();
            socket.close();
        } catch (Exception e) {
            System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
        }
    }
}

使用线程池进行优化

【服务端】:

java 复制代码
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class ServerDemo {
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动了...");
        // 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
        ServerSocket ss = new ServerSocket(8080);

        // 创建线程池
        ExecutorService pool = new ThreadPoolExecutor(3, 10, 10, TimeUnit.SECONDS
        , new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        while (true) {
            // 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
            Socket socket = ss.accept();
            System.out.println("一个客户端上线了:" + socket.getInetAddress().getHostAddress());
            // 3、把这个客户端管道包装成一个任务交给线程池处理
            pool.execute(new ServerReaderRunnable(socket));
        }
    }
}

【线程】:

java 复制代码
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

public class ServerReaderRunnable implements Runnable{
    private Socket socket;
    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 给当前对应的浏览器管道响应一个网页数据回去。
            OutputStream os = socket.getOutputStream();
            // 通过字节输出流包装写出去数据给浏览器
            // 把字节输出流包装成打印流。
            PrintStream ps = new PrintStream(os);
            // 写响应的网页数据出去
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=utf-8");
            ps.println(); // 必须换一行
            ps.println("<html>");
            ps.println("<head>");
            ps.println("<meta charset='utf-8'>");
            ps.println("<title>");
            ps.println("lm");
            ps.println("</title>");
            ps.println("</head>");
            ps.println("<body>");
            ps.println("<h1 style='color:red;font-size=20px'>lm</h1>");
            // 响应一个黑马程序员的log展示
            ps.println("<img src='https://www.baidu.com/images/logo.png'>");
            ps.println("</body>");
            ps.println("</html>");
            ps.close();
            socket.close();
        } catch (Exception e) {
            System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
        }
    }
}

项目实战

获取时间

java 复制代码
// LocalDate LocalTime LocalDateTime 获取此刻日期时间对象
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
System.out.println(now.getYear());
System.out.println(now.getDayOfYear());

// 格式化:DateTimeFormatter
// 1、创建一个格式化对象
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss EEE a");
// 2、格式化now对象的时间
String result2 = dtf.format(now);
System.out.println(result2);

StringBuilder

StringBuilder 代表可变字符串对象,相当于是一个容器。

java 复制代码
StringBuilder sb = new StringBuilder(); // StringBuilder对象是可变内容的容器  sb = "";
for (int i = 0; i < 1000000 ; i++) {
    sb.append("abc");
}
System.out.println(sb);
// StringBuilder只是拼接字符串的手段,结果还是要恢复成字符串(目的)
String s = sb.toString();
System.out.println(s);

StringBuilder sb2 = new StringBuilder();
String result = sb2.append("张三").append("李四").append("王五").toString();
System.out.println(result);

BigDecimal

用于解决浮点型运算时,出现结果失真的问题。

java 复制代码
double a = 0.1;
double b = 0.2;
System.out.println(a + b); // 0.30000000000000004

// 直接调用valueOf方法,内部使用的就是public BigDecimal(String val) 字符串构造器
BigDecimal a1 = BigDecimal.valueOf(a);
BigDecimal b1 = BigDecimal.valueOf(b);
BigDecimal c1 = a1.add(b1);  // 解决精度问题的手段
double result = c1.doubleValue();  // 把BigDecimal对象转成double类型
System.out.println(result);

System.out.println("------------");

BigDecimal i = BigDecimal.valueOf(0.1);
BigDecimal j = BigDecimal.valueOf(0.3);
// 除法
BigDecimal k = i.divide(j, 2, RoundingMode.HALF_UP);
System.out.println(k);

源码:

运行结果:

相关推荐
wangwren12 分钟前
SpringAI--RAG知识库
java·rag·spring ai·pgvector
LaLaLa_OvO13 分钟前
修改SpringBootApplication类的入参后,引用外部yml的启动命令要修改
java
黎黎黎明⁠⁢15 分钟前
SpringBoot整合Sa-Token:实现RBAC权限模型
java·sa-token·springboot·idea
Java知识库16 分钟前
聊聊JVM怎么调优?(实战总结)
java·开发语言·jvm·程序员·编程
nbsaas-boot20 分钟前
JWT 不对外,Session ID 对外:构建安全可控的微服务认证架构
安全·微服务·架构
不穿铠甲的穿山甲28 分钟前
Intellij IDEA 查找接口实现类的快捷键
java·ide·intellij-idea
山海上的风29 分钟前
idea本地git上传gitee码云失败分析,push rejected+git手动融合
git·gitee·intellij-idea
CodingKnight32 分钟前
IntelliJ IDEA Ultimate修改软件地区使用
java·ide·intellij-idea
小红的布丁44 分钟前
单例模式的隐秘危机
java·开发语言
嵌入式学习菌1 小时前
mqtt协议连接阿里云平台
物联网·网络协议·阿里云·云计算