// C语言写一个doip客户端,您可能需要以下几个步骤
// 创建一个套接字,指定TCP或UDP协议,并设置目标服务器的IP地址和端口号。
// 使用connect函数主动向服务器发起连接请求,与服务器的accept函数实现三次握手建立连接。
// 连接成功后,使用send和recv函数发送和接收doip报文,根据doip报文的格式和类型进行相应的处理。
// 使用close函数关闭套接字,结束通信。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define IP "192.168.102.175" //服务器的IP地址
#define PORT 7000 //服务器的端口号
#define UDP_DISCOVERY 13400 //DoIP UDP发现端口号
#define TCP_DATA 13400 //DoIP TCP数据端口号
//定义DoIP报文结构体
struct doip_message {
unsigned char protocol_version; //协议版本号
unsigned char inverse_version; //协议版本号取反
unsigned short payload_type; //有效载荷类型
unsigned long payload_length; //有效载荷长度
unsigned char *payload; //有效载荷数据
};
//打印错误信息并退出程序
void print_err(char *str, int line, int err_no) {
printf("%d, %s :%s\n",line,str,strerror(err_no));
exit(-1);
}
//创建一个UDP套接字并返回
int create_udp_socket() {
int skfd = -1;
skfd = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字
if ( -1 == skfd) {
print_err("socket failed",__LINE__,errno);
}
return skfd;
}
//创建一个TCP套接字并返回
int create_tcp_socket() {
int skfd = -1;
skfd = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
if ( -1 == skfd) {
print_err("socket failed",__LINE__,errno);
}
return skfd;
}
//向服务器发送UDP报文,并接收响应报文
void send_udp_message(int skfd, struct doip_message *msg) {
int ret = -1;
struct sockaddr_in addr; //服务器的地址结构体
addr.sin_family = AF_INET; //设置协议族为IPv4
addr.sin_port = htons(UDP_DISCOVERY); //设置端口号为DoIP UDP发现端口号
addr.sin_addr.s_addr = inet_addr(IP); //设置IP地址为服务器的IP地址
//计算DoIP报文的总长度(包括报头和有效载荷)
int msg_len = 8 + msg->payload_length;
//将DoIP报文结构体转换为字节数组,方便发送
unsigned char buf[msg_len];
buf[0] = msg->protocol_version;
buf[1] = msg->inverse_version;
buf[2] = (msg->payload_type >> 8) & 0xFF;
buf[3] = msg->payload_type & 0xFF;
buf[4] = (msg->payload_length >> 24) & 0xFF;
buf[5] = (msg->payload_length >> 16) & 0xFF;
buf[6] = (msg->payload_length >> 8) & 0xFF;
buf[7] = msg->payload_length & 0xFF;
memcpy(buf + 8, msg->payload, msg->payload_length);
//发送DoIP报文到服务器
ret = sendto(skfd, buf, msg_len, 0, (struct sockaddr*)&addr, sizeof(addr));
if (-1 == ret) {
print_err("sendto failed", __LINE__, errno);
}
//接收服务器的响应报文
bzero(&buf, sizeof(buf));
ret = recvfrom(skfd, buf, sizeof(buf), 0, NULL, NULL);
if (-1 == ret) {
print_err("recvfrom failed", __LINE__, errno);
}
else if (ret > 0) {
//将字节数组转换为DoIP报文结构体,方便处理
struct doip_message res;
res.protocol_version = buf[0];
res.inverse_version = buf[1];
res.payload_type = (buf[2] << 8) | buf[3];
res.payload_length = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
res.payload = buf + 8;
//根据响应报文的类型和内容进行相应的处理
switch (res.payload_type) {
case 0x0001: //DoIP报头否定应答
printf("DoIP header negative acknowledge: %02X\n", res.payload[0]);
break;
case 0x0002: //车辆声明及标识
printf("Vehicle identification and announcement: %s\n", res.payload);
break;
case 0x0004: //路由激活响应
printf("Routing activation response: %02X\n", res.payload[4]);
break;
case 0x0006: //在线检查响应
printf("Alive check response\n");
break;
default:
printf("Unknown payload type: %04X\n", res.payload_type);
break;
}
}
}
//向服务器发送TCP报文,并接收响应报文
void send_tcp_message(int skfd, struct doip_message *msg) {
int ret = -1;
//计算DoIP报文的总长度(包括报头和有效载荷)
int msg_len = 8 + msg->payload_length;
//将DoIP报文结构体转换为字节数组,方便发送
unsigned char buf[msg_len];
buf[0] = msg->protocol_version;
buf[1] = msg->inverse_version;
buf[2] = (msg->payload_type >> 8) & 0xFF;
buf[3] = msg->payload_type & 0xFF;
buf[4] = (msg->payload_length >> 24) & 0xFF;
buf[5] = (msg->payload_length >> 16) & 0xFF;
buf[6] = (msg->payload_length >> 8) & 0xFF;
buf[7] = msg->payload_length & 0xFF;
memcpy(buf + 8, msg->payload, msg->payload_length);
//发送DoIP报文到服务器
ret = send(skfd, buf, msg_len, 0);
if (-1 == ret) {
print_err("send failed", __LINE__, errno);
}
//接收服务器的响应报文
bzero(&buf, sizeof(buf));
ret = recv(skfd, buf, sizeof(buf), 0);
if (-1 == ret) {
print_err("recv failed", __LINE__, errno);
}
else if (ret > 0) {
//将字节数组转换为DoIP报文结构体,方便处理
struct doip_message res;
res.protocol_version = buf[0];
res.inverse_version = buf[1];
res.payload_type = (buf[2] << 8) | buf[3];
res.payload_length = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7];
res.payload = buf + 8;
//根据响应报文的类型和内容进行相应的处理
switch (res.payload_type) {
case 0x8001: //诊断报文响应
printf("Diagnostic message response: %s\n", res.payload);
break;
default:
printf("Unknown payload type: %04X\n", res.payload_type);
break;
}
}
}
//主函数,创建套接字,连接服务器,发送和接收报文
int main() {
int skfd_udp = -1; //UDP套接字
int skfd_tcp = -1; //TCP套接字
int ret = -1;
//创建UDP套接字
skfd_udp = create_udp_socket();
//创建TCP套接字
skfd_tcp = create_tcp_socket();
//设置服务器的地址结构体
struct sockaddr_in addr;
addr.sin_family = AF_INET; //设置协议族为IPv4
addr.sin_port = htons(TCP_DATA); //设置端口号为DoIP TCP数据端口号
addr.sin_addr.s_addr = inet_addr(IP); //设置IP地址为服务器的IP地址
//使用connect函数主动向服务器发起连接请求,与服务器的accept函数实现三次握手建立连接
ret = connect(skfd_tcp, (struct sockaddr*)&addr, sizeof(addr));
if (-1 == ret) {
print_err("connect failed", __LINE__, errno);
}
//构造一个车辆信息请求报文
struct doip_message msg1;
msg1.protocol_version = 0x02; //协议版本号为0x02
msg1.inverse_version = 0xFD; //协议版本号取反为0xFD
msg1.payload_type = 0x0001; //有效载荷类型为0x0001,表示车辆信息请求
msg1.payload_length = 0x00000000; //有效载荷长度为0,表示没有有效载荷数据
msg1.payload = NULL; //有效载荷数据为空
//向服务器发送车辆信息请求报文,并接收响应报文
send_udp_message(skfd_udp, &msg1);
//构造一个路由激活请求报文
struct doip_message msg2;
msg2.protocol_version = 0x02; //协议版本号为0x02
msg2.inverse_version = 0xFD; //协议版本号取反为0xFD
msg2.payload_type = 0x0005; //有效载荷类型为0x0005,表示路由激活请求
msg2.payload_length = 0x00000007; //有效载荷长度为7,表示有7个字节的有效载荷数据
unsigned char payload2[7] = {0xE0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x01}; //有效载荷数据,包括源地址、目标地址和激活类型
msg2.payload = payload2;
//向服务器发送路由激活请求报文,并接收响应报文
send_udp_message(skfd_udp, &msg2);
//构造一个诊断报文请求报文
struct doip_message msg3;
msg3.protocol_version = 0x02; //协议版本号为0x02
msg3.inverse_version = 0xFD; //协议版本号取反为0xFD
msg3.payload_type = 0x8001; //有效载荷类型为0x8001,表示诊断报文请求
msg3.payload_length = 0x00000006; //有效载荷长度为6,表示有6个字节的有效载荷数据
unsigned char payload3[6] = {0xE0, 0x11, 0x22, 0x33, 0xAA, 0xBB}; //有效载荷数据,包括源地址、目标地址和诊断数据
msg3.payload = payload3;
//向服务器发送诊断报文请求报文,并接收响应报文
send_tcp_message(skfd_tcp, &msg3);
//关闭套接字,结束通信
close(skfd_udp);
close(skfd_tcp);
return 0;
}