注:本人已购买韦东山老师第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。
这一节我们继续修改电子书的源码,让电子书既能够通过标准输出打印,也能通过网络远程打印,代码框架如下图所示:
如上图所示:
-
我们仿照之前的代码框架,使用 debug_manager.c 管理 stdout.c 和 netprint.c,在 stdout.c 和 netprint.c 中分别使用 RegisterDebugOpr 函数向上注册 T_DebugOpr 结构体,T_DebugOpr 结构体的代码如下:
typedef struct DebugOpr{ char *name; int isCanUsed; int (*DebugInit)(void); int (*DebugExit)(void); int (*DebugPrint)(char *strData); struct DebugOpr *ptNext; }T_DebugOpr, *PT_DebugOpr;
-
要实现的功能:
- ① 其他文件调用DebugPrint函数打印;
- ② DebugPrint 函数调用底层的结构体来打印,即调用 stdout.c 和 netprint.c提供的打印函数来打印;
- ③ 可以选择是否从某个渠道(stdout.c或者netprint.c)打印;
- ④ 对于netprint.c数据应该先存入一个buffer,然后当客户端连接之后,再用网络通信传出;
- ⑤ 每条信息都有打印级别,可以设置要打印的级别。
1.接下来,我们仿照之前的 display_manager.c 编写 debug_manager.c,debug_manager.c 的代码如下:
#include <debug_manager.h>
#include <config.h>
#include <string.h>
#include <stdio.h>
static PT_DebugOpr g_ptDebugOprHead = NULL;
int RegisterDebugOpr(PT_DebugOpr ptDebugOpr)
{
PT_DebugOpr ptCur;
if (!g_ptDebugOprHead)
{
g_ptDebugOprHead = ptDebugOpr;
ptDebugOpr->ptNext = NULL;
}
else
{
ptCur = g_ptDebugOprHead;
while (ptCur->ptNext)
{
ptCur = ptCur->ptNext;
}
ptCur->ptNext = ptDebugOpr;
ptDebugOpr->ptNext = NULL;
}
return 0;
}
void ShowDebugOpr(void)
{
PT_DebugOpr ptCur;
int i = 0;
if (!g_ptDebugOprHead)
{
printf("don't have InputOpr\n");
return;
}
else
{
ptCur = g_ptDebugOprHead;
do{
printf("%02d %s\n", i++, ptCur->name);
ptCur = ptCur->ptNext;
}while(ptCur);
}
}
PT_DebugOpr GetDebugOpr(char *pcName)
{
PT_DebugOpr ptCur;
if (!g_ptDebugOprHead)
{
return NULL;
}
else
{
ptCur = g_ptDebugOprHead;
do
{
if (strcmp(ptCur->name, pcName) == 0)
return ptCur;
else
ptCur = ptCur->ptNext;
}
while (ptCur);
}
return NULL;
}
int DebugInit(void)
{
int iError;
iError = StdoutInit();
iError |= NetPrintInit();
return iError;
}
2.stdout.c 的代码如下:
#include <debug_manager.h>
#include <stdio.h>
#include <string.h>
static int StdoutDebugPrint(char *strData)
{
/* 直接把输入信息使用printf打印出来 */
printf("%s\n", strData);
return strlen(strData);
}
static int StdoutDebugInit(void)
{
return 0;
}
static int StdoutDebugExit(void)
{
return 0;
}
static T_DebugOpr g_tStdoutDebugOpr = {
.name = "stdout",
.isCanUsed = 1,
.DebugInit = StdoutDebugInit,
.DebugExit = StdoutDebugExit,
.DebugPrint = StdoutDebugPrint,
};
int StdoutInit(void)
{
return RegisterDebugOpr(&g_tStdoutDebugOpr);
}
3.netprint.c 的代码如下:
#include <debug_manager.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#define SERVER_PORT 5678
#define PRINT_BUF_SIZE (16*1024)
static int g_iSocketServer;
static struct sockaddr_in g_tSocketServerAddr;
static struct sockaddr_in g_tSocketClientAddr;
static int g_iHaveConnected = 0; /* 标记是否连接上,0:没连接上;1:已经连接上 */
static char *g_pcNetPrintBuf;
static int g_iReadPos = 0;
static int g_iWritePos = 0;
static pthread_t g_tSendThreadID;
static pthread_t g_tRecvThreadID;
static pthread_mutex_t g_tNetDebugSendMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t g_tNetDebugSendConVar = PTHREAD_COND_INITIALIZER;
/* 判断环形缓冲区是否满
* 返回值:1:满;0:不满
*/
static int isFull(void)
{
return (((g_iWritePos + 1) % PRINT_BUF_SIZE) == g_iReadPos);
}
/* 判断环形缓冲区是为空
* 返回值:1:为空;0:不为空
*/
static int isEmpty(void)
{
return (g_iWritePos == g_iReadPos);
}
static int PutData(char cVal)
{
if (isFull())
{
return -1;
}
else
{
g_pcNetPrintBuf[g_iWritePos] = cVal;
g_iWritePos = (g_iWritePos + 1) % PRINT_BUF_SIZE;
return 0;
}
}
static int GetData(char *pcVal)
{
if (isEmpty())
{
return -1;
}
else
{
*pcVal = g_pcNetPrintBuf[g_iReadPos];
g_iReadPos = (g_iReadPos + 1) % PRINT_BUF_SIZE;
return 0;
}
}
static void *NetDebugSendThreadFunction (void *pVoid)
{
char strTmpBuf[512];
char cVal;
int i;
int iAddrLen;
int iSendLen;
while (1)
{
/* 平时处于休眠状态 */
pthread_mutex_lock(&g_tNetDebugSendMutex);
pthread_cond_wait(&g_tNetDebugSendConVar, &g_tNetDebugSendMutex);
pthread_mutex_unlock(&g_tNetDebugSendMutex);
while ((g_iHaveConnected == 1) && (!isEmpty()))
{
i = 0;
/* 把环形缓冲区的数据取出来,最多取512字节 */
while ((i < 512) && (GetData(&cVal) == 0))
{
strTmpBuf[i] = cVal;
i++;
}
strTmpBuf[i] = '\0';
/* 执行到这里表示被唤醒 */
/* 把环形缓冲区的数据取出来用sendto函数发送打印信息给客户端 */
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(g_iSocketServer, strTmpBuf, i, 0, (const struct sockaddr *)&g_tSocketClientAddr, iAddrLen);
}
}
return NULL;
}
static void *NetDebugRecvThreadFunction (void *pVoid)
{
int iRecvLen;
unsigned char ucRecvBuf[1000];
socklen_t iAddrLen;
struct sockaddr_in tSocketClientAddr;
while (1)
{
/* recvfrom 如果没有数据就处于休眠状态 */
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(g_iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
/* 解析数据:
* debuglevel=0, 1, 2, ... : 修改打印级别
* sdtout = 0 : 关闭stdout打印
* sdtout = 1 : 打开stdout打印
* netprint = 0 :关闭netprint打印
* netprint = 1 :打开netprint打印
* setclient : 设置接受打印信息的客户端
*/
if (strcmp((char *)ucRecvBuf, "setclient") == 0)
{
g_tSocketClientAddr = tSocketClientAddr;
g_iHaveConnected = 1;
}
else if (strncmp((char *)ucRecvBuf, "debuglevel=", 11) == 0)
{
SetDebugLevel((char *)ucRecvBuf);
}
else
{
SetDebugChannel((char *)ucRecvBuf);
}
}
}
return NULL;
}
static int NetDebugInit(void)
{
int iRet;
/* socket 初始化 */
g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0); /* SOCK_DGRAM:UDP数据报 */
if (g_iSocketServer == -1)
{
printf("socket error\n");
return -1;
}
g_tSocketServerAddr.sin_family = AF_INET;
g_tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
g_tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* INADDR_ANY表示可以和任何的主机通信 */
memset((unsigned char*)g_tSocketServerAddr.sin_zero, 0, sizeof(g_tSocketServerAddr.sin_zero));
iRet = bind(g_iSocketServer, (const struct sockaddr *)&g_tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
printf("bind error\n");
return -1;
}
g_pcNetPrintBuf = (char *)malloc(PRINT_BUF_SIZE);
if (g_pcNetPrintBuf == NULL)
{
close(g_iSocketServer);
return -1;
}
/* 创建netprint发送线程:用来发送打印信息给客户端 */
pthread_create(&g_tSendThreadID, NULL, NetDebugSendThreadFunction, NULL);
/* 创建netprint接受线程:用来接受控制信息,比如修改打印级别、打开或关闭打印 */
pthread_create(&g_tRecvThreadID, NULL, NetDebugRecvThreadFunction, NULL);
return 0;
}
static int NetDebugExit(void)
{
/* 关闭socket,... */
close(g_iSocketServer);
free(g_pcNetPrintBuf);
return 0;
}
static int NetDebugPrint(char *strData)
{
/* 把数据放入环形缓冲区 */
int i;
for (i = 0; i < strlen(strData); i++)
{
if (0 != PutData(strData[i]))
break;
}
/* 如果已经有客户端连接了,就把数据通过网络发送给客户端 */
/* 可以使用sendto();或 唤醒线程(这里使用唤醒线程)唤醒netprint的发送线程 */
pthread_mutex_lock(&g_tNetDebugSendMutex);
pthread_cond_signal(&g_tNetDebugSendConVar);
pthread_mutex_unlock(&g_tNetDebugSendMutex);
return 0;
}
static T_DebugOpr g_tNetDebugOpr = {
.name = "netprint",
.isCanUsed = 1,
.DebugInit = NetDebugInit,
.DebugExit = NetDebugExit,
.DebugPrint = NetDebugPrint,
};
int NetPrintInit(void)
{
return RegisterDebugOpr(&g_tNetDebugOpr);
}
对于上述 netprint.c 的代码:
- ① 对于 netprint.c,在网路通信中,我们把它作为服务端,所以初始化的时候,需要进行服务器相关的初始化操作;
- ② 为了更快的打印速度,这里我们使用 UDP 的传输方式,避免 TCP 在传输出错时进行重传等操作导致打印速度慢的问题;
- ③ 使用环形缓冲区存放需要发送的数据,如果有客户端连接了,就把数据通过网络发送给客户端 ;(关于环形缓冲区后面额外补充)
- ④ 创建两个线程:发送线程、接收线程。其中,发送线程用于发送打印信息给客户端,接收线程用于接受控制信息,比如修改打印级别、打开或关闭打印;
- ⑤ 创建后的发送线程和接收线程都处于休眠的状态,当接收线程接收到 “setclient” 表示已经连接上(实际上是获得了客户端的IP地址、端口等信息);连接上后,当有数据需要打印时,通过线程的条件变量唤醒发送线程,然后发送线程每次从环形缓冲区取出512字节的数据发送给客户端;
- ⑥ 对于接收到的控制信息的处理, 需要在 debug_manager.c 添加如下函数:其中 SetDebugLevel 函数用于设置打印级别,SetDebugChannel 函数用于设置打印的渠道:打开/关闭标准输出,打开/关闭网络打印,DebugPrint 用于调用 T_DebugOpr 结构体的打印函数进行打印,InitDebugChannel 用于启动打印(初始化),前面的DebugInit只是注册T_DebugOpr结构体。
#include <stdarg.h> static int g_iDebugLevelLimit = 8; /* strBuf = "debuglevel=<0~9>" */ int SetDebugLevel(char *strBuf) { g_iDebugLevelLimit = strBuf[11] - '0'; return 0; } /* sdtout = 0 : 关闭stdout打印 * sdtout = 1 : 打开stdout打印 * netprint = 0 :关闭netprint打印 * netprint = 1 :打开netprint打印 */ int SetDebugChannel(char *strBuf) { char *pStrTmp; char strName[100]; PT_DebugOpr ptTmp; pStrTmp = strchr(strBuf, '='); if (!pStrTmp) { return -1; } else { strncpy(strName, strBuf, (pStrTmp - strBuf)); strName[pStrTmp - strBuf] = '\0'; ptTmp = GetDebugOpr(strName); if (!ptTmp) { return -1; } if (pStrTmp[1] == '0') { ptTmp->isCanUsed = 0; } else { ptTmp->isCanUsed = 1; } return 0; } } /* 需要在debug_manager.c向外提供打印函数 */ int DebugPrint(const char *pcFormat, ...) { /* 调用链表中所有isCanUsed为1的结构体的 DebugPrint函数 */ char strTmpBuf[1000]; char *pcTmp; va_list tArg; int iNum; PT_DebugOpr ptTmp = g_ptDebugOprHead; int debuglevel = DEFAULT_DBGLEVEL; va_start(tArg, pcFormat); iNum = vsprintf(strTmpBuf, pcFormat, tArg); va_end(tArg); strTmpBuf[iNum] = '\0'; pcTmp = strTmpBuf; /*根据打印级别决定是否打印 */ if ((strTmpBuf[0] == '<') && (strTmpBuf[2] == '>')) { debuglevel = strTmpBuf[1] - '0'; if (debuglevel >= 0 && debuglevel <= 9) { pcTmp = strTmpBuf + 3; } else { debuglevel = DEFAULT_DBGLEVEL; } } if (debuglevel > g_iDebugLevelLimit) { return -1; } while (ptTmp) { if (ptTmp->isCanUsed) { ptTmp->DebugPrint(pcTmp); } ptTmp = ptTmp->ptNext; } return 0; } /* 启动打印,前面的DebugInit只是注册T_DebugOpr结构体 */ int InitDebugChannel(void) { PT_DebugOpr ptTmp = g_ptDebugOprHead; while (ptTmp) { if (ptTmp->isCanUsed && ptTmp->DebugInit) { ptTmp->DebugInit(); } ptTmp = ptTmp->ptNext; } return 0; }
额外补充: 环形缓冲区,其实我们可以理解为它是数组,如下图图所示:
- ① 假设我们有一个长度为 10 的 buf,用于存放我们的数据,开始时读的位置 r 和写的位置 w 都是 0;
- ② 写数据:
buf[w] = val;
更新写的位置:w = (w + 1) % Len;
(buf 的长度为 Len); - ③ 读数据:
val = buf[r];
更新读的位置:r = (r + 1) % Len;
- ④ 判断环形缓冲区是否为空:
r == w;
即当读的位置和写的位置相等时,环形缓冲区为空; - ⑤ 判断环形缓冲区是否已满:
(w + 1) % Len == r;
即当写的下一个位置等于读的位置时,环形缓冲区已满; - ⑥ 环形缓冲区的代码如下:
#define BUF_SIZE (16*1024) /* 环形缓冲区的最大空间为16K */ static char g_cLoopBuf[BUF_SIZE]; /* 定义一个空间为16k的环形缓冲区 */ /* 定义环形缓冲区的读写位置,初始值为0 */ static int g_iReadPos = 0; static int g_iWritePos = 0; /* 判断环形缓冲区是为空 * 返回值:1:为空;0:不为空 */ static int isEmpty(void) { return (g_iWritePos == g_iReadPos); } /* 判断环形缓冲区是否满 * 返回值:1:满;0:不满 */ static int isFull(void) { return (((g_iWritePos + 1) % BUF_SIZE) == g_iReadPos); } /* 往环形缓冲区存放一个数据 * 返回值:0: 成功; -1:失败 */ static int PutData(char cVal) { if (isFull()) { return -1; } else { g_cLoopBuf[g_iWritePos] = cVal; g_iWritePos = (g_iWritePos + 1) % BUF_SIZE; return 0; } } /* 从环形缓冲区读一个数据 * 返回值:0: 成功; -1:失败 */ static int GetData(char *pcVal) { if (isEmpty()) { return -1; } else { *pcVal = g_cLoopBuf[g_iReadPos]; g_iReadPos = (g_iReadPos + 1) % BUF_SIZE; return 0; } }
4.把config.h的#define DBG_PRINTF printf
修改为 #define DBG_PRINTF DebugPrint
。
5.main.c的修改,在 main.c 中我们需要对 T_DebugOpr 结构体进行初始化,并启动 DebugPrint,同时添加响应的打印信息,main.c 的代码如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <config.h>
#include <draw.h>
#include <encoding_manager.h>
#include <fonts_manager.h>
#include <disp_manager.h>
#include <input_manager.h>
#include <debug_manager.h>
#include <string.h>
/* ./show_file [-s Size] [-d display] [-f font_file] [-h HZK] <text_file> */
int main(int argc, char **argv)
{
int iError;
unsigned int dwFontSize = 16;
char acHzkFile[128];
char acFreetypeFile[128];
char acTextFile[128];
char acDisplay[128];
int bList = 0;
T_InputEvent tInputEvent;
acHzkFile[0] = '\0';
acFreetypeFile[0] = '\0';
acTextFile[0] = '\0';
strcpy(acDisplay, "fb");
iError = DebugInit();
if (iError)
{
DBG_PRINTF("DebugInit error!\n");
return -1;
}
InitDebugChannel();
while ((iError = getopt(argc, argv, "ls:f:h:d:")) != -1)
{
switch(iError)
{
case 'l':
{
bList = 1;
break;
}
case 's':
{
dwFontSize = strtoul(optarg, NULL, 0);
break;
}
case 'f':
{
strncpy(acFreetypeFile, optarg, 128);
acFreetypeFile[127] = '\0';
break;
}
case 'h':
{
strncpy(acHzkFile, optarg, 128);
acHzkFile[127] = '\0';
break;
}
case 'd':
{
strncpy(acDisplay, optarg, 128);
acDisplay[127] = '\0';
break;
}
default:
{
DBG_PRINTF("Usage: %s [-s Size] [-d display] [-f font_file] [-h HZK] <text_file>\n", argv[0]);
DBG_PRINTF("Usage: %s -l\n", argv[0]);
return -1;
break;
}
}
}
if (!bList && optind >= argc)
{
DBG_PRINTF("Usage: %s [-s Size] [-d display] [-f font_file] [-h HZK] <text_file>\n", argv[0]);
DBG_PRINTF("Usage: %s -l\n", argv[0]);
return -1;
}
iError = DisplayInit();
if (iError)
{
DBG_PRINTF("DisplayInit error!\n");
return -1;
}
iError = FontsInit();
if (iError)
{
DBG_PRINTF("FontsInit error!\n");
return -1;
}
iError = EncodingInit();
if (iError)
{
DBG_PRINTF("EncodingInit error!\n");
return -1;
}
iError = InputInit();
if (iError)
{
DBG_PRINTF("InputInit error!\n");
return -1;
}
if (bList)
{
DBG_PRINTF("supported display:\n");
ShowDispOpr();
DBG_PRINTF("supported font:\n");
ShowFontOpr();
DBG_PRINTF("supported encoding:\n");
ShowEncodingOpr();
DBG_PRINTF("supported input:\n");
ShowInputOpr();
DBG_PRINTF("supported debug chanel:\n");
ShowDebugOpr();
return 0;
}
strncpy(acTextFile, argv[optind], 128);
acTextFile[127] = '\0';
iError = OpenTextFile(acTextFile);
if (iError)
{
DBG_PRINTF("OpenTextFile error!\n");
return -1;
}
iError = SetTextDetail(acHzkFile, acFreetypeFile, dwFontSize);
if (iError)
{
DBG_PRINTF("SetTextDetail error!\n");
return -1;
}
iError = SelectAndInitDisplay(acDisplay);
if (iError)
{
DBG_PRINTF("SelectAndInitDisplay error!\n");
return -1;
}
iError = AllInputDevicesInit();
if (iError)
{
DBG_PRINTF("AllInputDevicesInit error!\n");
return -1;
}
iError = ShowNextPage();
if (iError)
{
DBG_PRINTF("Error to show first page\n");
return -1;
}
DBG_PRINTF("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit:\n");
while (1)
{
//printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit: ");
if (0 == GetInputEvent(&tInputEvent))
{
if (tInputEvent.iVal == INPUT_VALUE_DOWN)
{
ShowNextPage();
}
else if (tInputEvent.iVal == INPUT_VALUE_UP)
{
ShowPrePage();
}
else if (tInputEvent.iVal == INPUT_VALUE_EXIT)
{
return 0;
}
}
}
return 0;
}
6.客户端程序 netprint_client 的编写:改程序的主要功能是:① 接收服务器的打印信息,并通过 printf 从终端打印出来;② 向服务器发送控制信息。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/* socket
* connect
* send/recv
*/
#define SERVER_PORT 5678
/*
* ./netprint_client <server_ip> debuglevel=<0-9>
* ./netprint_client <server_ip> stdout=0|1
* ./netprint_client <server_ip> netprint=0|1
* ./netprint_client <server_ip> show // setclient,并且接收打印信息
*/
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucRecvBuf[1000];
int iSendLen;
int iRecvLen;
int iAddrLen;
if (argc != 3)
{
printf("Usage:\n");
printf("%s <server_ip> debuglevel=<0-9>\n", argv[0]);
printf("%s <server_ip> stdout=0|1\n", argv[0]);
printf("%s <server_ip> netprint=0|1\n", argv[0]);
printf("%s <server_ip> show\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero, 0, 8);
if (strcmp(argv[2], "show") == 0)
{
/* 发送数据 */
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(iSocketClient, "setclient", 9, 0,
(const struct sockaddr *)&tSocketServerAddr, iAddrLen);
while (1)
{
/* 循环: 从网络读数据, 打印出来 */
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketClient, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketServerAddr, &iAddrLen);
if (iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("%s\n", ucRecvBuf);
}
}
}
else
{
/* 发送数据 */
iAddrLen = sizeof(struct sockaddr);
iSendLen = sendto(iSocketClient, argv[2], strlen(argv[2]), 0,
(const struct sockaddr *)&tSocketServerAddr, iAddrLen);
}
return 0;
}
7.测试:
- ① 服务端 (即开发板):
./show_file -s 24 -d fb -f MSYH.TTF -h HZK16 utf8.txt
- ② 客户端:
./netprint_client <server_ip> debuglevel=<0-9>
设置打印级别:1~9;./netprint_client <server_ip> stdout=0|1
关闭(0)、打开(1)标准输出;/netprint_client <server_ip> netprint=0|1
关闭(0)、打开(1)网络打印;/netprint_client <server_ip> show
打印接收到的信息。