自学内容网 自学内容网

Java 网络编程:Socket 与网络通信

1 引言

在古代,由于通信不便利,人们利用鸽子的飞行能力和方向辨识能力,驯化鸽子进行消息传递,即所谓的“飞鸽传书”。在现代计算机网络中,套接字(Socket)扮演了类似的角色。套接字是应用程序通过网络发送或接收数据的抽象层,允许应用程序将输入输出(I/O)操作应用于网络中,并与其他应用程序进行通信。

2 套接字(Socket)简介

套接字是 IP 地址与端口的组合,是网络通信的基础。通过套接字,应用程序可以像操作文件一样打开、读写和关闭网络连接。

3 网络调试工具:ping 与 telnet

在调试网络程序时,pingtelnet 是两个非常有用的工具。

  • ping:用于测试数据包能否通过 IP 协议到达特定主机。ping 会向目标主机发出一个 ICMP 请求回显数据包,并等待接收回显响应数据包。通过 ping 命令,可以检查网络连接是否正常。

    例如,我们 ping 一下百度。截图如下。
    在这里插入图片描述

  • telnet:用于远程登录到另一台计算机。通过 telnet,用户可以在本地计算机上登录到远程计算机,进行交互式操作。在 Windows 系统中,telnet 通常是默认安装但未激活的,可以通过控制面板启用。使用 telnet 时,远程计算机需要运行一个服务,该服务持续监听网络连接请求。当接收到客户端的连接请求时,服务器进程会被唤醒,并为两者建立连接,直到某一方中止连接。

  • 然而,由于 telnet 是明文传输协议,用户的所有内容(包括用户名和密码)都没有经过加密,因此在现代网络技术中,telnet 的安全性受到质疑,并不被广泛使用。
    例如,我们 telnet 一下火(shui)土(mu)社区。截图如下。
    在这里插入图片描述

4 Socket 实例:客户端

以下是一个简单的 Java 客户端套接字(Socket)示例,模拟 telnet 命令连接远程服务器并读取数据。

import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import java.io.IOException;

