第三阶段应用层——1.7 数码相册—电子书(6)—支持远程打印信息

数码相册——电子书支持远程打印信息

  • 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
  • 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》、【多线程编写】
  • 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链
  • 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3


一、前言

【1.7 数码相册—电子书(5)—多线程支持多输入】中实现了多线程支持多输入,在此基础上,实现多线程支持多输出:标准输出与UDP网络打印输出

二、网络编程基础

1、客户端与服务端

对于网络程序,与我们之前编写的应用程序最大的区别就是网络程序有两个部分组成:客户端和服务器端,两者可通过局域网连接,进行数据的接受与发送。

  • 服务器端:被动等待外面的程序来和自己通讯的程序,称为服务端程序
  • 客户端:主动与外界的程序进行通信的程序,称为客户端程序

举一个简单的例子,服务器端可以类比为一家餐厅客服端类比为顾客
“服务器餐厅”准备好菜单(这里的菜单就是服务端程序中服务器的设置:ip地址…)打开门来等待“客户端顾客”的“帮衬(连接)”,同时“客户端顾客”也会准备好钱(这里的钱就是客户端程序中客户端的设置:ip地址…)来埋单。
对于“服务器餐厅”与“客户端顾客”,二者都可以选择怎样的顾客进行接待与进入到何种餐厅进行消费(在客户端和服务器端都可以通过设置,选择连接特定的网段或类型)

2、TCP传输控制协议

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流传输层通信协议,其中可靠的特点使得他在一些重要的数据通信中使用的很广泛。

提问:如何使用这种协议呢?
回答:通过类比文件读写来理解:

  • 对于文件读写,其最终要的三个要素就是:文件描述符(源)、存储读写信息的变量(终)、读写信息的大小或长度,
  • 对于网络程序,(源)就是客户端与服务端的IP地址或Socket套接字(终)就是客户端与服务端的IP地址或Socket套接字发送或接收信息的内容与大小。
    服务器端:
    socket-->bind-->listen-->accept
    客户端:
    socket-->connect

    在这里插入图片描述

3、UDP传输控制协议

对于Internet 的传输层有两个主要协议,除了TCP协议,就是现在介绍的UDP协议。UDP协议为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法,其中报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小数据传输效率高适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。
理解这种传输协议,同样可以与文件读写来进行类比

  • 对于文件读写,其最终要的三个要素就是:文件描述符(源)、存储读写信息的变量(终)、读写信息的大小或长度,
  • 对于网络程序,(源)就是客户端与服务端的IP地址或Socket套接字(终)就是客户端与服务端的IP地址或Socket套接字发送或接收信息的内容与大小。
    服务器端:
    socket-->bind
    客户端:
    socket-->connect

    在这里插入图片描述

三、框架

1、软件框架

在之前的基础上新增一个debug调试部分debug_manager.c管理者负责管理下层的stdout标准输出和netprint网络打印输出

在这里插入图片描述
对于上述完成主要功能的6个部分:encoding编码部分、fonts获取字体点阵部分、display显示部分、input输入部分、draw协调部分debug调试部分

  1. encoding编码部分去文件中获得编码信息,对于每个文件,保存的时候,系统对自动或手动的根据编码规范,对文件的信息进行编码:如保存为ASCII、GBK、UTF-8、UTF16LE、UTF16BE
  2. fonts获取字体点阵部分根据获得的编码信息得到字体数据(LCD上表示为点阵)
  3. display显示部分把字体数据(点阵)显示在LCD上
  4. input输入部分管理不同的输入方式,定制不同的输入方式所实现的控制,满足多种控制场合;
  5. draw协调部分组织各个模块部分进行合作,实现电子书的显示、翻页功能等。
  6. debug调试部分设置打印等级和打印通道,通过打印等级控制程序打印的调试信息错误信息警告信息等,通过打印通道设置来控制程序打印的输出的流向标准输出还是网络打印输出

2、Makefile框架图

在这里插入图片描述
由于整个分为如下3部分:顶层目录的Makefile顶层目录的Makefile.build各级子目录的Makefile

