JAVA基础篇——网络通信

发布时间:2023年12月21日

十八、网络编程

实现网络通讯,设备中的程序与网络中其他设备中的程序进行数据交互

java.net.*

1. 前置知识

基本的通信架构:CS(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)

  • CS:客户端需要程序员开发,用户需要安装;服务端也是需要程序员开发;二者要实现相互通信
  • BS:客户端没有不需要开发,安装浏览器就行;服务端就需要程序员开发,在浏览器中访问
  • 注:都需要网络编程

网络通信三要素

  • IP:设备在网络中的地址
  • 端口:应用程序在设备中的唯一标识
  • 协议:连接和数据在网络中传输的规则
  • 注:192.168.是局域网;127.0.0.1/localhist代表本机IP,只会寻找当前所在的主机

操作IP的类和方法: InetAddress

public class InetAddressTest {
    public static void main(String[] args) throws Exception {
        // 1. 获取本机IP地址对象
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1);
        // 获取主机名称
        System.out.println(ip1.getHostName());
        // 获取主机IP
        System.out.println(ip1.getHostAddress());

        // 2. 获取指定IP或域名的IP地址对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2);
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());
        // 查看是否可以连同ping
        System.out.println(ip2.isReachable(6000));
    }
}

端口知识:

端口是用来标记设备中的应用程序的,16位的二进制,范围是0~65535

分类

  • 周知端口——0~1023,预先定义的知名程序占用(HTTP占用80,FTP占用21)
  • 注册端口——1024~49151,分配给用户进程或某些应用程序。自己开发的程序一般选择使用该范围的端口号,且一个设备中不能出现两个程序的端口号一样
  • 动态端口:49152~65535,不固定分给某种进程,是动态分配的

协议知识:

OSI网络参考模型——全球网络互联标准

TCP/IP网络模型——事实上的国际标准

传输层

UDP(User Datagram Protocal):用户数据报协议——无连接、不可靠通信

TCP(Transmission Control Protovol):传输控制协议——面向连接,可靠通信(三次握手建立连接,传输数据返回确认,四次挥手断开连接)

2. UDP通信

无连接、不可靠通信

java.net.DatagramSocket类实现UDP通信

案例:实现UDP 通信的一收一发

客户端

public class Client {
    // 实现一发一收
    public static void main(String[] args) throws Exception {
        // 1. 创建一个客户端对象
        DatagramSocket socket = new DatagramSocket();  // 系统随机分配一个端口,也可以设置参数,自己定义端口
        // 2. 创建一个数据包封装要发送的数据
        // 参数一:要发送的数据,是byte数组的形式
        // 参数二:数据包的长度
        // 参数三:要发送数据的服务端IP地址
        // 参数四:端口号
        byte[] bytes = "Hello 小鱼0135".getBytes();
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 666);
        // 3. 将数据发送出去
        socket.send(packet);
        System.out.println("客户端发送数据完毕!");
        // 4. 将连接关闭,释放资源
        socket.close();
    }
}

服务端

public class Server {
    // 完成UDP通信的服务端开发
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动=====");
        // 1. 创建服务端对象
        DatagramSocket socket  = new DatagramSocket(666);

        // 2. 创建一个数据包对象接受数据
        byte[] buffer = new byte[1024 * 64];  // 数据包的数据不超过64KB
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        // 3. 接收数据
        socket.receive(packet);

        // 4. 取出数据包中的数据
        String rs = new String(buffer, 0, packet.getLength());
        System.out.println(rs);

        // 获取客户端的信息
        System.out.println(packet.getAddress());
        System.out.println(packet.getPort());

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

案例:实现UDP通信的多发多收

客户端——IDEA里面进行设置,实现客户端可以多开

public class Client {
    // 实现一发一收
    public static void main(String[] args) throws Exception {
        // 1. 创建一个客户端对象
        DatagramSocket socket = new DatagramSocket();  // 系统随机分配一个端口,也可以设置参数,自己定义端口
        // 2. 创建一个数据包封装要发送的数据
        // 参数一:要发送的数据,是byte数组的形式
        // 参数二:数据包的长度
        // 参数三:要发送数据的服务端IP地址
        // 参数四:端口号
        // 让用户自己输入数据进行发送
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.print("要发送的内容:");
            String msg = sc.nextLine();
            // 判断用户是否想跳出此循环
            if ("exit".equals(msg)) {
                System.out.println("轻轻的我走了~");
                socket.close();  // 关闭资源
                break;  // 跳出死循环
            }
            byte[] bytes = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 666);
            // 3. 将数据发送出去
            socket.send(packet);
        }
    }
}

