如何从Java调用C/C++ TCP/IP服务器并发送指定格式消息?
构造符合要求的TCP/IP服务器注册消息(C语言实现)
我来帮你一步步搞定这个注册消息的构造和发送问题。首先先把给定的结构体定义理清楚,然后再写实际的发送代码,还要注意几个容易踩坑的细节。
一、明确结构体定义(按要求的字节数)
首先把两个结构体完整写出来,注意注释里的字节数要求,这是服务器解析的关键:
// 头部结构体,固定18字节 typedef struct { uint32 ui32Synch1; /* 消息同步标识1,固定值 0x55555555 */ uint32 ui32Synch2; /* 消息同步标识2,固定值 0xaaaaaaaa */ uint16 ui16Version; /* 头部格式版本,固定值 1 */ uint32 ui32Cmd; /* 命令码——这里必须用服务器约定的注册命令,比如0x00000001这类,得确认清楚 */ // 注意:你提供的代码里头部后面有省略号,一定要补全所有字段! // 否则结构体总大小会达不到18字节,服务器解析直接错位 // 比如如果还有预留字段或长度字段,像uint16 ui16Reserved; uint32 ui32Length; 之类的,必须加上 } SPMSifHdr; // 注册消息结构体,固定62字节(18+20+20+4=62) struct SPMSifRegisterMsg { SPMSifHdr hdr1; // 18字节的头部 char szLisence[20]; // 20字节的许可证字符串 char szApplName[20]; // 20字节的应用名称字符串 int nRet; // 4字节的返回值——发送给服务器时一般设为0,服务器回复时会填充结果 };
二、构造并发送消息的示例代码
下面是一个完整的C语言示例,包含socket创建、连接服务器、构造消息、发送的全过程:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdint.h> // 先补全头部结构体(假设省略的字段是一个uint16预留字段,确保总大小18字节) typedef struct { uint32_t ui32Synch1; // 固定0x55555555 uint32_t ui32Synch2; // 固定0xaaaaaaaa uint16_t ui16Version; // 固定1 uint32_t ui32Cmd; // 注册命令码,替换为服务器实际值 uint16_t ui16Reserved;// 预留字段,设为0(用来凑够18字节:4+4+2+4+4=18) } SPMSifHdr; struct SPMSifRegisterMsg { SPMSifHdr hdr1; char szLisence[20]; char szApplName[20]; int nRet; }; int main() { int sockfd; struct sockaddr_in server_addr; struct SPMSifRegisterMsg reg_msg; // 1. 创建TCP套接字 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("创建socket失败"); exit(EXIT_FAILURE); } // 2. 设置服务器地址信息 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); // 替换为服务器实际监听端口 if (inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr) <= 0) { // 替换为服务器IP perror("服务器地址无效"); close(sockfd); exit(EXIT_FAILURE); } // 3. 连接服务器 if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("连接服务器失败"); close(sockfd); exit(EXIT_FAILURE); } // 4. 构造注册消息 memset(®_msg, 0, sizeof(reg_msg)); // 先清空整个结构体,避免脏数据 // 设置头部字段——重点:多字节字段必须转成网络字节序(大端)! reg_msg.hdr1.ui32Synch1 = htonl(0x55555555); reg_msg.hdr1.ui32Synch2 = htonl(0xaaaaaaaa); reg_msg.hdr1.ui16Version = htons(1); reg_msg.hdr1.ui32Cmd = htonl(0x00000001); // 这里替换为服务器实际的注册命令码 reg_msg.hdr1.ui16Reserved = htons(0); // 设置许可证和应用名称——注意要填充到20字节,不足的话自动补\0 strncpy(reg_msg.szLisence, "MY_VALID_LICENSE", sizeof(reg_msg.szLisence)-1); strncpy(reg_msg.szApplName, "MY_TEST_APP", sizeof(reg_msg.szApplName)-1); // nRet发送时设为0,同样转网络字节序 reg_msg.nRet = htonl(0); // 5. 发送消息 ssize_t bytes_sent = send(sockfd, ®_msg, sizeof(reg_msg), 0); if (bytes_sent != sizeof(reg_msg)) { perror("发送失败或仅发送了部分数据"); close(sockfd); exit(EXIT_FAILURE); } printf("注册消息发送成功!\n"); // 这里可以添加接收服务器响应的代码,比如: // struct SPMSifRegisterMsg resp_msg; // ssize_t bytes_recv = recv(sockfd, &resp_msg, sizeof(resp_msg), 0); // 然后解析resp_msg.nRet等字段 close(sockfd); return 0; }
三、必须注意的几个关键细节
- 字节序问题:这是网络编程最容易踩的坑!所有多字节类型(uint32、uint16、int)都要转换为网络字节序(大端),用
htonl()(32位)和htons()(16位)函数。如果客户端是小端系统(比如x86架构),不转换的话服务器解析出来的数值会完全错误。 - 结构体大小验证:一定要确保
SPMSifHdr是18字节,SPMSifRegisterMsg是62字节。可以用printf("Hdr size: %zu\n", sizeof(SPMSifHdr));来打印验证,大小不对的话必须补全头部的省略字段,否则服务器解析会错位。 - 字符串处理:
szLisence和szApplName是固定20字节,用strncpy的时候要留一个字节给\0,如果服务器要求字符串不带\0而是用空格填充,那就要先memset(reg_msg.szLisence, ' ', sizeof(reg_msg.szLisence))再拷贝字符串。 - 命令码确认:
ui32Cmd字段必须是服务器定义的注册命令码,这个一定要和服务器的开发文档或者维护人员确认,填错的话服务器根本识别不了你的消息。
内容的提问来源于stack exchange,提问作者kabugima2
相关产品推荐
相关产品推荐

