杰瑞科技汇

Java与C Socket通信如何实现跨语言数据交互?

  1. 核心概念回顾:确保理解 Socket 编程的基本原理。
  2. 通信模型:确定采用客户端/服务器模型。
  3. C 语言实现:作为服务器端。
  4. Java 实现:作为客户端。
  5. 关键点与注意事项:处理字节序、字符串编码、阻塞等问题。
  6. 完整代码示例:提供可直接运行的代码。

核心概念回顾

Socket(套接字)是网络通信的端点,两个程序通过 Socket 建立连接,然后在这个连接上发送和接收数据。

Java与C Socket通信如何实现跨语言数据交互?-图1
(图片来源网络,侵删)
  • IP 地址:网络中设备的唯一标识,如 0.0.1 (本机)。
  • 端口号:设备上应用程序的唯一标识,范围 0-65535,HTTP 服务常用 80 端口。
  • 协议:我们主要使用 TCP(面向连接、可靠)和 UDP(无连接、不可靠),这里我们以更可靠的 TCP 为例。
  • :数据以字节流的形式传输,发送方将数据写入输出流,接收方从输入流中读取。

通信模型

我们采用最经典的 客户端/服务器 模型。

  • 服务器

    1. 创建一个 Socket (socket())。
    2. 将 Socket 绑定到一个 IP 地址和端口 (bind())。
    3. 开始监听客户端连接 (listen())。
    4. 阻塞等待,接受客户端的连接请求 (accept()),一旦接受,就返回一个新的 Socket,专门用于与这个客户端通信。
    5. 通过新的 Socket 与客户端进行数据收发 (read()/write())。
    6. 关闭连接 (close())。
  • 客户端

    1. 创建一个 Socket (socket())。
    2. 连接到服务器的 IP 地址和端口 (connect())。
    3. 通过 Socket 与服务器进行数据收发 (send()/recv()read()/write())。
    4. 关闭连接 (close())。

注意:Java 和 C 的函数名和调用方式不同,但底层逻辑是完全一致的。

Java与C Socket通信如何实现跨语言数据交互?-图2
(图片来源网络,侵删)

C 语言实现(服务器端)

我们将创建一个 C 服务器,它监听 8888 端口,等待客户端连接,当客户端连接后,它会接收客户端发送的消息,打印出来,然后回复一个固定的消息。

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    // 1. 创建 socket 文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    // 2. 设置 socket 选项,允许地址重用
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET; // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 监听所有可用的网络接口
    address.sin_port = htons(PORT); // 将端口号从主机字节序转换为网络字节序
    // 3. 绑定 socket 到地址和端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    // 4. 开始监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);
    // 5. 接受客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    printf("Client connected: %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
    // 6. 从客户端读取数据
    int valread = read(new_socket, buffer, BUFFER_SIZE);
    printf("Client message: %s\n", buffer);
    // 7. 向客户端发送响应
    char *response = "Hello from C Server!";
    send(new_socket, response, strlen(response), 0);
    printf("Response sent.\n");
    // 8. 关闭 socket
    close(new_socket);
    close(server_fd);
    return 0;
}

编译与运行

# 编译
gcc server.c -o server
# 运行
./server

服务器会启动并等待连接。


Java 实现(客户端)

我们将创建一个 Java 客户端,它连接到 0.0.1:8888,向服务器发送一条消息,然后读取服务器的响应。

Client.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
    public static void main(String[] args) {
        String hostname = "127.0.0.1";
        int port = 8888;
        try (Socket socket = new Socket(hostname, port)) {
            System.out.println("Connected to the server.");
            // 获取输出流,用于发送数据到服务器
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            // 获取输入流,用于从服务器读取数据
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 要发送的消息
            String messageToServer = "Hello from Java Client!";
            System.out.println("Sending to server: " + messageToServer);
            out.println(messageToServer);
            // 读取服务器的响应
            String responseFromServer = in.readLine();
            System.out.println("Server response: " + responseFromServer);
        } catch (UnknownHostException e) {
            System.err.println("Don't know about host " + hostname);
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to " +
                    hostname);
            System.exit(1);
        }
    }
}