服务端

public class Server {
    // 完成UDP通信的服务端开发
    public static void main(String[] args) throws Exception {
        System.out.println("=====服务端启动=====");
        // 1. 创建服务端对象
        DatagramSocket socket  = new DatagramSocket(666);

        // 2. 创建一个数据包对象接受数据
        byte[] buffer = new byte[1024 * 64];  // 数据包的数据不超过64KB
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        while (true) {
            // 3. 接收数据
            socket.receive(packet);

            // 4. 取出数据包中的数据
            String rs = new String(buffer, 0, packet.getLength());
            System.out.println(rs);

            // 获取客户端的信息
            System.out.println(packet.getAddress());
            System.out.println(packet.getPort());
            System.out.println("=============");
        }
    }
}

3. TCP通信

java.net.Socket类来实现TCP通信

案例:一收一发

客户端

/*
 * 实现TCP通信,一收一发
 */
public class Client {
    public static void main(String[] args) throws Exception {
        // 1. 创建Socket对象,并同时请求与服务端程序的连接
       Socket socket =  new Socket(InetAddress.getLocalHost(), 8888);

        // 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os = socket.getOutputStream();

        // 3. 将低级的字节输出流包装成数据输出流——经验
        DataOutputStream dos = new DataOutputStream(os);

        // 4. 开始写数据出去
        dos.writeUTF("小鱼仔");

        // 5. 关闭资源,要关闭流资源,也要关闭Scoket
        dos.close();
        socket.close();

    }
}

服务端

/*
 * TCP的服务端开发
 */
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功-----");
        // 1. 创建SeverSocket对象,同时为服务端注册端口
        ServerSocket severSocket = new ServerSocket(8888);

        // 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
        Socket socket = severSocket.accept();

        // 3. 从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();

        // 4. 把InputStream字节输入流包装成数据输入流
        DataInputStream dis = new DataInputStream(is);

        // 5. 使用数据输入流对象读取数据
        String re = dis.readUTF();
        System.out.println(re);
        // 获得客户端的IP
        System.out.println(socket.getRemoteSocketAddress());

        // 6. 关闭流、关闭资源
        dis.close();
        socket.close();
    }
}

案例:多收多发

客户端

/*
 * 实现TCP通信,多收多发
 */
