第三階段應用層——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工具鏈


一、前言

【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成功
在這裏插入圖片描述

正確運行

  • 客戶端輸出的提示信息
    在這裏插入圖片描述

  • 服務器端輸出的打印信息
    在這裏插入圖片描述

  • 可以看到此時有五個線程:主線程、標準輸入子線程、觸摸屏輸入子線程、標準輸出子線程、網絡打印輸出子線程
    在這裏插入圖片描述

  • 客戶端設置網絡打印無效,標準串口輸出有效:
    在這裏插入圖片描述

  • 客戶端設置網絡打印無效,標準串口輸出無效:在這裏插入圖片描述

  • 客戶端設置網絡打印有,標準串口輸出無效:在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章