public class SocketClientExample {
    public static void main(String[] args) {
        try (Socket socket = new Socket("bbs.newsmth.net", 23)) {
            InputStream is = socket.getInputStream();
            Scanner scanner = new Scanner(is, "gbk");

            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.1 代码解析:

  1. 建立套接字连接

    Socket socket = new Socket("bbs.newsmth.net", 23);
    
    • host 为主机名,port 为端口号(23 为默认的 telnet 端口号)。
    • 如果无法确定主机的 IP 地址,则抛出 UnknownHostException 异常;如果在创建套接字时发生 IO 错误,则抛出 IOException 异常。
    • 需要注意的是,套接字在建立的时候,如果远程主机不可访问,这段代码会阻塞很长时间,直到底层操作系统的限制而抛出异常。因此,通常会在套接字建立后设置一个超时时间:
      socket.setSoTimeout(10000); // 单位为毫秒
      
  2. 获取输入流并读取数据

    InputStream is = socket.getInputStream();
    Scanner scanner = new Scanner(is, "gbk");
    
    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        System.out.println(line);
    }
    
    • 通过 java.net.Socket 类的 getInputStream() 方法获取输入流。

    • 使用 Scanner 类将输入流中的内容按行读取并打印出来。

    • 部分结果如下图所示(完整结果建议自己亲手实践一下):

      在这里插入图片描述

通过这种方式,我们可以模拟 telnet 命令的行为,直接与远程主机进行交互,体验网络通信的底层细节。

5 ServerSocket 实例:服务器端

以下是一个简单的 Java 服务器端套接字(ServerSocket)示例,模拟一个远程服务。

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class ServerSocketExample {
    public static void main(String[] args) {
        try (ServerSocket server = new ServerSocket(8888);
             Socket socket = server.accept();
             InputStream is = socket.getInputStream();
             OutputStream os = socket.getOutputStream();
             Scanner scanner = new Scanner(is)) {

            PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
            pw.println("你好啊,欢迎关注「沉默王二」公众号,回复关键字「2048」领取程序员进阶必读资料包");

            boolean done = false;
            while (!done && scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);

                if ("2048".equals(line)) {
                    done = true;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.1 代码解析:

  1. 建立服务器端的套接字

    ServerSocket server = new ServerSocket(8888);
    
    • 创建一个 ServerSocket 对象,并指定端口号 8888。端口号 0~1023 通常被系统预留,因此我们选择了一个大于 1023 的端口号。
  2. 等待客户端套接字的连接请求

    Socket socket = server.accept();
    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();
    
    • 调用 ServerSocket 对象的 accept() 方法,等待客户端的连接请求。一旦有客户端连接,accept() 方法会返回一个 Socket 对象,表示连接已建立。
    • 通过 Socket 对象获取输入流和输出流,分别用于读取客户端发送的数据和向客户端发送数据。
  3. 向客户端发送消息

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"), true);
    pw.println("你好啊,欢迎关注「沉默王二」 公众号,回复关键字「2048」 领取程序员进阶必读资料包");
    
    • 使用 PrintWriter 将消息发送到客户端。PrintWriter 的构造函数中使用了 OutputStreamWriter 来指定字符编码为 gbk,并且第二个参数 true 表示自动刷新缓冲区。
  4. 读取客户端发送的消息

    Scanner scanner = new Scanner(is);
    boolean done = false;
    while (!done && scanner.hasNextLine()) {
        String line = scanner.nextLine();
        System.out.println(line);
    
        if ("2048".equals(line)) {
            done = true;
        }
    }
    
    • 使用 Scanner 读取客户端发送的每一行数据。
    • 当客户端发送字符串 "2048" 时,服务器端会中断连接,客户端会显示“遗失对主机的连接”。

5.2 运行服务并测试:

  1. 运行服务器端代码

    • 在命令行或 IDE 中运行上述 ServerSocketExample 类。
  2. 使用 telnet 连接服务器

    • 打开一个新的命令行窗口,输入以下命令连接到服务器:
      telnet localhost 8888
      
    • 连接成功后,你会看到服务器发送的消息:
      你好啊,欢迎关注「沉默王二」 公众号,回复关键字「2048」 领取程序员进阶必读资料包
      
    • telnet 窗口中输入 2048,服务器端会中断连接,telnet 窗口会显示“遗失对主机的连接”。
      在这里插入图片描述

通过这种方式,我们可以模拟一个简单的远程服务,并通过 telnet 进行测试和交互。

6 为多个客户端服务

在这里插入图片描述
。以下是优化后的代码示例:

服务器端代码(MultiThreadedServer.java)

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

public class MultiThreadedServer {
    public static void main(String[] args) throws IOException {
        int port = 12345;
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Server is listening on port " + port);

        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("Client connected");
            new ClientHandler(socket).start();
        }
    }
}

class ClientHandler extends Thread {
    private Socket socket;

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

    public void run() {
        try {
            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));

            OutputStream output = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(output, true);

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println("Received: " + line);
                writer.println("Server: " + line);
            }

            socket.close();
        } catch (IOException e) {
            System.out.println("Client disconnected");
        }
    }
}

客户端代码(Client.java)

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        String hostname = "localhost";
        int port = 12345;

        Socket socket = new Socket(hostname, port);
        System.out.println("Connected to the server");

        InputStream input = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));

        OutputStream output = socket.getOutputStream();
        PrintWriter writer = new PrintWriter(output, true);

        writer.println("Hello, server!");
        String response = reader.readLine();
        System.out.println("Server response: " + response);

        socket.close();
    }
}

6.1 代码解析:

  1. 服务器端代码

    • MultiThreadedServer 类创建一个 ServerSocket 并监听指定端口(12345)。
    • 使用 while (true) 循环不断接受客户端的连接请求。
    • 每当有新的客户端连接时,创建一个新的 ClientHandler 线程来处理该连接。
  2. ClientHandler 类

    • ClientHandler 类继承自 Thread 类,用于处理单个客户端的连接。
    • run() 方法中,处理客户端的输入输出流,并向客户端发送消息。
    • 当客户端断开连接时,捕获 IOException 并打印“Client disconnected”。
  3. 客户端代码

    • Client 类创建一个 Socket 并连接到服务器。
    • 通过输入输出流与服务器进行通信。
    • 向服务器发送消息并接收服务器的响应。

