杰瑞科技汇

java socket 连接服务器

  1. Socket 客户端:主动连接服务器,发送和接收数据。
  2. Socket 服务端:监听端口,等待客户端连接,并处理客户端的请求。

第一部分:Socket 客户端

客户端是主动发起连接的一方,它的基本流程是:

java socket 连接服务器-图1
(图片来源网络,侵删)
  1. 创建一个 Socket 对象,指定服务器的 IP 地址和端口号。
  2. 如果连接成功,通过 Socket 对象获取输出流(OutputStream),向服务器发送数据。
  3. 通过 Socket 对象获取输入流(InputStream),接收服务器返回的数据。
  4. 通信结束后,关闭所有流和 Socket 连接。

完整的客户端代码示例

这是一个简单的客户端,它会连接到本地服务端(localhost),发送一条消息,并打印服务器的回复。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketClient {
    public static void main(String[] args) {
        // 服务器地址和端口
        String host = "localhost"; // 或者服务器的IP地址,如 "192.168.1.100"
        int port = 8080;
        // try-with-resources 语句,可以自动关闭资源
        try (
            // 1. 创建一个Socket对象并尝试连接到服务器
            Socket socket = new Socket(host, port);
            // 2. 获取输出流,用于向服务器发送数据
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(outputStream, true); // true表示自动刷新缓冲区
            // 3. 获取输入流,用于接收服务器返回的数据
            InputStream inputStream = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))
        ) {
            System.out.println("成功连接到服务器 " + host + ":" + port);
            // 4. 向服务器发送数据
            String messageToSend = "你好,服务器!我是客户端。";
            writer.println(messageToSend);
            System.out.println("已发送消息: " + messageToSend);
            // 5. 接收服务器的回复
            String serverResponse = reader.readLine();
            System.out.println("收到服务器回复: " + serverResponse);
        } catch (UnknownHostException e) {
            System.err.println("找不到主机: " + host);
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("I/O错误: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("客户端连接已关闭。");
    }
}