编译与运行

# 编译
javac Client.java
# 运行
java Client

关键点与注意事项

a. 字节序

网络数据传输必须使用统一的字节序,即 网络字节序,它采用 大端序 (Big-Endian)。

  • C 语言htonl() (Host to Network Long), htons() (Host to Network Short) 用于将主机字节序转换为网络字节序。ntohl(), ntohs() 用于反向转换,在 bind 时,我们使用了 htons(PORT)
  • Java 语言:Java 虚拟机内部默认使用大端序,Socket 的输入/输出流会自动处理字节序转换,你不需要像在 C 中那样手动调用转换函数。

b. 字符串编码

当传输字符串时,必须确保客户端和服务器使用相同的字符编码,否则会出现乱码。

  • C 语言:C 语言本身没有内置的、跨平台的字符串编码处理,通常使用 char 数组,并假设是 ASCII 或本地编码(如 Linux 下的 UTF-8),为了与 Java 兼容,最好在 C 中也使用 UTF-8。
  • Java 语言String 是基于 UTF-16 的。PrintWriterBufferedReader 默认使用平台的默认字符集,为了确保跨平台一致性,最好显式指定编码。
    // 修改后的 Java 客户端,显式使用 UTF-8
    PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));

c. 阻塞

Socket 的 I/O 操作(如 C 的 read/accept,Java 的 readLine)默认是阻塞的,这意味着程序会一直等待,直到有数据到达或连接建立,对于简单的示例来说这是可以的,但对于高性能应用,需要使用多线程或非阻塞 I/O/NIO。

d. 缓冲区大小

  • C 语言:你需要自己定义一个缓冲区(如 char buffer[BUFFER_SIZE]),如果接收的数据比缓冲区大,数据会被截断。
  • Java 语言BufferedReader 有自己的内部缓冲区,你不需要关心底层缓冲区的大小,它会自动处理。

完整代码示例(带编码处理)

为了确保通信的健壮性,我们对上面的代码进行优化,统一使用 UTF-8 编码。

C 服务器 (server_utf8.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h> // 用于错误码
#define PORT 8888
#define BUFFER_SIZE 1024
int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }
    printf("Client connected: %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
    // 读取客户端发送的UTF-8字符串
    ssize_t valread = read(new_socket, buffer, BUFFER_SIZE - 1); // -1 to ensure null-termination
    if (valread > 0) {
        buffer[valread] = '\0'; // 确保字符串正确终止
        printf("Client message (UTF-8): %s\n", buffer);
    }
    // 发送UTF-8响应
    char *response = "你好,来自C服务器的问候!"; // 使用中文测试UTF-8
    send(new_socket, response, strlen(response), 0);
    printf("Response sent: %s\n", response);
    close(new_socket);
    close(server_fd);
    return 0;
}

Java 客户端 (ClientUtf8.java)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientUtf8 {
    public static void main(String[] args) {
        String hostname = "127.0.0.1";
        int port = 8888;
        try (Socket socket = new Socket(hostname, port)) {
            System.out.println("Connected to the server.");
            // 显式使用UTF-8编码
            PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            // 发送UTF-8字符串
            String messageToServer = "你好,来自Java客户端的问候!";
            System.out.println("Sending to server: " + messageToServer);
            out.println(messageToServer);
            // 读取服务器的UTF-8响应
            String responseFromServer = in.readLine();
            System.out.println("Server response: " + responseFromServer);
        } catch (UnknownHostException e) {
            System.err.println("Don't know about host " + hostname);
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to " + hostname);
            e.printStackTrace();
            System.exit(1);
        }
    }
}

运行步骤

  1. 编译并运行 C 服务器:gcc server_utf8.c -o server_utf8 && ./server_utf8
  2. 编译并运行 Java 客户端:javac ClientUtf8.java && java ClientUtf8

你将看到双方都能正确显示中文字符,证明通信成功且编码处理正确。

分享:
扫描分享到社交APP
上一篇
下一篇