网络通信


网络通信

1、三要素

  • IP
  • 端口
  • 协议

package com.dapixiu;

import java.net.InetAddress;

public class Main {
    /**
     * 主函数,用于演示如何获取和打印主机信息
     */
    public static void main(String[] args) throws Exception {
        // 获取本地主机的InetAddress对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);

        // 获取并打印本地主机的主机名
        String hostName = localHost.getHostName();
        System.out.println(hostName);

        // 获取并打印本地主机的IP地址
        String address = localHost.getHostAddress();
        System.out.println(address);

        // 通过域名获取InetAddress对象
        InetAddress ip2 = InetAddress.getByName("www.bilibili.com");

        // 获取并打印指定域名的主机名
        String ip2HostName = ip2.getHostName();
        System.out.println(ip2HostName);

        // 获取并打印指定域名的IP地址
        String address1 = ip2.getHostAddress();
        System.out.println(address1);

        // 判断是否联通
        boolean ip2Reachable = ip2.isReachable(6000);
        System.out.println(ip2Reachable);
    }
}

运行结果

2、2个通信协议

UDP 和 TCP

2.1 TCP协议的三次握手建立连接

三次握手

  • TCP协议下,数据传输会进行确认,以保证数据传输的可靠性。

2.2 TCP协议的四次挥手断开连接

四次挥手

3、UDP快速入门

UDP通信

  • 特点:无连接,不可靠通信
  • 不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端IP、等信息封装成一个数据包,发出去就不管了。
  • Java提供了一个java.net.Datagramsocket类来实现UDP通信:

模拟客户端和服务端的一对一单向传输:

package com.dapixiu;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class Client {
    /**
     * 主函数,用于创建客户端 DatagramSocket,发送数据包到指定服务器端口
     * @param args 命令行参数
     * @throws Exception 如果网络操作失败
     */
    public static void main(String[] args) throws Exception {
        // 创建 DatagramSocket 实例,用于发送和接收数据包
        DatagramSocket socket = new DatagramSocket();

        // 准备要发送的数据
        byte[] bytes = "我是客户端,abs".getBytes();

        // 创建 DatagramPacket,指定数据、数据长度、目标服务器地址和端口
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 6666);

        // 通过 socket 发送数据包到指定服务器
        socket.send(packet);

        // 打印发送完成的消息
        System.out.println("客户端发送完毕~~~");

        // 关闭 socket 以释放资源
        socket.close();
    }
}
package com.dapixiu;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class Server {
    /**
     * 主函数,用于接收并打印通过UDP协议发送到端口6666的数据包
     *
     * @param args 命令行参数,未使用
     * @throws Exception 如果在创建DatagramSocket或接收数据包时发生错误
     */
    public static void main(String[] args) throws Exception {
        // 创建一个 DatagramSocket 实例,绑定到端口 6666 以监听UDP数据包
        DatagramSocket socket = new DatagramSocket(6666);

        // 创建一个缓冲区,用于存储接收到的数据
        byte[] buffer = new byte[1024 * 64];

        // 创建一个 DatagramPacket 实例,用于接收数据到缓冲区
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        // 等待并接收一个UDP数据包
        socket.receive(packet);

        String address = packet.getAddress().getHostAddress();
        int port = packet.getPort();
        System.out.println("端口是:" + port);
        System.out.println("客户端的IP是" + address);

        // 获取接收到的数据包的实际长度
        int len = packet.getLength();

        // 根据接收到的数据包内容创建字符串,并打印
        String s = new String(buffer, 0, len);
        System.out.println(s);
    }
}

4、TCP快速入门

4.1 介绍TCP

1

2

4.2 开发客户端

服务端现在只有一个主线程,只能处理一个客户端的消息。

客户端

package com.dapixiu;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    /**
     * 主函数,用于发送信息到指定服务器
     * 该函数创建一个Socket连接到本地主机的指定端口,并发送一条消息
     * @param args 命令行参数,本例中未使用
     * @throws Exception 如果Socket连接或数据发送过程中发生错误,则抛出异常
     */
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动");
        // 创建Socket连接到本地主机的8888端口
        Socket socket = new Socket("127.0.0.1", 8888);

        // 获取Socket的输出流,用于发送数据
        OutputStream os = socket.getOutputStream();
        // 包装输出流,以便于发送UTF-8编码的字符串
        DataOutputStream dos = new DataOutputStream(os);

        // 发送消息到服务器
        dos.writeUTF("在一起?不要!");

        // 关闭数据输出流,表明发送完毕
        dos.close();
        socket.close();
    }
}
package com.dapixiu;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;

public class Server {
    /**
     * 主函数,用于启动一个简单的服务器,接收客户端的消息并打印出来
     * 该函数使用ServerSocket在指定端口监听客户端的连接,接收到客户端连接后
     * 读取客户端发送的消息并打印到控制台,同时打印出客户端的IP地址
     *
     * @param args 命令行参数,本例中未使用
     * @throws Exception 如果网络通信出现异常,将抛出此异常
     */
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动");
        // 创建ServerSocket,监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);