6.2 运行服务并测试:

  1. 运行服务器端代码

    • 在命令行或 IDE 中运行 MultiThreadedServer 类。
  2. 运行多个客户端

    • 在多个命令行窗口中分别运行 Client 类。
    • 每个客户端都会连接到服务器,并向服务器发送消息。
    • 服务器会接收客户端的消息,并返回响应。
      在这里插入图片描述

通过这种方式,服务器端可以同时为多个客户端提供服务,每个客户端连接由一个独立的线程处理。这使得服务器能够高效地处理并发连接,满足一对多的需求。

7 UDP 通信:DatagramSocket 实例

UDP 是一种无连接的传输协议,适用于对可靠性要求不高但对速度要求较高的场景。以下是一个简单的 UDP 通信示例。

服务器端代码(UDPServer.java)

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    public static void main(String[] args) throws IOException {
        int port = 12345;
        DatagramSocket serverSocket = new DatagramSocket(port);
        System.out.println("Server is listening on port " + port);

        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        serverSocket.receive(packet);
        String message = new String(packet.getData(), 0, packet.getLength());
        System.out.println("Received: " + message);

        serverSocket.close();
    }
}

客户端代码(UDPClient.java)

import java.io.IOException;
import java.net.*;

public class UDPClient {
    public static void main(String[] args) throws IOException {
        String hostname = "localhost";
        int port = 12345;

        InetAddress address = InetAddress.getByName(hostname);
        DatagramSocket clientSocket = new DatagramSocket();

        String message = "Hello, server!";
        byte[] buffer = message.getBytes();

        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);
        clientSocket.send(packet);
        System.out.println("Message sent");

        clientSocket.close();
    }
}

7.1 代码解析:

  1. 服务器端代码

    • UDPServer 类创建一个 DatagramSocket 并监听指定端口(12345)。
    • 创建一个 DatagramPacket 对象,用于存储接收到的数据包。
    • 使用 serverSocket.receive(packet) 方法阻塞,直到收到一个数据包。
    • 收到数据包后,从数据包中提取消息并打印。
    • 最后关闭 DatagramSocket
  2. 客户端代码

    • UDPClient 类解析服务器的 IP 地址。
    • 创建一个 DatagramSocket 对象。
    • 将要发送的消息转换为字节数组,并创建一个 DatagramPacket 对象,指定目标地址和端口。
    • 使用 clientSocket.send(packet) 方法发送数据包。
    • 最后关闭 DatagramSocket

7.2 运行结果:

  1. 运行服务器端代码

    • 在命令行或 IDE 中运行 UDPServer 类。
    • 服务器启动并监听端口 12345,输出:
      Server is listening on port 12345
      
  2. 运行客户端代码

    • 在命令行或 IDE 中运行 UDPClient 类。
    • 客户端发送消息到服务器,输出:
      Message sent
      
  3. 服务器端接收消息

    • 服务器接收到客户端发送的消息,输出:
      Received: Hello, server!
      

通过这个示例,我们可以看到如何使用 DatagramSocket 类实现基于 UDP 协议的通信。UDP 是一种无连接的协议,因此不需要建立连接,发送和接收数据包的速度通常比 TCP 更快,但可靠性较低。

8 总结

掌握 Java Socket 编程对于理解网络通信机制至关重要。通过编写简单的客户端和服务器端程序,可以更好地理解网络通信的基本原理和实现方式。无论是基于 TCP 的 Socket 和 ServerSocket,还是基于 UDP 的 DatagramSocket,都是网络编程中不可或缺的工具。通过实践,可以进一步提升网络编程技能,为开发更复杂的网络应用打下坚实基础。

9 思维导图

在这里插入图片描述

10 参考链接

Java Socket:飞鸽传书的网络套接字


原文地址:https://blog.csdn.net/gaosw0521/article/details/143696182

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!