所以对于新添加的debug输入部分需要进行如下修改:其目录下也需要添加该目录下的Makefile,对于顶层目录的Makeifle也需要添加obj-y += /debug,具体的在下面会具体介绍。

而对于顶层的Makefile.build不需要进行修改,因为它是一个比较通用的文件根据顶层目录的Makefile信息进入到各个子目录下进行预处理、编译、汇编,最后每个子目录与顶层得到的built-in.o进行链接,最后得到可执行文件show_file.

四、debug调试部分

1、管理者头文件debug_manager.h

在这个文件:

  • 定义一个结构体变量拓展文件可以根据自己的情况:分配结构体、设置结构体、注册结构体
  • 定义一些函数,供外部文件调用

#ifndef _DEBUG_MANAGER_H
#define _DEBUG_MANAGER_H

#define	APP_EMERG			"<0>"	/* system is unusable */
#define	APP_ALERT			"<1>"	/* action must be taken immediately	*/
#define	APP_CRIT			"<2>"	/* critical conditions */
#define	APP_ERR	    		"<3>"	/* error conditions	*/
#define	APP_WARNING			"<4>"	/* warning conditions */
#define	APP_NOTICE			"<5>"	/* normal but significant condition	*/
#define	APP_INFO			"<6>"	/* informational */
#define	APP_DEBUG			"<7>"	/* debug-level messages */

#define DEFAULT_DBGLEVEL	4

typedef struct DebugOpr {
	char *name;
	int isUsed;
	int (*DebugInit)(void);
	int (*DebugExit)(void);
	int (*DebugPrint)(char *strdata);
	struct DebugOpr *ptNext;
}T_DebugOpr, *PT_DebugOpr;

int RegisterDebugOpr(PT_DebugOpr ptDebugOpr);
void ShowDebugOpr(void);
int SetDebugLevel(char *precvbuf);
int DebugPrint(const char * pFormat, ...);
PT_DebugOpr GetDebugOpr(char *pName);
int SetDebugChanel(char *precvbuf);
int DebugInit(void);
int InitDebugChanel(void);
int StdoutInit(void);
int NetPrintInit(void);

#endif /* _DEBUG_MANAGER_H */

2、管理者实现文件debug_manager.c

  • 注册函数:
/* 函数名:	注册函数
 * 函数功能:构建一个链表:把多个拓展文件的结构体“串”起来
 * 函数实现:根据传入的结点,首先判断该链表头是否为空
 *				空则,头结点指向传入的节点,且把节点的ptNext域指向NULL
 *				不空则,尾插法插入链表
 */
int RegisterDebugOpr(PT_DebugOpr ptDebugOpr)
{
	PT_DebugOpr ptTmp;

	if (!s_ptDebugOprHead)
	{
		s_ptDebugOprHead   = ptDebugOpr;
		ptDebugOpr->ptNext = NULL;
	}
	else
	{
		ptTmp = s_ptDebugOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptDebugOpr;
		ptDebugOpr->ptNext = NULL;
	}

	return 0;
}
  • 显示支持拓展文件的名字:
/* 显示支持拓展文件的名字 */
void ShowDebugOpr(void)
{
	int i = 0;
	PT_DebugOpr ptTmp = s_ptDebugOprHead;

	while (ptTmp)
	{
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}
  • 设置打印级别函数:
    对于此函数,根据约定的输入方式规定输入字符串为:dgblevel=<0-7>,字符串的9号索引为等级
/* 设置打印级别
 * precvbuf = "dgblevel=<0-7>"
 */
int SetDebugLevel(char *precvbuf)
{
	s_DebugLevelLimit = precvbuf[9] - '0';
	return 0;
}
  • 打印调试信息函数:
    参考内核的打印函数进行改写,用于程序中的打印信息的输出使用方法是DebugPrint(标志位+打印信息),其中标志位为debug_manager.h中定义的8个打印等级。
/* 打印函数 */
int DebugPrint(const char * pFormat, ...)
{
	int num;
	int debuglevel;
	char tmpbuf[1000];
	char *tmpstr;
	va_list args;
	PT_DebugOpr ptTmp = s_ptDebugOprHead;

	va_start(args, pFormat);
	num = vsprintf(tmpbuf, pFormat, args);
	va_end(args);
	tmpbuf[num] = '\0';
	
	debuglevel = DEFAULT_DBGLEVEL;
	tmpstr = tmpbuf;
	/* 根据打印级别决定打印何种信息 */
	if ((tmpbuf[0] == '<') && (tmpbuf[2] == '>')) {
		debuglevel = tmpbuf[1] - '0';

		if ((debuglevel >= 0) && (debuglevel <= 9))
			tmpstr = tmpstr + 3;
		else
			debuglevel = DEFAULT_DBGLEVEL;
	}

	/* 打印级别不同 */
	if (debuglevel > s_DebugLevelLimit)
		return -1;

	/* 调用链表中所有isUsed==1的结构体的DebugPrint() */
	while (ptTmp)
	{
		if (ptTmp->isUsed == 1)
			ptTmp->DebugPrint(tmpstr);
		
		ptTmp = ptTmp->ptNext;
	}

	return 0;
}
  • 获取指定名字的拓展文件结构体:
    根据输入的名字字符串,在链表中寻找对应名字的结点
/* 获取指定的名字的拓展文件结构体 */
PT_DebugOpr GetDebugOpr(char *pName)
{
	PT_DebugOpr ptTmp = s_ptDebugOprHead;
	
	while (ptTmp)
	{
		if (strcmp(ptTmp->name, pName) == 0)
		{
			return ptTmp;
		}
		ptTmp = ptTmp->ptNext;
	}
	return NULL;
}
  • 设置打印通道
    1、约定好输入的字符串,根据字符串,先找到=
    2、提取=号前的字符串获得需要设置的通道
    3、调用GetDebugOpr()来获取对应的结点,设置其的打印通道标志位
/* 设置打印通道 
 * stdout=0			: 关闭stdout通道
 * stdout=1			: 打开stdout通道
 * netprint=0		: 关闭netprint通道
 * netprint=1		: 打开netprint通道
 */
int SetDebugChanel(char *precvbuf)
{
	char *strtmp;
	char strname[100];
	PT_DebugOpr ptTmp;

	memset(strname, 0, 100);
	strtmp = strchr(precvbuf, '=');
	if (!strtmp)
		return -1;
	else {
		strncpy(strname, precvbuf, strtmp - precvbuf);
		ptTmp = GetDebugOpr(strname);

		if (ptTmp == NULL)
			return -1;
		
		if (strtmp[1] == '0')
			ptTmp->isUsed = 0;
		else
			ptTmp->isUsed = 1;
		
		return 0;
	}

	return 0;
}
  • 初始化函数
    把设备节点存入到链表中
/* 初始化函数 */
int DebugInit(void)
{
	int error;
	
	error  = StdoutInit();
	error |= NetPrintInit();
	
	return error;
}
  • 初始化设备函数:
    根据链表中已经存在的设备,进行具体的设备初始化
int InitDebugChanel(void)
{
	PT_DebugOpr ptTmp = s_ptDebugOprHead;
	
	while (ptTmp)
	{
		if (ptTmp->isUsed && ptTmp->DebugInit)
			ptTmp->DebugInit();

		printf("name %s\n", ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
	
	return 0;
}
  • 完整文件

#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#include "config.h"
#include "debug_manager.h"

static PT_DebugOpr s_ptDebugOprHead;
static int s_DebugLevelLimit = 8;	//打印级别,8为最高

/* 函数名:		 注册函数
 * 函数功能:构建一个链表:把多个拓展文件的结构体“串”起来
 * 函数实现:根据传入的结点,首先判断该链表头是否为空
 *				空则,头结点指向传入的节点,且把节点的ptNext域指向NULL
 *				不空则,尾插法插入链表
 */
int RegisterDebugOpr(PT_DebugOpr ptDebugOpr)
{
	PT_DebugOpr ptTmp;

	if (!s_ptDebugOprHead)
	{
		s_ptDebugOprHead   = ptDebugOpr;
		ptDebugOpr->ptNext = NULL;
	}
	else
	{
		ptTmp = s_ptDebugOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptDebugOpr;
		ptDebugOpr->ptNext = NULL;
	}

	return 0;
}

/* 显示支持拓展文件的名字 */
void ShowDebugOpr(void)
{
	int i = 0;
	PT_DebugOpr ptTmp = s_ptDebugOprHead;

	while (ptTmp)
	{
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}

/* 设置打印级别
 * precvbuf = "dgblevel=<0-7>"
 */
int SetDebugLevel(char *precvbuf)
{
	s_DebugLevelLimit = precvbuf[9] - '0';
	return 0;
}

/* 打印函数 */
int DebugPrint(const char * pFormat, ...)
{
	int num;
	int debuglevel;
	char tmpbuf[1000];
	char *tmpstr;
	va_list args;
	PT_DebugOpr ptTmp = s_ptDebugOprHead;

	va_start(args, pFormat);
	num = vsprintf(tmpbuf, pFormat, args);
	va_end(args);
	tmpbuf[num] = '\0';
	
	debuglevel = DEFAULT_DBGLEVEL;
	tmpstr = tmpbuf;
	/* 根据打印级别决定打印何种信息 */
	if ((tmpbuf[0] == '<') && (tmpbuf[2] == '>')) {
		debuglevel = tmpbuf[1] - '0';

		if ((debuglevel >= 0) && (debuglevel <= 9))
			tmpstr = tmpstr + 3;
		else
			debuglevel = DEFAULT_DBGLEVEL;
	}

	/* 打印级别不同 */
	if (debuglevel > s_DebugLevelLimit)
		return -1;

	/* 调用链表中所有isUsed==1的结构体的DebugPrint() */
	while (ptTmp)
	{
		if (ptTmp->isUsed == 1)
			ptTmp->DebugPrint(tmpstr);
		
		ptTmp = ptTmp->ptNext;
	}

	return 0;
}

/* 获取指定的名字的拓展文件结构体 */
PT_DebugOpr GetDebugOpr(char *pName)
{
	PT_DebugOpr ptTmp = s_ptDebugOprHead;
	
	while (ptTmp)
	{
		if (strcmp(ptTmp->name, pName) == 0)
		{
			return ptTmp;
		}
		ptTmp = ptTmp->ptNext;
	}
	return NULL;
}

/* 设置打印通道 
 * stdout=0			: 关闭stdout通道
 * stdout=1			: 打开stdout通道
 * netprint=0		: 关闭netprint通道
 * netprint=1		: 打开netprint通道
 */
int SetDebugChanel(char *precvbuf)
{
	char *strtmp;
	char strname[100];
	PT_DebugOpr ptTmp;

	memset(strname, 0, 100);
	strtmp = strchr(precvbuf, '=');
	if (!strtmp)
		return -1;
	else {
		strncpy(strname, precvbuf, strtmp - precvbuf);
		ptTmp = GetDebugOpr(strname);

		if (ptTmp == NULL)
			return -1;
		
		if (strtmp[1] == '0')
			ptTmp->isUsed = 0;
		else
			ptTmp->isUsed = 1;
		
		return 0;
	}

	return 0;
}

/* 初始化函数 */
int DebugInit(void)
{
	int error;
	
	error  = StdoutInit();
	error |= NetPrintInit();
	
	return error;
}

int InitDebugChanel(void)
{
	PT_DebugOpr ptTmp = s_ptDebugOprHead;
	
	while (ptTmp)
	{
		if (ptTmp->isUsed && ptTmp->DebugInit)
			ptTmp->DebugInit();

		printf("name %s\n", ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
	
	return 0;
}

3、debug调试部分——stdout.c

在这个函数中,为stdout设备进行:分配结构体、设置结构体、注册结构体

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#include "config.h"
#include "debug_manager.h"

static int StdoutDebugPrint(char *strdata)
{
	/* 直接把输出信息用printf打印出来 */
	printf("%s", strdata);
	
	return strlen(strdata);
}

static T_DebugOpr s_tStdoutDebugOpr = {
	.name        = "stdout",
	.isUsed      = 1,
	.DebugPrint  = StdoutDebugPrint,
};

int StdoutInit(void)
{
	return RegisterDebugOpr(&s_tStdoutDebugOpr);
}

4、debug调试部分——netprint.c

在这里采用的是UDP协议,让这个文件作为服务器端程序,即在开发板上运行的程序为服务器端,供局域网中的客户端连接进来
对于传输数据的存储,考虑到一开始没有客户端连接,所以设置了一个环形缓冲区来进行存储数据
同时在这个文件中设置了两个子线程,发送线程与接收线程

  • 发送线程:用来发送打印信息给客户端
  • 接收线程:用来接收控制信息,如修改打印级别、修改打印通道

#include <stdio.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>

#include "config.h"
#include "debug_manager.h"


#define SERVER_PORT 	 	5678
#define PRINT_BUF_SIZE 		(16 * 1024)

static int s_SocketServer;		//服务器端套接字

static int s_isHaveConect = 0;	//服务器端与客户端连接标志,0-未连接,1-连接
static struct sockaddr_in s_tSocketServerAddr;	//服务器端
static struct sockaddr_in s_tSocketClientAddr;	//客户端

static char *s_pNetPrintBuf;	//网络打印缓冲区
static int s_WritePos = 0;		//环形缓冲区写位置
static int s_ReadPos  = 0;		//环形缓冲区读位置

static pthread_t s_tSendThreadId;		//发送线程ID
static pthread_t s_tRecvThreadId;		//接收线程ID
static pthread_cond_t s_tDebugSendCondvar   = PTHREAD_COND_INITIALIZER;		//发送线程条件变量
static pthread_mutex_t s_tNetDebugSendMutex = PTHREAD_MUTEX_INITIALIZER;	//发送线程互斥量

/* 判断缓冲区是否满, 满 - 1 */
static int isFull(void)
{
	return (((s_WritePos + 1) % PRINT_BUF_SIZE) == s_ReadPos);
}

/* 判断缓冲区是否空, 空 - 1 */
static int isEmpty(void)
{
	return (s_WritePos == s_ReadPos);
}

/* 把数据写入环形缓冲区 */
static int PutData(char val)
{
	if (isFull())
		return -1;
	else {
		s_pNetPrintBuf[s_WritePos] = val;		//写入数据
		s_WritePos = (s_WritePos + 1) % PRINT_BUF_SIZE;	//移动写位置
		return 0;
	}
}

/* 从环形缓冲区中读出数据 */
static int GetData(char *val)
{
	if (isEmpty())
		return -1;
	else {
		*val = s_pNetPrintBuf[s_ReadPos];		//读出数据
		s_ReadPos = (s_ReadPos + 1) % PRINT_BUF_SIZE;	//移动读位置
		return 0;
	}		
}

/* 发送线程函数 */
static void *NetDebugSendThreadFunction(void *pvoid)
{
	int i;
	int addrlen;
	int sendlen;
	char val;
	char sendbuf[512];
	
	addrlen = sizeof(struct sockaddr);
	
	while (1) {
		/* 休眠 */
		/* 进入临界资源前,获得互斥量 */
		pthread_mutex_lock(&s_tNetDebugSendMutex);	

		/* pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的s_tNetDebugRecvMutex,
	     * 然后阻塞在等待队列里休眠,直到再次被唤醒
	     * (大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定pthread_mutex_lock(&s_tNetDebugRecvMutex)
	     */
		pthread_cond_wait(&s_tDebugSendCondvar, &s_tNetDebugSendMutex);

		/* 释放互斥量 */
		pthread_mutex_unlock(&s_tNetDebugSendMutex);

		/* 被唤醒之后,把环形缓冲区的数据取出来,用sendto()进行网络打印信息给客户端 */
		while ((s_isHaveConect == 1) && (!isEmpty())) {
			i = 0;
			
			/* 从环形缓冲区中取数据 */
			while((i < 512) && (GetData(&val) == 0)) {
				sendbuf[i] = val;
				i++;
			}

			/* 发送数据 */
			sendlen = sendto(s_SocketServer, sendbuf, i, 0, 
								(const struct sockaddr *)&s_tSocketClientAddr, addrlen);
		}
	}

	return NULL;
}

/* 接收线程函数 */
static void *NetDebugRecvThreadFunction(void *pvoid)
{
	int addrlen;
	int recvlen;
	char recvbuf[1000];
	struct sockaddr_in SocketClientAddr;

	memset(recvbuf, 0, 1000);
	addrlen = sizeof(struct sockaddr);
	while (1) {
		recvlen = recvfrom(s_SocketServer, recvbuf, 999, 0, 
							(struct sockaddr *)&SocketClientAddr, (socklen_t *)&addrlen);

		/* 处理数据 */
		if (addrlen > 0) {
			DBG_PRINTF("netprint.c get msg: %s\n", recvbuf);
			if (strcmp(recvbuf, "setclient") == 0)
			{
				/* 设置客户端 */
				s_tSocketClientAddr = SocketClientAddr;
				s_isHaveConect = 1;
			} else if (strncmp(recvbuf, "dbglevel=", 9) == 0)
				SetDebugLevel(recvbuf);		//设置打印级别
			else
				SetDebugChanel(recvbuf);	//设置打印通道
		}
	}

	return NULL;
}

/* socket初始化 */
static int NetPrintDebugInit(void)
{
	int ret;
	int error;

	printf("NetPrintDebugInit start\n");
	
	/* 分配一个套接口的描述字及其所用的资源
 	 * AF_INET:针对Internet的,因而可以允许在远程主机之间通信
 	 * SOCK_DGRAM:使用UDP协议,这样会提供定长的,不可靠,无连接的通信
	 */
	s_SocketServer = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == s_SocketServer)
	{
		printf("Socket error!\n");
		return -1;
	}

	s_tSocketServerAddr.sin_family	      = AF_INET;
	s_tSocketServerAddr.sin_port		  = htons(SERVER_PORT);  /* host to net, short */
	s_tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(s_tSocketServerAddr.sin_zero, 0, 8);

	/* 与socket返回的文件描述符捆绑在一起 */
	ret = bind(s_SocketServer, (const struct sockaddr *)&s_tSocketServerAddr, sizeof(struct sockaddr));
	if (ret == -1) {
		printf("Bind error:%s\n",strerror(errno));
		return -1;
	}

	/* 分配缓冲区 */
	s_pNetPrintBuf = malloc(PRINT_BUF_SIZE);
	if (s_pNetPrintBuf == NULL) {
		close(s_SocketServer);
		printf("s_pNetPrintBuf malloc error!\n");
		return -1;
	}

	/* 创建netprint发送线程:用来发送打印信息给客户端 */
	error = pthread_create(&s_tSendThreadId, NULL, NetDebugSendThreadFunction, NULL);
	if (error != 0) {
		printf("send pthread_creat error ,error code : %d\n", error);
		return error;
	}
	
	/* 创建netprint接收线程:用来接收控制信息,如修改打印级别、修改打印通道 */
	error = pthread_create(&s_tRecvThreadId, NULL, NetDebugRecvThreadFunction, NULL);
	if (error != 0) {
		printf("recv pthread_creat error ,error code : %d\n", error);
		return error;
	}

	printf("NetPrintDebugInit end\n");
	return 0;
}

/* 关闭socket */
static int NetPrinDebugtExit(void)
{
	close(s_SocketServer);
	free(s_pNetPrintBuf);
	
	return 0;
}

/* 网络打印服务器端 */
static int NetPrintDebugPrint(char *strdata)
{
	
	int i;

	/* 把数据放入到环形缓冲区 */
	for (i = 0; i < strlen(strdata); i++) {
		if (PutData(strdata[i]) != 0)
			break;
	}

	/* 进入临界资源前,获得互斥量 */
	pthread_mutex_lock(&s_tNetDebugSendMutex);	

	/* 客户端连接后,数据通过网络发送给客户端,采用线程的方式 */
	/* 唤醒netprint的发送线程 */
	pthread_cond_signal(&s_tDebugSendCondvar);

	/* 释放互斥量 */
	pthread_mutex_unlock(&s_tNetDebugSendMutex);
	
	return i;
}

static T_DebugOpr s_tNetPrintDebugOpr = {
	.name        = "netprint",
	.isUsed      = 1,
	.DebugInit   = NetPrintDebugInit,
	.DebugExit   = NetPrinDebugtExit,
	.DebugPrint  = NetPrintDebugPrint,
};

int NetPrintInit(void)
{
	return RegisterDebugOpr(&s_tNetPrintDebugOpr);
}

五、Makefile

1、 /debug目录下Makefile

# 子目录Makefile

obj-y += debug_manager.o
obj-y += netprint.o
obj-y += stdout.o

2、顶层目录Makefile

  • 添加库:
LDFLAGS := -lm -lfreetype -lts -lpthread
  • 添加input目录:
obj-y += debug/
  • 完整文件:
# 顶层目录Makefile
#
# 目的:
#	1、每个子目录都会建立一个Makefile,包含当前目录下.c文件.h文件
# 	2、顶层目录下


# 交叉编译工具链
CROSS_COMPILE = arm-linux-

# 定义一些变量 $(CROSS_COMPILE):取出该变量的值 $(CROSS_COMPILE)gcc->arm-linux-gcc
AS		= $(CROSS_COMPILE)as		
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

# 取出变量的值
export 	AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

# := 		简单扩展型变量的值是一次扫描永远使用
# CFLAGS	方便您利用隐含规则指定编译C语言源程序的旗标
# -Wall 	帮助列出所有警告信息
# -02		多优化一些,除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作
# -g		可以认为它是缺省推荐的选项	
CFLAGS := -Wall -O2 -g

# 把当前目录下的include目录下指定为系统目录
# +=		为已经定以过的变量的值追加更多的文本
# -I		指定搜寻包含makefile文件的路径
# $(shell pwd)  执行shell命令的pwd命令,获取执行命令的结果
CFLAGS += -I $(shell pwd)/include

# LDFLAGS	用于调用linker(‘ld’)的编译器的额外标志
# -l		连接库的搜寻目录 -lm调用math库 -lfreetype调用freetype库
LDFLAGS := -lm -lfreetype -lts -lpthread

# 取出变量的值
export 	CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

# TARGET	目标变量
# := 		简单扩展型变量的值是一次扫描永远使使用
TARGET := show_file

obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
obj-y += input/
obj-y += debug/

# 规则		arm-linux-gcc -lm -lfreetype -o show_file built-in.o
all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
	
# clean 	删除所有make正常创建的文件
# 规则		找到所有make创建出来.o文件进行删除
#			删除show_file
clean :
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)
	
# distclean 删除所有正常创建的文件
#			配置文件或为编译正常创建的准备文件,甚至makefile文件自身不能创建的文件
# 规则		找到所有mkae创建出来的.o文件进行删除
#			删除show_file
distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

六、编译与运行

1、编译

执行make,得到可执行文件show_file

2、运行

由于使用到触摸屏,需要调用tslib库来进行校准

  • 执行./show_file -l,显示出当前支持的设备,发现提示一下错误信息:

错误

这里显示绑定地址已在使用中
在这里插入图片描述
通过执行netstat -nap,发现开发板原来有一个UDP协议的端口
在这里插入图片描述
杀掉进程后kill -9 874,重新执行./show_file -l成功
在这里插入图片描述

正确运行

  • 客户端输出的提示信息
    在这里插入图片描述

  • 服务器端输出的打印信息
    在这里插入图片描述

  • 可以看到此时有五个线程:主线程、标准输入子线程、触摸屏输入子线程、标准输出子线程、网络打印输出子线程
    在这里插入图片描述

  • 客户端设置网络打印无效,标准串口输出有效:
    在这里插入图片描述

  • 客户端设置网络打印无效,标准串口输出无效:在这里插入图片描述

  • 客户端设置网络打印有,标准串口输出无效:在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/107116921