04 嵌入式私有通信协议源码,用于两个设备间或者双核之间通信

嵌入式私有通信协议源码,用于两个设备间或者双核之间通信

作者将狼才鲸
创建时间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;
}

/*********************************** 文件尾 ***********************************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值