public class Client {
    public static void main(String[] args) throws Exception {
        // 1. 创建Socket对象,并同时请求与服务端程序的连接
       Socket socket =  new Socket(InetAddress.getLocalHost(), 8888);

        // 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os = socket.getOutputStream();

        // 3. 将低级的字节输出流包装成数据输出流——经验
        DataOutputStream dos = new DataOutputStream(os);

        // 用户输入数据
        Scanner sc = new Scanner(System.in);
        while (true) {
            // 4. 开始写数据出去
            System.out.print("曰:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("走了~");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

服务端

/*
 * TCP的服务端开发
 */
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功-----");
        // 1. 创建SeverSocket对象,同时为服务端注册端口
        ServerSocket severSocket = new ServerSocket(8888);

        // 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
        Socket socket = severSocket.accept();

        // 3. 从socket通信管道中得到一个字节输入流
        InputStream is = socket.getInputStream();

        // 4. 把InputStream字节输入流包装成数据输入流
        DataInputStream dis = new DataInputStream(is);

        while (true) {
            try {
                // 5. 使用数据输入流对象读取数据
                String re = dis.readUTF();
                System.out.println(re);
            } catch (Exception e) {
                System.out.println(socket.getRemoteSocketAddress() + "离线了");
                dis.close();
                socket.close();
                break;
            }
        }
    }
}

问题:不能像UDP那样同时开多个client,因为建立连接的,双方都指定了端口,一心一意;而UDP知识指定了客户端向服务端发送的位置,没有指定服务端只能接受某个端口的数据,来者不拒——>下一节内容:服务端现在只有一个主线程,只能处理一个客户端消息

4. TCP支持与多个客户端同时通信

在这里插入图片描述

客户端

/*
 * 实现TCP通信,多收多发
 */
public class Client {
    public static void main(String[] args) throws Exception {
        // 1. 创建Socket对象,并同时请求与服务端程序的连接
       Socket socket =  new Socket(InetAddress.getLocalHost(), 8888);

        // 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os = socket.getOutputStream();

        // 3. 将低级的字节输出流包装成数据输出流——经验
        DataOutputStream dos = new DataOutputStream(os);

        // 用户输入数据
        Scanner sc = new Scanner(System.in);
        while (true) {
            // 4. 开始写数据出去
            System.out.print("曰:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("走了~");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

服务端

/*
 * TCP的服务端开发
 */
public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功-----");
        // 1. 创建SeverSocket对象,同时为服务端注册端口
        ServerSocket severSocket = new ServerSocket(8888);

        while (true) {
            // 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
            Socket socket = severSocket.accept();
            System.out.println("有人上线:" + socket.getRemoteSocketAddress());
            // 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}

线程

public class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread() {
    }

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 3. 从socket通信管道中得到一个字节输入流
        try {
            InputStream is = socket.getInputStream();
            // 4. 把InputStream字节输入流包装成数据输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                } catch (IOException e) {
                    System.out.println("一个走了" + socket.getRemoteSocketAddress());
                    dis.close();
                    socket.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
//            System.out.println("一个走了");  // nonono
        }
    }
}

5. TCP通信:群聊

客户端

public class Client {
    public static void main(String[] args) throws Exception {
        // 1. 创建Socket对象,并同时请求与服务端程序的连接
       Socket socket =  new Socket(InetAddress.getLocalHost(), 8888);

       // 创建一个独立的线程,从服务端接收消息
        new ClientReaderThread(socket).start();

        // 2. 从socket通信管道中得到一个字节输出流,用来发数据给服务端程序
        OutputStream os = socket.getOutputStream();

        // 3. 将低级的字节输出流包装成数据输出流——经验
        DataOutputStream dos = new DataOutputStream(os);

        // 用户输入数据
        Scanner sc = new Scanner(System.in);
        while (true) {
            // 4. 开始写数据出去
            System.out.print("曰:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("走了~");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

客户端线程类

public class ClientReaderThread extends Thread{
    private Socket socket;

    public ClientReaderThread() {
    }

    public ClientReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 3. 从socket通信管道中得到一个字节输入流
        try {
            InputStream is = socket.getInputStream();
            // 4. 把InputStream字节输入流包装成数据输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                } catch (IOException e) {
                    System.out.println("自己" + socket.getRemoteSocketAddress());
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
//            System.out.println("一个走了");  // nonono
        }
    }
}

服务器端

public class Server {
    // 定义一个在线socket的集合列表
    public static List<Socket> onLineSockets = new ArrayList<>();
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功-----");
        // 1. 创建SeverSocket对象,同时为服务端注册端口
        ServerSocket severSocket = new ServerSocket(8888);

        while (true) {
            // 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
            Socket socket = severSocket.accept();
            onLineSockets.add(socket);
            System.out.println("有人上线:" + socket.getRemoteSocketAddress());
            // 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}

服务器端线程类

public class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread() {
    }

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 3. 从socket通信管道中得到一个字节输入流
        try {
            InputStream is = socket.getInputStream();
            // 4. 把InputStream字节输入流包装成数据输入流
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                    // 把这个消息分发给全部客户端接收
                    sendMsgToAll(msg);
                } catch (IOException e) {
                    System.out.println("一个走了" + socket.getRemoteSocketAddress());
                    Server.onLineSockets.remove(socket);
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
//            System.out.println("一个走了");  // nonono
        }
    }

    private void sendMsgToAll(String msg) throws Exception {
        // 发送给全部在线的socket管道接收
        for (Socket onLineSocket : Server.onLineSockets) {
            OutputStream os = onLineSocket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

6. BS架构

浏览器端输出的内容格式要满足HTTP格式

服务器端:

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端启动成功-----");
        // 1. 创建SeverSocket对象,同时为服务端注册端口
        ServerSocket severSocket = new ServerSocket(8080);

        while (true) {
            // 2. 创建Socket对象,使用ServerSocket调用accept()方法,等待客户端的连接请求
            Socket socket = severSocket.accept();
            System.out.println("有人上线:" + socket.getRemoteSocketAddress());
            // 3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}

服务器线程类:

public class ServerReaderThread extends Thread{
    private Socket socket;

    public ServerReaderThread() {
    }

    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        // 3. 从socket通信管道中得到一个字节输出流
        try {
            OutputStream os = socket.getOutputStream();
            // 4. 把OnputStream字节输入流包装成打印输入流
            PrintStream pos = new PrintStream(os);
            pos.println("HTTP/1.1 200 OK");
            pos.println("Content-Type:text/html;charset=UTF-8");
            pos.println();  // 这三行是必须有的
            pos.println("小鱼0135");
            socket.close();

        } catch (Exception e) {
            e.printStackTrace();
//            System.out.println("一个走了");  // nonono
        }
    }
}

7. 拓展知识

线程池优化——避免死机

文章来源:https://blog.csdn.net/qq_44689664/article/details/135069862
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。