        // 等待客户端的连接
        Socket socket = serverSocket.accept();

        // 获取客户端发送的数据流
        InputStream is = socket.getInputStream();

        // 使用DataInputStream读取数据流中的数据
        DataInputStream dis = new DataInputStream(is);

        // 读取客户端发送的UTF格式字符串并打印到控制台
        String rs = dis.readUTF();
        System.out.println(rs);

        // 获取并打印客户端的IP地址
        SocketAddress ip = socket.getRemoteSocketAddress();
        System.out.println(ip);

        // 关闭DataInputStream和Socket连接,释放资源
        dis.close();
        socket.close();
    }
}

4.3 多发多受

package com.dapixiu;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    /**
     * 主函数,用于发送信息到指定服务器
     * 该函数创建一个Socket连接到本地主机的指定端口,并发送一条消息
     *
     * @param args 命令行参数,本例中未使用
     * @throws Exception 如果Socket连接或数据发送过程中发生错误,则抛出异常
     */
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动");
        // 创建Socket连接到本地主机的8888端口
        Socket socket = new Socket("127.0.0.1", 8888);

        // 获取Socket的输出流,用于发送数据
        OutputStream os = socket.getOutputStream();
        // 包装输出流,以便于发送UTF-8编码的字符串
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);

        while (true) {
            // 发送消息到服务器
            System.out.println("请说:");
            String msg = sc.nextLine();

            if (msg.equals("exit")) {
                System.out.println("欢迎下次光临,退出成功");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}
package com.dapixiu;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;

public class Server {
    /**
     * 主函数,用于启动一个简单的服务器,接收客户端的消息并打印出来
     * 该函数使用ServerSocket在指定端口监听客户端的连接,接收到客户端连接后
     * 读取客户端发送的消息并打印到控制台,同时打印出客户端的IP地址
     *
     * @param args 命令行参数,本例中未使用
     * @throws Exception 如果网络通信出现异常,将抛出此异常
     */
    public static void main(String[] args) throws Exception {
        System.out.println("服务端启动");
        // 创建ServerSocket,监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);

        // 等待客户端的连接
        Socket socket = serverSocket.accept();

        // 获取客户端发送的数据流
        InputStream is = socket.getInputStream();

        // 使用DataInputStream读取数据流中的数据
        DataInputStream dis = new DataInputStream(is);

        // 获取并打印客户端的IP地址
        SocketAddress ip = socket.getRemoteSocketAddress();
        System.out.println(ip);

        while (true) {
            try {
                // 读取客户端发送的UTF格式字符串并打印到控制台
                String rs = dis.readUTF();
                System.out.println(rs);
            } catch (Exception e) {
                String s = socket.getRemoteSocketAddress() + "离线了";
                System.out.println(s);
                dis.close();
                socket.close();
                break;
            }
        }
    }
}

4.4 TCP通信-多个客户端同时通信

架构

package com.dapixiu;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

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

        // 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
        OutputStream os = socket.getOutputStream();
        // 3、把低级的字节输出流包装成数据输出流
        DataOutputStream dos = new DataOutputStream(os);

        Scanner sc = new Scanner(System.in);

        while (true) {
            // 发送消息到服务器
            System.out.print("请说:");
            String msg = sc.nextLine();

            if (msg.equals("exit")) {
                System.out.println("欢迎下次光临,退出成功");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}
package com.dapixiu;

import java.net.ServerSocket;
import java.net.Socket;

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

        while (true) {
//            2.使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket = serverSocket.accept();
//            3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }
    }
}
package com.dapixiu;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ServerReaderThread extends Thread {
    private Socket socket;

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

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                } catch (Exception e) {
                    String s = socket.getRemoteSocketAddress() + "下线了";
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.5 群聊(C/S架构)

群聊架构

Client:

package com.dapixiu;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("客户端启动");
        // 1、创建Socket对象,并同时请求与服务端程序的连接。
        Socket socket = new Socket("127.0.0.1", 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) {
            // 发送消息到服务器
            System.out.print("请说:");
            String msg = sc.nextLine();

            if (msg.equals("exit")) {
                System.out.println("欢迎下次光临,退出成功");
                dos.close();
                socket.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

ClientReaderThread:

package com.dapixiu;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class ClientReaderThread extends Thread {
    private Socket socket;

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

    @Override
    public void run() {

        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                } catch (Exception e) {
                    String s = socket.getRemoteSocketAddress() + "下线了";
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Server:

package com.dapixiu;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server {
    public static List<Socket> onLineSockets = new ArrayList<>();
    public static void main(String[] args) throws Exception {
        System.out.println("------------服务端启动---------");
//            1.创建ServerSocket的对象,同时为服务端注册端口。
        ServerSocket serverSocket = new ServerSocket(8888);

        while (true) {
//            2.使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
            Socket socket = serverSocket.accept();
            onLineSockets.add(socket);
//            3. 把这个客户端对应的socket通信管道,交给一个独立的线程负责处理
            new ServerReaderThread(socket).start();
        }


    }
}

ServerReaderThread:

package com.dapixiu;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class ServerReaderThread extends Thread {
    private Socket socket;

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

    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            while (true) {
                try {
                    String msg = dis.readUTF();
                    System.out.println(msg);
                    // 把这个消息发给全部的在线客户端进行接收。

                    sendMsgToAll(msg);
                } catch (Exception e) {
                    String s = socket.getRemoteSocketAddress() + "下线了";
                    //有人离线就清除
                    Server.onLineSockets.remove(socket);
                    dis.close();
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    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();
        }
    }
}

4.6 实现一个简易的BS架构

格式

要求从浏览器中访问服务器并立即让服务器响应一个很简单的网页给浏览器展示,网页内容就是“黑马程序员666”

package com.dapixiu;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    /**
     * 主函数入口
     * 该函数负责启动服务器,并监听客户端的连接请求
     * @param args 命令行参数,本例中未使用
     * @throws IOException 如果服务器启动过程中出现I/O错误
     */
    public static void main(String[] args) throws IOException {
        // 通知服务器启动成功
        System.out.println("-----服务端启动成功------");
        // 创建ServerSocket实例,绑定到指定端口
        ServerSocket serverSocket = new ServerSocket(8080);
        // 服务器循环监听,等待客户端连接
        while (true){
            // 接受客户端连接请求
            Socket socket = serverSocket.accept();
            // 获取并打印客户端地址信息
            String s = socket.getRemoteSocketAddress() + "上线了";
            System.out.println(s);
            // 创建一个新的线程处理客户端的读取操作
            new ServerReaderThread(socket).start();
        }
    }
}
package com.dapixiu;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

public class ServerReaderThread extends Thread {
    private Socket socket;

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

    /**
     * 重写run方法以处理客户端连接
     * 该方法通过socket向客户端发送HTTP响应
     */
    @Override
    public void run() {
        OutputStream os = null;
        try {
            // 获取socket的输出流,用于向客户端发送数据
            os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);

            // 发送HTTP响应头,包括状态码和内容类型
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            // 结束HTTP头的发送
            ps.println();

            // 发送HTTP响应体,这里是一个简单的字符串
            ps.println("黑马程序员666");

            // 关闭socket连接
            socket.close();
        } catch (Exception e) {
            // 异常处理留空,可以根据需要添加日志记录或异常处理逻辑
        }
    }
}

4.7 BS简易优化

优化架构

package com.dapixiu;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Server {
    /**
     * 主函数入口
     * 该函数负责启动服务器,并监听客户端的连接请求
     *
     * @param args 命令行参数,本例中未使用
     * @throws IOException 如果服务器启动过程中出现I/O错误
     */
    public static void main(String[] args) throws IOException {
        // 通知服务器启动成功
        System.out.println("-----服务端启动成功------");
        // 创建ServerSocket实例,绑定到指定端口
        ServerSocket serverSocket = new ServerSocket(8080);
        // 创建出一个线程池,负责处理通信管道的任务。
        ThreadPoolExecutor pool = new ThreadPoolExecutor(16 * 2, 32, 0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 服务器循环监听,等待客户端连接
        while (true) {
            // 接受客户端连接请求
            Socket socket = serverSocket.accept();
            // 获取并打印客户端地址信息
            String s = socket.getRemoteSocketAddress() + "上线了";
            System.out.println(s);
            // 创建一个新的线程处理客户端的读取操作
            ServerReaderRunnable runnable = new ServerReaderRunnable(socket);
            pool.execute(runnable);
        }
    }
}
package com.dapixiu;

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;
    }

    /**
     * 重写run方法以处理客户端连接
     * 该方法通过socket向客户端发送HTTP响应
     */
    @Override
    public void run() {
        OutputStream os = null;
        try {
            // 获取socket的输出流,用于向客户端发送数据
            os = socket.getOutputStream();
            PrintStream ps = new PrintStream(os);

            // 发送HTTP响应头,包括状态码和内容类型
            ps.println("HTTP/1.1 200 OK");
            ps.println("Content-Type:text/html;charset=UTF-8");
            // 结束HTTP头的发送
            ps.println();

            // 发送HTTP响应体,这里是一个简单的字符串
            ps.println("黑马程序员666");

            // 关闭socket连接
            socket.close();
        } catch (Exception e) {
            // 异常处理留空,可以根据需要添加日志记录或异常处理逻辑
        }
    }
}

文章作者: ZhangYao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ZhangYao !
  目录