嵌入式私有通信协议源码,用于两个设备间或者双核之间通信
作者 | 将狼才鲸 |
---|---|
创建时间 | 2023-03-23 |
-
Gitee源码仓库地址:嵌入式私有通信协议
-
CSDN文章阅读地址:嵌入式私有通信协议源码,用于两个设备间或者双核之间通信
-
使用场景限定:
- 当前实现了主从设备间或者双核间的通信,暂未实现多设备间的路由、转发、级联、透传;
- 当前使用了非阻塞死循环查询数据,适用于单片机裸机;要想在操作系统中有较低的CPU占用率,需要将死循环改成延时,非阻塞改成阻塞;
-
技术依赖:模块中用到了链表、队列。
-
已含有的功能:消息ACK响应、单发单收、多发多收、重发、超时处理、接收缓存队列、发送缓存队列、支持操作权限控制。
一、效果演示
- 运行效果如下:
第一个终端
jim@ubuntu:/mnt/hgfs/git_win10/many-repositories/20_私有通信协议$ ./server
enter scp(self communication protocol) server demo
waitting for client connect...
connect pass
recv: aabbccdd#ǘihello world!
server got packet
server send respond packet
jim@ubuntu:/mnt/hgfs/git_win10/many-repositories/20_私有通信协议$
第二个终端
jim@ubuntu:/mnt/hgfs/git_win10/many-repositories/20_私有通信协议$ ./client
client waitting for server responds...
recv: aabbccdd#ǘi#ǘi
SCP: Packet received:
CID = 0x23c79869
LEN = 24
SID = 0x00000001
TYPE = 0x02
SEC = 0x03
PID = 0x0001
SUB = 0x00000005
ACK:
CID = 0x23c79869
LEN = 14
SID = 0x00000001
TYPE = 0x02
SEC = 0x03
PID = 0x1001
SUB = 0x00000000
client receive responds finish
jim@ubuntu:/mnt/hgfs/git_win10/many-repositories/20_私有通信协议$
二、源码结构
jim@DESKTOP-SVP3BEM MINGW64 /d/1_git/many-repositories/20_私有通信协议 (develop)
$ tree
.
|-- Makefile
|-- dev_socket.c
|-- dev_socket.h
|-- list.h
|-- queue.c
|-- queue.h
|-- queue_buffer.c
|-- queue_buffer.h
|-- queue_buffer_cfg.c
|-- queue_buffer_cfg.h
|-- readme.md
|-- scp_client.c
|-- scp_client.h
|-- scp.c
|-- scp.h
|-- scp_client_debug.c
|-- scp_client_debug.h
|-- scp_dev_socket.c
|-- scp_dev_socket.h
|-- scp_devs.c
|-- scp_devs.h
|-- scp_protocol.c
|-- scp_protocol.h
|-- scp_server.c
|-- scp_server.h
|-- test_client.c
|-- test_server.c
|-- util_comm.h
|-- util_config.h
|-- util_errno.h
`-- util_print.h
0 directories, 31 files
jim@DESKTOP-SVP3BEM MINGW64 /d/1_git/many-repositories/20_私有通信协议 (develop)
三、源码
-
此处只展示部分源码,全部源码见上方Gitee仓库
-
test_client.c
/******************************************************************************
* \brief 两个进程间使用私有通信协议进行通信,客户端测试Demo
* \details 模拟两个嵌入式设备间的主从通信;通信的收和发都有队列缓存
* \note 等待响应时会持续轮询,非阻塞,未加延时;在PC上会CPU占用高
* \note File format: UTF-8,中文编码:UTF-8
* \author 将狼才鲸
* \date 2023-03-12
******************************************************************************/
/*********************************** 头文件 ***********************************/
#include "scp_client_debug.h"
#include "scp_client.h"
/*********************************** 主函数 ***********************************/
int main()
{
scp_handle scp;
/* 初始化系统缓存 */
qbuf_groups_cfg();
/* 在指定链路上建立一个连接 */
scp = scp_init(0, SCP_DEVTYPE_SOCKET);
scpclt_debug_settarget(scp); /* 将传输连接和要使用的应用层协议组进行绑定,保持当次传输的连接 */
/* 使用指定应用层协议向服务器端发送消息 */
scpclt_debug_puts(" hello world!\n");
scpclt_debug_printf(" is from %s\n", "```CLIENT```");
/* 销毁之前的连接 */
scp_exit(scp);
return 0;
}
/*********************************** 文件尾 ***********************************/
- test_server.c
/******************************************************************************
* \brief 两个进程间使用私有通信协议进行通信,服务器端测试Demo
* \details 模拟两个嵌入式设备间的主从通信;通信的收和发都有队列缓存
* \note 轮询,非阻塞,未加延时;在PC上会CPU占用高
* \remarks Linux进程间通信:管道pipe/mkfifo(文件读写)、共享内存shmget、
* 消息队列msgget、信号量semget……
* Windows API(Win32 API)进程间通信:管道Pipe、共享内存FileMapping、
* 邮槽MailSlot、剪贴板Clipboard、套接字Sockets……
* \note File format: UTF-8,中文编码:UTF-8
* \author 将狼才鲸
* \date 2023-03-04
******************************************************************************/
/*********************************** 头文件 ***********************************/
#include "scp_server.h"
#ifdef linux
/* 当前使用的是Linux的进程间通信机制 */
#elif WIN32
# error "not support windows currently"
#endif
/*
* \brief 使用PC的一些机制模拟板子的物理通信链路
* \details 使用共享内存模拟共享寄存器寄存器的读写;
* 使用命名管道、套接字Socket、消息队列模拟串口收发;
*/
/*********************************** 主函数 ***********************************/
int main()
{
scp_handle hscp;
//TODO: 可自行将scp这个名字全部替换成pcp(private communication protocol)或其它缩写
pr_dbg("enter scp(self communication protocol) server demo\n");
/* 初始化系统缓存 */
qbuf_groups_cfg();
/* 创建一个通信连接 */
hscp = scp_init(0, SCP_DEVTYPE_SOCKET);
/* 服务器端额外的初始化,当前内容为空 */
scpsrv_init();
do {
/* 服务器端轮询响应 */
scpsrv_run(hscp);
} while (1);
/* 退出通信连接 */
scp_exit(hscp);
return 0;
}
/*********************************** 文件尾 ***********************************/
- scp_client.c
/******************************************************************************
* \brief 私有通信协议客户端函数接口
* \note File format: UTF-8,中文编码:UTF-8
* \author 将狼才鲸
* \date 2023-03-24
******************************************************************************/
/*********************************** 头文件 ***********************************/
#include "scp_client.h"
/********************************** 私有函数 **********************************/
/**
* \brief 一个已有的数据包中如果没有连接ID,则赋值一个新的
* \details 这个包的连接ID和通信连接ID有关
*/
static uint32_t scp_newcid(scp_t *pscp)
{
/* 通信链路的连接ID加1,包的连接ID使用通信连接ID + 随机值 */
uint32_t id = (((++pscp->CID) & 0xffff) << 16) | (rand() & 0xffff);
return htonl(id);
}
/**
* \brief 将要发送的单个消息(或多发多收的消息系列)添加到写队列
* \param pscp: 通信连接
* \param seg: 单次要发送的通信消息,或一系列消息,该消息已事先分配过空间
* \return 已加入队列的消息数量;单条消息的话正常处理后会为1
*/
static int scp_tx_addto_writelist(scp_t *pscp, segment_t *seg)
{
int sec, count = 0;
seg_pkt_head_t *pkt;
irq_flag lock;
irq_disable(lock);
while (seg) {
/* 如果是一系列消息,则循环将这个消息系列都加入到写队列中,否则只执行一次 */
if (seg->TT == TT_ACKONLY || seg->TT == TT_RESPONDS) {
/* 如果包类型正确 */
pkt = (seg_pkt_head_t *)scpseg_data(seg); /* 获取实际包数据 */
if (!pkt->CID) {
/* 赋值新的连接ID,每个发送包的ID都不一样,发送包和响应包ID相同 */
pkt->CID = scp_newcid(pscp);
}
/* 段ID归零,实际发送时再赋值;发送包从1开始递增,相应包和发送包相同 */
pkt->SID = 0;
seg->LEN = SEG_PKT_HEAD_LEN + ntohl(pkt->LEN); /* 段中数据长度 */
/* 从权限判断用户组 */
if (pkt->SEC & SEC_UG)
sec = SECLVL_URGENCY;
else if (pkt->SEC & (SEC_SW | SEC_SR))
sec = SECLVL_ADMIN;
else if (pkt->SEC & (SEC_TW | SEC_TR))
sec = SECLVL_OPERATOR;
else {
pkt->SEC = (SEC_PW | SEC_PR);
sec = SECLVL_USER;
}
/* 将要发送的消息加入到不同的用户组的队列 */
init_list_head(&seg->node);
list_add_tail(&seg->node, &(pscp->wpkt_list[sec]));
count++;
} else {
/* 如果传输包类型错误,则要重新组织发送包;当前这种情况下未做额外处理 */
seg->responds = ACK_RACK;
}
seg = seg->next; /* 处理该系列中下一条消息(很少需要使用该功能) */
}
irq_enable(lock);
return count;
}
/**
* \brief 从写数据包队列中取出一个要写的数据包,随后会发送出去
*/
static segment_t *tx_readfrom_list(scp_t *pscp)
{
int i, found;
segment_t *seg = NULL, *n;
irq_flag lock;
if (!scp_valid(pscp)) /* scp句柄有效 */
return NULL;
irq_disable(lock);
found = 0;
for (i = 0; !found && (i < SECLVL_COUNT); i++) {
/* 循环整个用户组(通常只会使用操作员组) */
list_head_t *head = &(pscp->wpkt_list[i]); /* 写数据包队列 */
if (!list_empty_careful(head)) {
/* 判断队列非空 */
list_for_each_entry_safe(seg, n, head, node) {
/* 从队列中循环读出数据 */
if ((seg->responds == ACK_NULL) && (!(seg->PHS & SEGMENT_TRANSFERING))
&& (seg->PHS & (SEGMENT_TRANSMIT1 | SEGMENT_TRANSMIT2 | SEGMENT_TRANSMIT3))
!= (SEGMENT_TRANSMIT1 | SEGMENT_TRANSMIT2 | SEGMENT_TRANSMIT3)) {
/* 如果该数据包还没有响应,而且该包未发送,并且该包不处于已发送状态 */
found = 1;
break;
}
}
}
}
irq_enable(lock);
return found ? seg : NULL;
}
/**
* \brief 判断是不是发送包序列中其它请求包对应的响应包
* \param pscp: 当前发送的协议句柄
* \param pkt: 当前已接收的响应包
* \return 是当前发送包的响应包则返回假,是发送包序列中其它包的响应包则返回真
*/
static bool scp_pkt_old(scp_t *pscp, seg_pkt_head_t *pkt)
{
int i;
bool cidfound, sidfound;
uint16_t PID = ntohs(pkt->PID); /* 协议ID */
uint32_t CID = ntohl(pkt->CID); /* 连接ID */
uint32_t SID = ntohl(pkt->SID); /* 段ID */
if (SCP_QPID(PID) == SCP_PID_ACK) {
/* 如果不是当前请求包的响应包 */
return false;
}
/* 是其它发送包的响应包 */
cidfound = false;
for (i = 0; !cidfound && i < SCP_MAX_VCID_VALID; i++) {
if (CID == pscp->VCID[i]) {
/* 每个包的连接ID都不同,一个发送包和其对应的响应包ID相同 */
cidfound = true; /* 如果接收包的连接id在已发送包id数组中 */
break;
}
}
sidfound = false;
for (i = 0; !sidfound && i < SCP_MAX_VSID_VALID; i++) {
if (SID == pscp->VSID[i]) {
/* 每个包的段ID都不同,一个发送包和其对应的响应包ID相同 */
sidfound = true; /* 如果接收包的段id在已发送包id数组中 */
break;
}
}
if (cidfound && sidfound)
return true;
if (!cidfound) {
/* 如果所有已发送包都未匹配到该响应包,则将该响应包ID放到队列中最前面;
这种情况下算收到的异常数据吧 */
for (i = SCP_MAX_VCID_VALID - 1; i > 0; i--)
pscp->VCID[i] = pscp->VCID[i - 1];
pscp->VCID[0] = CID;
}
if (!sidfound) {
for (i = SCP_MAX_VSID_VALID - 1; i > 0; i--)
pscp->VSID[i] = pscp->VSID[i - 1];
pscp->VSID[0] = SID;
}
return false;
}
/**
* \brief 打印包头信息,调试用
*/
static void scp_dump_packet_header(const char *msg, seg_pkt_head_t *pkt)
{
uint32_t LEN = ntohl(pkt->LEN);
pr_info("\n\t%s:\n"
"\t\t CID = 0x%08x \n"
"\t\t LEN = %d \n"
"\t\t SID = 0x%08x \n"
// "\t\t TYPE = 0x%02x \n"
// "\t\t SEC = 0x%02x \n"
"\t\t PID = 0x%04x \n"
"\t\t SUB = 0x%08x \n",
msg,
ntohl(pkt->CID),
LEN,
ntohl(pkt->SID),
// (unsigned)pkt->TYPE,
// (unsigned)pkt->SEC,
ntohs(pkt->PID),
ntohl(pkt->SUB));
}
/**
* \brief 打印包头信息,调试用
*/
static void scp_dump_packet(const char *msg, seg_pkt_head_t *pkt)
{
scp_dump_packet_header(msg, pkt); /* 打印包头信息 */
if (ntohs(pkt->PID) == SCP_PID_ACK) /* 如果是响应包 */
scp_dump_packet_header("Is also ACK for", (seg_pkt_head_t *)(((uint8_t *)(pkt)) + SEG_PKT_HEAD_LEN));
}
/**
* \brief 判断两个包是否相同
* \param ack: 接收包的响应包头(第二个包头)
* \param pkt: 发送包的包头
*/
static inline bool scp_is_same_pkt(seg_pkt_head_t *ack, seg_pkt_head_t *pkt)
{
return true
&& (ack->TYPE == pkt->TYPE)
&& (ack->CID == pkt->CID)
&& (ack->SID == pkt->SID)
&& (ack->PID == pkt->PID)
&& (ack->LEN == pkt->LEN);
}
/**
* \brief 获取响应包,没有完整包则持续重入轮询
* \param pscp: 通信句柄
* \param segment:当前刚发送的包
*/
static int scp_run_receiving(scp_t *pscp, segment_t *segment)
{
segment_t *seg_tx, *seg_rx;
scp_dev_t *pdev = pscp->pdev;
seg_pkt_head_t *pkt_tx, *pkt_rx;
uint16_t PID;
int32_t LEN;
/** 1. 尝试接收数据包(从接收队列中读取) */
seg_rx = scpdevs_read(pdev, RWMODE_RESPOND);
if (seg_rx == NULL)
return -EPERM; /* 未收到数据包直接返回,再次进入此函数轮询 */
/* 已获取到响应包 */
pkt_rx = (seg_pkt_head_t *)scpseg_data(seg_rx); /* 从接收数据段中提取数据包 */
PID = ntohs(pkt_rx->PID); /* 除了回环包,一般都是SCP_PID_ACK */
LEN = ntohl(pkt_rx->LEN) + SEG_PKT_HEAD_LEN;
/** 2. 检查包响应是否正确 */
seg_tx = segment; /* 当前刚发送的包,后面会检查收到的响应包是不是之前也收到过 */
if (scp_pkt_old(pscp, pkt_rx)) {
/* 是发送包序列中其它请求包对应的响应包,很少进入这里 */
seg_tx = NULL; /* 本次发送包重新从已发送队列中获取 */
}
// #if defined(CONFIG_USR_SOC_SCP_CRC32)
// if ( !Scp_PktCrc(pkt_rx, false) ) {
// seg_tx = NULL;
// }
// #endif
if (!seg_tx) {
/* 是发送包序列中其它请求包对应的响应包,很少进入这里 */
seg_tx = segment; /* 当前刚发送的包 */
while (seg_tx) {
if (seg_tx == seg_rx) /* ??? */
break;
seg_tx = seg_tx->next; /* 如果是发送包系列则循环处理(很少使用此功能) */
}
if (!seg_tx) {
scpseg_free(seg_rx);
}
return -EFAULT;
}
/* 获取到响应包,打印包头信息 */
scp_dump_packet("Client received ACK", pkt_rx);
/* 判断是否是对应的响应包 */
seg_tx = segment;
while (seg_tx && seg_rx) {
if (seg_tx->respseg || seg_tx->responds != ACK_NULL) {
/* 如果当前发送包已有响应包,则循环判断这一系列发送的包(该功能很少使用) */
seg_tx = seg_tx->next;
continue;
}
if (seg_rx == seg_tx) {
/* ??? */
if (PID == SCP_PID_ACK) {
/* 如果接受包是响应包 */
seg_tx->responds = (int)(uint8_t)(ntohl(pkt_rx->SUB));
} else {
seg_tx->responds = ACK_ACK1;
}
} else {
/* 判断是否是对应的响应包 */
pkt_tx = (seg_pkt_head_t *)scpseg_data(seg_tx);
if (PID == SCP_PID_ACK) {
/* 如果接收包是响应包 */
/* 响应包中携带的原发送包包头紧跟在响应包包头后面 */
uint8_t *ack_data = (uint8_t *)(pkt_rx) + SEG_PKT_HEAD_LEN; /* 原发送包 */
if (scp_is_same_pkt((seg_pkt_head_t *)ack_data, pkt_tx)) {
/* 如果响应包的包头(第二个包头)和发送包的包头一样 */
/* 该发送包已正确响应 */
seg_tx->responds = (int)(uint8_t)(ntohl(pkt_rx->SUB));
}
} else if ((pkt_rx->TYPE == pkt_tx->TYPE) && (pkt_rx->CID == pkt_tx->CID)
&& (pkt_rx->SUB == pkt_tx->SID)
&& (PID == SCP_RPID(ntohs(pkt_tx->PID)))) {
/* 通常不进入,如果响应包是对回环包的响应 */
seg_tx->responds = ACK_ACK1;
}
}
if (seg_tx->responds != ACK_NULL) {
/* 已正确收到响应包 */
seg_tx->respseg = seg_rx; /* 存放对方回复的消息 */
seg_rx = NULL;
}
seg_tx = seg_tx->next; /* 循环处理一系列发送包(很少用) */
}
if (seg_rx)
scpseg_free(seg_rx);
return 0;
}
/**
* \brief 将已处理完的发送包从发送队列中清除
*/
static int scp_tx_remove_from_writelist(scp_t *pscp, segment_t *segment)
{
int res;
irq_flag lock;
segment_t *seg;
irq_disable(lock);
res = ACK_ACK1;
seg = segment;
while (seg) {
if (seg->responds != ACK_ACK1 && seg->responds != ACK_ACK2
&& seg->responds != ACK_ACK3) {
res = ACK_NACK; /* 等待响应超时 */
}
list_del_init(&(seg->node)); /* 将本次消息从系列中删除 */
seg = seg->next; /* 循环处理一系列发送包 */
}
irq_enable(lock);
return res;
}
/********************************** 接口函数 **********************************/
/**
* \brief 客户端向服务器发送一个数据包,并等待它确认已收到消息
* \param pscp: 创建的通信连接
* \param segment:单次通信的消息,或者一系列消息
* \return 正常响应返回ACK_ACK1,异常返回超时ACK_NACK
*/
int scpclt_send_packet(scp_t *pscp, segment_t *segment)
{
int res, lastcnt;
unsigned long timeout;
scp_dev_t *pdev; /* 通信设备 */
segment_t *seg = segment; /* 数据段 */
if (!scpseg_valid(seg) /* 要发送的消息是否有效 */
|| !scp_valid(pscp) /* 通信连接是否有效 */
|| !scpdev_valid(pscp->pdev, SCP_DEVTYPE_ANY)) { /* 通信设备是否有效 */
return -EACCES;
}
pdev = pscp->pdev; /* 获取与通信连接相匹配的通信设备 */
/** 1. 将消息或消息系列添加到发送队列 */
scp_tx_addto_writelist(pscp, seg);
/** 2. 准备发送消息的包头数据 */
scpdevs_send(segment, RWMODE_START);
run_state:
/** 3. 从写数据包队列中取出一个要写的数据包 */
seg = tx_readfrom_list(pscp);
if (scpseg_valid(seg)) {
seg->pdev = pdev;
// seg->XTM = jiffies; //TODO: 传输时的时间tick,当前未操作
seg->PHS |= SEGMENT_TRANSFERING; /* 设置包状态:当前包正在发送中 */
/** 4. 发送包 */
res = scpdevs_send(seg, RWMODE_REQUEST); /* 客户端发送请求包 */
if (res > 0) {
if (!(seg->PHS & SEGMENT_TRANSMIT1))
seg->PHS |= SEGMENT_TRANSMIT1; /* 当前包已发送,需要等待响应 */
else if (!(seg->PHS & SEGMENT_TRANSMIT2))
seg->PHS |= SEGMENT_TRANSMIT2; /* 未使用 */
else if (!(seg->PHS & SEGMENT_TRANSMIT3))
seg->PHS |= SEGMENT_TRANSMIT3; /* 未使用 */
} else {
seg->responds = ACK_NACK;
}
}
/** 5. 接收数据包响应 */
pscp->active_segment = segment;
/* 这里需要轮询,没有收到完整包则依靠下面的goto实现轮询阻塞 */
scp_run_receiving(pscp, pscp->active_segment);
/* 复位包状态 */
lastcnt = 0;
seg = segment; /* 本次发送的包 */
while (seg) {
if (seg->responds == ACK_NULL && (seg->PHS & SEGMENT_TRANSFERING)) {
/* 如果未收到响应包,做超时判断,超时后将发送状态取消 */
#ifdef CONFIG_SCP_USE_TIMEOUT
//TODO: 增加超时处理
// timeout = difft_jiffies(seg->XTM);
// if (jiffies_to_msecs(timeout) > TCPCS_WORK_TIMEOUT) {
// seg->PHS &= ~SEGMENT_TRANSFERING; // stop
// }
#endif
}
if (seg->responds == ACK_NULL) {
if ((!(seg->PHS & SEGMENT_TRANSFERING)) && (seg->PHS & SEGMENT_TRANSMIT3)) {
/* 如果3次重发都没得到响应,告知发送包响应失败 */
seg->responds = ACK_FACK;
} else {
lastcnt++;
}
}
seg = seg->next; /* 循环处理一系列发送包(当前未使用) */
}
//TODO: 当前没有增加超时机制,是在这里死循环等待数据响应
if (lastcnt > 0) /* 重新接收响应包,直到超时退出 */
goto run_state;
/* 做发送结束后的清理 */
scpdevs_send(segment, RWMODE_FINISH);
pscp->active_segment = NULL; /* 发送包处理完则清空 */
/* 将已处理完的发送包从发送队列中清除 */
res = scp_tx_remove_from_writelist(pscp, segment);
return res;
}
/*********************************** 文件尾 ***********************************/
- scp_server.c
/******************************************************************************
* \brief 私有通信协议服务器端函数接口
* \note File format: UTF-8,中文编码:UTF-8
* \author 将狼才鲸
* \date 2023-03-25
******************************************************************************/
/*********************************** 头文件 ***********************************/
#include "scp_server.h"
#include <stdio.h> /* puts */
/*********************************** 宏定义 ***********************************/
#define scpsrv_packet_declare(pid) static int scpsrv_packet_##pid(scp_handle hdl, seg_pkt_head_t *pkt)
#define scpsrv_packet_call(pid) case pid: {ack = scpsrv_packet_##pid(hdl, pkt);} break
/********************************** 私有函数 **********************************/
/**
* \brief 回环包处理请求包的操作,响应包中赋值请求包对应的ID
* \details 等同于static int scpsrv_packet_SCP_PG_SCP_LOOPBACK(scp_handle hdl, seg_pkt_head_t *pkt)
* \param hdl: 通信句柄
* \param pkt: 接收到的包头
*/
scpsrv_packet_declare(SCP_PG_DEBUG_LOOPBACK)
{
pkt->PID = htons(SCP_RPID(SCP_PG_DEBUG_LOOPBACK));
pkt->SUB = pkt->SID;
return ACK_NULL;
}
/**
* \brief 调试打印输出包
* \details 等同于static int scpsrv_packet_SCP_PG_DEBUG_PUTS(scp_handle hdl, seg_pkt_head_t *pkt)
* \param hdl: 通信句柄
* \param pkt: 接收到的包头
*/
scpsrv_packet_declare(SCP_PG_DEBUG_PUTS)
{
long len = ntohl(pkt->LEN);
char *str = (char *)(((uint8_t *)pkt) + SEG_PKT_HEAD_LEN);
str[len] = 0;
puts(str);
return ACK_ACK1;
}
/**
* \brief 处理请求包的操作,响应包中赋值请求包对应的ID
* \param hdl: 通信句柄
* \param pkt: 收到的包
* \return 协议SUB ID
*/
static uint8_t scpsrv_pkt_parse(scp_handle hdl, seg_pkt_head_t *pkt)
{
uint8_t ack = ACK_NACK;
uint16_t pid = ntohs(pkt->PID); /* 应用层协议ID */
/* 权限控制 */
int sec_level = -1;
if (pkt->SEC & SEC_UG)
sec_level = SECLVL_URGENCY;
else if (pkt->SEC & (SEC_SW | SEC_SR))
sec_level = SECLVL_ADMIN;
else if (pkt->SEC & (SEC_TW | SEC_TR))
sec_level = SECLVL_OPERATOR;
else if (pkt->SEC & (SEC_PW | SEC_PR))
sec_level = SECLVL_USER;
/* 检查权限和通信协议(很少会用到) */
if (sec_level < 0 || sec_level >= SECLVL_COUNT) {
pr_err("\n\tSEC not valid for the packet !\n");
}
else if (pkt->TYPE != SCP_TYPE) {
pr_err("\n\tTYPE not valid for the packet !\n");
}
#if defined(CONFIG_SCP_USE_CRC32)
//TODO: 该函数暂未实现
// else if (!scp_pkt_crc(pkt, false)) {
// pr_err("\n\tCRC error for the packet !\n");
// }
#endif
else {
switch (pid) {
/* 里面会赋值ack变量 */
scpsrv_packet_call(SCP_PG_DEBUG_LOOPBACK); /* scpsrv_packet_declare(SCP_PG_DEBUG_LOOPBACK) */
scpsrv_packet_call(SCP_PG_DEBUG_PUTS);
// scpsrv_packet_call(SCP_PG_DEBUG_GETCHAR);
// scpsrv_packet_call(SCP_PG_SYSTEM_IOR);
// scpsrv_packet_call(SCP_PG_SYSTEM_IOW);
// scpsrv_packet_call(SCP_PG_SYSTEM_IOM);
// scpsrv_packet_call(SCP_PG_SYSTEM_EXIT);
// scpsrv_packet_call(SCP_PG_MSG_READ);
// scpsrv_packet_call(SCP_PG_MSG_WRITE);
};
}
return ack;
}
/********************************** 接口函数 **********************************/
/**
* \brief 当前为空函数
*/
void scpsrv_init(void)
{
/* 打开服务器端只在通信协议中才支持的一些功能,如GUI界面的命令控制等 */
}
/**
* \brief 服务器端轮询响应消息
* \details 查询客户端发来的消息,发回ACK包,或者返回处理后需要返回的数据包
*/
int scpsrv_run(scp_handle handle)
{
int res;
uint8_t ack;
scp_dev_t *pdev = NULL;
seg_pkt_head_t *pkt;
segment_t *seg;
scp_t *pscp = (scp_t *)handle;
if (!scp_valid(pscp))
return -EACCES;
pdev = (scp_dev_t *)(pscp->pdev);
if (!scpdev_valid(pdev, SCP_DEVTYPE_ANY))
return -EACCES;
/* 轮询的读消息,一次轮询不一定读到完整的包,可能多次轮询后才能收满包 */
seg = scpdevs_read(pdev, RWMODE_REQUEST);
if (!seg)
return 0; /* 未收到包或未收满包则直接返回 */
/* 处理读到的消息 */
pkt = (seg_pkt_head_t *)scpseg_data(seg);
/* 处理请求包的操作,响应包中赋值请求包对应的ID */
ack = scpsrv_pkt_parse(handle, pkt);
if (ack != ACK_NULL) {
/* 如果是正确的响应包,即ack等于ACK_NACK或ACK_ACK1,原始包不是SCP_PG_DEBUG_LOOPBACK */
memcpy(pkt + 1, pkt, sizeof(seg_pkt_head_t)); /* 将原发送来的包头复制一份放在响应包头后面的位置 */
/* 协议ID改成包响应,原始协议ID在紧跟的第二包中有保留 */
pkt->PID = htons(SCP_PID_ACK);
pkt->SUB = htonl(ack); /* 响应包类型,ACK_NACK、 */
pkt->LEN = htonl(sizeof(seg_pkt_head_t)); /* 包长度只算响应包的包头 */
}
seg->LEN = sizeof(seg_pkt_head_t) + ntohl(pkt->LEN);
/* 发送响应包 */
res = scpdevs_send(seg, RWMODE_RESPOND);
if (seg->respseg && seg->respseg != seg)
scpseg_free(seg->respseg); /* 如果有响应包则删除 */
scpseg_free(seg); /* 删除收到的包 */
if (ack == ACK_EXTA)
return -ECANCELED;
return res;
}
/*********************************** 文件尾 ***********************************/
- scp_protocol.h
/******************************************************************************
* \brief 私有通信协议
* \note File format: UTF-8,中文编码:UTF-8
* \author 将狼才鲸
* \date 2023-03-25
******************************************************************************/
#ifndef _SCP_PROTOCOL_H_
#define _SCP_PROTOCOL_H_
/*********************************** 头文件 ***********************************/
#include "util_errno.h"
#include "util_config.h"
#include "util_comm.h"
#include "util_print.h"
#include "queue_buffer.h"
#include "queue_buffer_cfg.h"
/*
* 简介:
* 通信协议是分层的,不同层对数据包有不同说法:
* 链路层叫帧frame,网络层叫包packet,传输层叫段segment,应用层叫消息message
* 消息里面加header变成段,段里面加header变成包,包里面加header变成帧,
* 帧通过物理或虚拟线路发送出去本通信模块位于传输层和应用层
* 功能:
* 支持一次发送一系列包,发送方和接收方都有缓存
* 通信链路上的实际发送的数据内容:
* 4字节魔术字起始码 + seg_pkt_head_t + 实际消息数据
* 包含关系:
* 段句柄中有设备句柄,设备句柄中包含网络设备等句柄
* 通信过程中要申请的空间:
* 通信链路建立时申请一个scp_t和一个scpdev_t,
* 以后每个消息发送或者响应时申请一个segment_t
*/
/*********************************** 宏定义 ***********************************/
/* 一个消息系列允许最多包个数 */
#define SCP_MAX_VCID_VALID (8)
#define SCP_MAX_VSID_VALID (8)
/* 长度调整到字节对齐,Alignment must be power of 2. */
#define ALIGN_TO(value, alignment) (((value) + ((alignment) - 1)) & ~((alignment) - 1))
#define SCP_MAGIC 0xAABBCCDD /* 魔术字,便于流式设备一个字节一个字节的读数据时判断设备帧的包头 */
#define SCP_TYPE 0xFF /* 传输层协议类型,当前就一个协议,私有通信协议SCP */
#define SEG_PKT_HEAD_LEN (sizeof(seg_pkt_head_t)) /* 包头长度 */
/**
* \brief 生成协议id
*/
#define SCP_RPID_MARK (0x8000) /* 响应包协议ID最高位为1,responds packet mask */
#define SCP_QPID_MASK (0x7fff) /* 请求包协议ID最高位为0 */
#define SCP_RPID(pid) (SCP_RPID_MARK | (pid)) /* 将协议ID转成响应包 */
#define SCP_QPID(pid) (SCP_QPID_MASK & (pid)) /* 将协议ID转成请求包 */
#define SCP_PID(g, n) ((((g) & 0x7f) << 8) | ((n) & 0xff)) /* 用组别和递增ID生成包协议ID */
#define SCP_PIDG(pid) (((pid) >> 8) & 0x7f) /* 从协议ID中获取组别信息 */
#define SCP_PIDP(pid) ((pid) & 0xff) /* 从协议ID中获取协议信息 */
/**
* \brief 用户组和操作权限控制,很少用到此功能
*/
#define SEC_UG 0x40 /* 紧急urgency,支持system及以下读写 */
#define SEC_SW 0x20 /* system,系统级别操作 */
#define SEC_SR 0x10
#define SEC_SRW (SEC_SR | SEC_SW)
#define SEC_TW 0x08 /* terminal,终端级别操作 */
#define SEC_TR 0x04
#define SEC_TRW (SEC_TR | SEC_TW)
#define SEC_PW 0x02 /* protocol,单次连接级别操作 */
#define SEC_PR 0x01
#define SEC_PRW (SEC_PR | SEC_PW)
#define SEC_NO 0x00
/********************************** 类型定义 **********************************/
/**
* \brief 用户组级别,用于权限控制,预留,当前未使用该功能
*/
enum {
SECLVL_URGENCY = 0x00, /* level: urgency operator,紧急操作,支持system系统及以下操作权限 */
SECLVL_ADMIN, /* administrator,管理员,支持terminal终端及以下操作权限 */
SECLVL_OPERATOR, /* operator,操作员权限,支持protocol协议控制权限 */
SECLVL_USER, /* normal user,普通用户,支持protocol协议内容读内容 */
SECLVL_COUNT
};
/**
* \brief 客户端发出的数据包是否需要服务器端返回数据,transfer type
*/
enum {
TT_NONE = 0, /* 传输类型,transfer type */
TT_ACKONLY, /* 接收方只需回复确认包 */
TT_RESPONDS, /* 接收方需响应具体的数据 */
};
/**
* \brief 包发送状态
*/
enum {
SEGMENT_ISNULL = 0x00, /* the segment is blank */
SEGMENT_TRANSMIT1 = 0x01, /* 数据包已发送出去,the segment need transmit */
SEGMENT_TRANSMIT2 = 0x02, /* 允许重发2次,总共发送3次,当前未使用重传机制 */
SEGMENT_TRANSMIT3 = 0x04,
SEGMENT_TRANSFERING = 0x08, /* 数据包正在发送中,the segment has transmitted, need respond */
};
/**
* \brief 包反馈类型,acknowledge signal
*/
typedef enum {
ACK_NULL = 0x00, /* 无确认信息 */
ACK_ACK0 = 0x01, /* 正确接收,当前未使用 */
ACK_ACK1 = 0x02, /* 对第1次发送的确认信息 */
ACK_ACK2 = 0x03, /* 对第2次发送的确认信息,当前未使用重传机制 */
ACK_ACK3 = 0x04, /* 对第3次发送的确认信息,当前未使用重传机制 */
ACK_NACK = 0x05, /* 传输超时或者服务器不支持该发送包,worker transmit timeout */
ACK_FACK = 0x06, /* 传输错误,telecommuting transmit fault */
ACK_RACK = 0x07, /* 需要重传,retransmit requirement,当前未使用重传机制 */
ACK_EXTA = 0x08, /* 额外的确认信息,extra acknowledge */
} SCP_ACK;
/**
* \brief 应用层通信包类型的组别,protocol group
* \details 除了ACK,都不是最终的应用层协议ID,这里的分类主要用来划分模块,
* 比如SCP_PG_DEBUG组别的协议处理都放在scp_client_debug.c这个文件中
*/
enum {
SCP_PID_NONE = 0,
SCP_PID_ACK = 0x01, /* 响应包;内容就响应包头+原始接收包的包头+响应数据(如果有) */
SCP_PG_MSG = 0x02, /* SCP协议的通用读写 */
SCP_PG_DEBUG = 0x03, /* 调试信息 */
SCP_PG_SYSTEM = 0x04, /* 控制对方设备的寄存器读写,从底层控制对方设备 */
SCP_PG_DISP, /* GUI底层控制 */
};
/**
* \brief 应用层协议类型
* \details 具体的包类型,也就是具体的通信协议,protocol group,在PID中赋值;
* 在一次通信连接中可以使用多个应用层协议
*/
enum {
/* 正常主从设备间的数据收发 */
SCP_PG_MSG_READ = SCP_PID(SCP_PG_MSG, 0x82), // READ
SCP_PG_MSG_WRITE = SCP_PID(SCP_PG_MSG, 0x83), // WRITE
/* 调试用 */
SCP_PG_DEBUG_PUTS = SCP_PID(SCP_PG_DEBUG, 0x01), /* 打印字符串 */
SCP_PG_DEBUG_GETCHAR = SCP_PID(SCP_PG_DEBUG, 0x02), /* 获取服务器端终端输入 */
SCP_PG_DEBUG_LOOPBACK = SCP_PID(SCP_PG_DEBUG, 0x03), /* 主机和从机通信回环测试 */
/* 对设备的底层操作,寄存器读写 */
SCP_PG_SYSTEM_IOR = SCP_PID(SCP_PG_SYSTEM, 0x01), /* 寄存器读 */
SCP_PG_SYSTEM_IOW = SCP_PID(SCP_PG_SYSTEM, 0x02), /* 寄存器写 */
SCP_PG_SYSTEM_IOM = SCP_PID(SCP_PG_SYSTEM, 0x03), /* 内存拷贝 */
SCP_PG_SYSTEM_EXIT = SCP_PID(SCP_PG_SYSTEM, 0xFF), /* 退出SCP通信 */
};
typedef struct _scp_dev scp_dev_t; /* 不使用头文件包含,而能直接声明scp_devs.h里面的类型定义 */
/* 段数据头 */
typedef struct _seg_pkt_head {
/* 链路层8字节 */
uint32_t CID; /* 连接ID,connection id,发送的不同包CID不同,一个发送包和对应的响应包相同 */
uint32_t LEN; /* 这是发送的有效数据长度 */
/* 传输层16字节 */
uint32_t SID; /* 段ID,segment id,发送的不同包SID不同,发送包和对应的响应包SID相同 */
uint8_t TYPE; /* 传输层协议类型,当前只支持SCP_TYPE */
uint8_t SEC; /* 段操作权限,segment security */
uint16_t PID; /* 应用层协议ID,protocol id,如SCP_PG_DEBUG_LOOPBACK...在响应包中会加入响应MASK */
uint32_t SUB; /* 响应包类型如ACK_ACK1等,protocol sub id */
uint32_t CRC; /* CRC校验 */
} PACKED seg_pkt_head_t;
/**
* \brief 数据段句柄
* \details 取名segment表示其位于传输层,一个segment段中携带一条通信消息
* 该结构体占用152~204字节,需要小于QBUF_GROUP_TMPBUF缓存的512字节
*/
typedef struct _segment {
struct list_head node; /* 数据段句柄中携带本次消息的节点,用于在一系列消息中加入到发送队列中取 */
int TT; /* 数据包类型(是否需要对方反馈数据),segment transfer type */
int PHS; /* 发送包的状态,segment phase */
int requests; /* requests, noused ? */
int responds; /* 已收到的响应包类型,如ACK_ACK1... */
uint32_t LEN; /* segment data length */
unsigned long XTM; /* 传输时的时间,transmit time */
qbuf_t *qbuf; /* segment_t自己所在的缓存 */
qbuf_t *req_qbuf, *rsp_qbuf; /* 请求和响应缓存 */
/* next用于实现一次性发送一系列消息(较少会用到),
respseg存放对方回复的消息; */
struct _segment *next, *respseg;
scp_dev_t *pdev;
/* 前面的60字节是传输数据帧私有数据,暂未使用,后面4字节存入的只有魔术字SCP_MAGIC */
#define SEGMENT_USER_SIZE 64
/* &private_data[SEGMENT_USER_SIZE - 4]是最终通过链路发出去的数据;
发送的数据是4字节魔术字起始码 + seg_pkt_head_t成员 + 有效数据;
当前允许的总长度只有36字节,可以自行将其改大 */
uint8_t private_data[SEGMENT_USER_SIZE + 32];
} segment_t;
/********************************** 接口函数 **********************************/
/**
* \brief 从通信片段segment_t中获取名为private_data的成员
*/
extern uint8_t *scpseg_pri(segment_t *seg);
/**
* \brief 从通信片段segment_t的private_data成员中获取seg_pkt_head_t实际包数据
*/
extern uint8_t *scpseg_data(segment_t *seg);
// extern segment_t *SEG_FROM_PRI(void *pri);
// extern segment_t *SEG_FROM_DATA(void *dat);
/**
* \brief 申请数据段句柄空间
*/
extern segment_t *scpseg_new(scp_dev_t *pdev);
// extern bool Scp_SegmentExpandRomdata(segment_t *seg, bool beReq, bool bRsp);
/**
* \brief 要发送的通信包是否存在
*/
extern bool scpseg_valid(segment_t *seg);
/**
* \brief 删除一个通信包
*/
extern bool scpseg_free(segment_t *seg);
#endif /* _SCP_PROTOCOL_H */
/*********************************** 文件尾 ***********************************/
- scp_protocol.c
/******************************************************************************
* \brief 私有通信协议传输层数据段的相关操作
* \note File format: UTF-8,中文编码:UTF-8
* \author 将狼才鲸
* \date 2023-03-25
******************************************************************************/
/*********************************** 头文件 ***********************************/
#include "scp_protocol.h"
/********************************** 接口函数 **********************************/
/**
* \brief 从通信片段segment_t中获取名为private_data的成员
*/
uint8_t *scpseg_pri(segment_t *seg)
{
uint8_t *su = (uint8_t *)seg;
size_t sz = offsetof(segment_t, private_data);
sz = ALIGN_TO(sz, 16L);
return (uint8_t *)(su + sz);
}
/**
* \brief 从通信片段segment_t的private_data成员中获取seg_pkt_head_t实际包数据
*/
uint8_t *scpseg_data(segment_t *seg)
{
uint8_t *su = (uint8_t *)scpseg_pri(seg); /* segment unit */
return (uint8_t *)(su + SEGMENT_USER_SIZE);
}
/**
* \brief 申请数据段句柄空间
*/
segment_t *scpseg_new(scp_dev_t *pdev)
{
segment_t *seg;
qbuf_t *qbuf;
qbuf = qbuf_alloc(QBUF_GROUP_MSGBUF);
if (qbuf == NULL)
return NULL;
seg = (segment_t *)(qbuf->addr);
memset(seg, 0, sizeof(segment_t) + sizeof(seg_pkt_head_t) * 2);
init_list_head(&seg->node);
seg->qbuf = qbuf; /* segment_t自己所在的缓存 */
seg->pdev = pdev;
return seg;
}
/**
* \brief 要发送的通信包是否存在
*/
bool scpseg_valid(segment_t *seg)
{
if (!seg || !QBUF_ISVALID(seg->qbuf))
return false;
return true;
}
/**
* \brief 删除一个通信包
*/
bool scpseg_free(segment_t *seg)
{
irq_flag lock;
if (!scpseg_valid(seg)) /* 通信包存在 */
return false;
irq_disable(lock);
if (!list_empty(&seg->node)) { /* 消息中携带的链表非空 */
list_del_init(&seg->node); /* 将链表初始化 */
}
irq_enable(lock);
if (seg->req_qbuf) { /* 如果请求缓存存在则删除 */
qbuf_free(seg->req_qbuf);
seg->req_qbuf = NULL;
}
if (seg->rsp_qbuf) { /* 如果响应缓存存在则删除 */
qbuf_free(seg->rsp_qbuf);
seg->rsp_qbuf = NULL;
}
if (seg->qbuf) { /* 如果消息缓存存在则删除 */
qbuf_free(seg->qbuf);
seg->qbuf = NULL;
}
return true;
}
/*********************************** 文件尾 ***********************************/