代码解释

  1. new Socket(host, port):这是创建客户端连接的核心,它会尝试与指定 hostport 的服务器建立 TCP 连接,如果连接失败(例如服务器未启动或地址错误),会抛出 IOException
  2. OutputStreamPrintWriterSocket.getOutputStream() 获取一个字节输出流,我们通常将其包装成 PrintWriter,因为它提供了方便的 println()print() 等方法,可以直接写入字符串和数字,并处理字符编码。
  3. InputStreamBufferedReaderSocket.getInputStream() 获取一个字节输入流,我们将其包装成 InputStreamReader(将字节流转换为字符流),再包装成 BufferedReader,以便使用 readLine() 方法方便地按行读取服务器发来的数据。
  4. try-with-resources:这是一个非常推荐的做法,它会自动在 try 块结束时关闭 Socket 以及所有从它创建的流(writer, reader),避免了资源泄漏,代码也更简洁。
  5. 网络流与阻塞
    • writer.println(...) 会将数据发送到服务器的 TCP 缓冲区。
    • reader.readLine() 是一个阻塞方法,它会一直等待,直到从输入流中读取到一行完整的数据(以 \n\r\n 或者流被关闭,如果服务器没有发送数据,客户端就会在这里“卡住”。

第二部分:Socket 服务端

服务端是被动等待连接的一方,它的基本流程是:

  1. 创建一个 ServerSocket 对象,并绑定一个端口号,开始监听该端口。
  2. 调用 accept() 方法,这是一个阻塞方法,它会一直等待,直到有客户端连接上来。
  3. accept() 返回时,它会返回一个新的 Socket 对象,这个 Socket 代表了与那个已连接客户端的通信通道。
  4. 通过这个新的 Socket 对象,像客户端一样获取输入输出流,与单个客户端进行通信。
  5. 通信结束后,关闭与该客户端的 Socket 连接。ServerSocket 通常会一直运行,可以接受更多的客户端连接。

完整的服务端代码示例

这个服务端会监听 8080 端口,等待客户端连接,处理一个客户端的请求后,会继续等待下一个客户端。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
    public static void main(String[] args) {
        int port = 8080; // 服务端监听的端口
        try (
            // 1. 创建一个ServerSocket,并绑定到指定的端口
            ServerSocket serverSocket = new ServerSocket(port);
        ) {
            System.out.println("服务器已启动,正在监听端口 " + port + "...");
            // 2. 调用accept()方法,等待客户端连接
            // 这是一个阻塞方法,会一直阻塞直到有客户端连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("客户端 " + clientSocket.getInetAddress().getHostAddress() + " 已连接。");
            // try-with-resources 只管理 clientSocket 的流,serverSocket 保持开启
            try (
                // 3. 获取客户端的输入流,用于接收客户端发来的数据
                InputStream inputStream = clientSocket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                // 4. 获取客户端的输出流,用于向客户端发送数据
                OutputStream outputStream = clientSocket.getOutputStream();
                PrintWriter writer = new PrintWriter(outputStream, true);
            ) {
                // 5. 接收客户端的消息
                String clientMessage = reader.readLine();
                System.out.println("收到客户端消息: " + clientMessage);
                // 6. 处理消息并发送回复
                String responseMessage = "你好,客户端!我已经收到你的消息: [" + clientMessage + "]";
                writer.println(responseMessage);
                System.out.println("已向客户端发送回复: " + responseMessage);
            } // 自动关闭 clientSocket 的流
            finally {
                // 7. 关闭与该客户端的连接
                clientSocket.close();
                System.out.println("与客户端 " + clientSocket.getInetAddress().getHostAddress() + " 的连接已关闭。");
            }
        } catch (IOException e) {
            System.err.println("服务器发生I/O错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

代码解释

  1. new ServerSocket(port):创建一个服务端套接字,并绑定到指定的端口,一个端口在同一时间只能被一个程序绑定。
  2. serverSocket.accept():这是服务端的核心,它会阻塞程序的执行,直到一个新的网络连接被建立,一旦有客户端连接,accept() 方法就会返回一个 Socket 对象,后续的通信都通过这个 Socket 对象进行。
  3. 处理多个客户端:上面的示例代码一次只能处理一个客户端,当第一个客户端连接、通信、断开后,accept() 才会返回,等待下一个客户端,要同时处理多个客户端,你需要使用多线程线程池,基本思路是:在 accept() 返回后,为这个新的 Socket 创建一个新线程,让这个线程去处理通信逻辑,而主线程则立即回到 accept(),继续等待下一个客户端。

如何运行和测试

  1. 先运行服务端

    java socket 连接服务器-图2
    (图片来源网络,侵删)
    java SocketServer

    你会看到控制台输出:

    服务器已启动,正在监听端口 8080...
  2. 再运行客户端

    java SocketClient

    客户端控制台会输出:

    成功连接到服务器 localhost:8080
    已发送消息: 你好,服务器!我是客户端。
    收到服务器回复: 你好,客户端!我已经收到你的消息: [你好,服务器!我是客户端,]
    客户端连接已关闭。
  3. 观察服务端控制台

    java socket 连接服务器-图3
    (图片来源网络,侵删)
    服务器已启动,正在监听端口 8080...
    客户端 127.0.0.1 已连接。
    收到客户端消息: 你好,服务器!我是客户端。
    已向客户端发送回复: 你好,客户端!我已经收到你的消息: [你好,服务器!我是客户端,]
    与客户端 127.0.0.1 的连接已关闭。

总结与注意事项

  • 阻塞模型:基本的 Socket 编程是阻塞的。accept(), read(), readLine() 等方法在没有数据或连接时会一直等待,对于高并发的场景,需要升级到 NIO (New I/O) 模型。
  • 资源管理:务必关闭所有打开的流和 Socket,使用 try-with-resources 是最佳实践。
  • 异常处理:网络编程充满了不确定性,如连接中断、数据丢失等,必须妥善处理 IOException 及其子类异常。
  • 字符编码:当传输文本数据时,InputStreamReaderOutputStreamWriter 可以指定字符编码(如 UTF-8),以确保在不同系统上不会出现乱码。
    // 在读写时指定编码
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true);
  • 协议设计:简单的 readLine() 只能处理以换行符结尾的文本,在实际应用中,你可能需要设计自己的应用层协议,
    • 固定长度:每条消息固定为 N 个字节。
    • 特殊分隔符:用特殊字符(如 \0)作为消息的结束标志。
    • 长度前缀:在每个消息的头部加上消息体的长度,接收方先读取长度,再读取对应长度的数据,这是最常用和最健壮的方式。
分享:
扫描分享到社交APP
上一篇
下一篇