当前位置:   article > 正文

7、数码相框之修改电子书源码支持远程打印_数码相框源码

数码相框源码

上一节:6、数码相框之网络编程

下一节:8、数码相框之libjpeg的使用

代码见:第 1 个项目数码相框全部源码_图片_文档\源码(含讲课过程中即时编写的文档)\10.远程打印\source\12.show_file_input_netprint

1、功能分析

要实现的功能:
在这里插入图片描述
第4点的理解:如果用stdou.c来打印的话肯定很快,但是用netprint来打印的话还会涉及客户端和服务端,不会一开始马上就打印出来。所以要先把数据存入buffer,当客户端连接之后再传输数据。
第5点理解:参照内核printk的实现的功能,实现设置和显示打印级别。

2、程序框架

在这里插入图片描述
定义结构体成员的时候之所以用DebugPrint(const *format,***)这是因为参照printf的函数实现是这样的,可以通过man 3 printf来查看。

3、修改程序

同之前一样,这里也是相当于新增一个模块,然后通过注册一个结构体来完成,先考虑这个结构体的成员有哪些。

要实现既可以通过串口打印出调试信息也要通过网络传输打印出调试信息,由于有多个方式,我们还要根据客户端选择打印的方式,肯定要有个名字: name

  • 那么首先肯定还是要初始化成员:DebugInit
  • 相对于的要有个退出的函数DebugExit
  • 我们要通过他们来实现打印,肯定有个打印的函数: DebugPrint
  • 我们要实现选择使用哪种或是否同时使用多个打印的方式,要有一个标准来表示该打印方式是否被使用: isCanUse

3.1、debug_manager.h

有了上面的思路先把相应的文件添加好,在include里面添加一个.h,定义上面说到的这些成员的结构体。
在这里插入图片描述

3.2、debug_manager.c

参照别的manager.c,修改名字即可。

3.3、stdout.c

1、分配设置一个结构体:
在这里插入图片描述
对于标准输入和标准输出我们不需要做什么initexit,一开始我们isCanUse都设置为1,让他们一开始都能使用,要注意这个结构体还有其他的成员没有初始化,所以我们在别的地方调用的时候要先判断结构体里面有没有相关成员。

下面来分别实现上面的这些函数
2、实现相关的函数成员:
StdoutDebugPrint这个函数的返回值应该写成什么?

我们可以参照printf函数看他的返回值,man 3 printf,发现它的返回值为返回已经打印的个数,但是错误返回好像没找到,我们这里就直接返回0就好了。

我们之前定义这个标准打印函数的时候的参数一开始是设置为变长的,是参照printf的函数模型,在man 3 printf中查看。
在这里插入图片描述
由于我们这里StdoutDebugPrint函数的参数。那么这参数怎么处理呢?我们参照内核里面的printk是怎么做的。
在这里插入图片描述
上面的内核printk传入的也是变参,他就是用上面的方式处理变参的,我们参照内核的printk我们修改StdoutDebugPrint为:
在这里插入图片描述
我们只要把内核中的vprintk换成printf就可以了,其他的语句就是处理变参的改完之后我们发现,不是说我们的printf是变参么,为什么我们上面直接替换之后不是变参了呢?可能不是这样做的,我们参照下glibc里面printf的实现(glibc可以在网上下,然后用sourceinsight打开),所有的库函数都可以在glibc里面找到他的实现方式。

glibc里面printf的实现:
在这里插入图片描述
发现glibc里面是用vfprintf来实现的,我们把之前的修改的替换成:
在这里插入图片描述
其实上面的框中的内容就是printf最终的代码我们把变参放到了上面一层,变参的原理还是上面的讲的,最终变成了如下:
在这里插入图片描述
由于我们是标准输入,我们直接用printf打印出来就可以了。

3.4、netprint.c

先明确几个问题。

首先我们要确定我们这里采用TCP还是UDP。开发板是作为server端还是client端,我们这里采用网络通信使用 UDP,至于把烧入开发板的程序当做 server 端还是 client 端由你自己来决定。

这里把烧入开发板的程序当做 server 端,因为开发板上你可以随便执行应用程序,然后你想在哪一台机器上打印,就用那台机器登录开发板就可以了。

结构体变量定义如下:
在这里插入图片描述

NetDbgInit函数

其实里面要完成的就是UDPserver端socketbind函数。
在这里插入图片描述
由于我们要实现向客户端发送数据,但是数据可能已经准备好了,但是客户端还没连接成功,所以我们必须先将数据放入一个缓冲中,等待客户端连接成功才能发送,由于我们缓冲区的数据要一边产生一边消耗,所以这样我们就引入了环形缓冲区。由于我们想通过一个环形缓冲区来实现一个存储数据的池子,而环形缓冲区有连个互不干扰的指针,读和写,而且创建之前必须分配一个环形缓冲区的内存,另外我们还要实现从客户端接收命令然后发送指定的数据,所以我们可以分别创建两个线程,一个用来读,一个用来。
在这里插入图片描述
写进程是用来向客户端发送数据的,所以必须等待客户端连接成功才能发送,那么什么时候客户端连接成功了呢?

只有做个全局变量,然后在接收线程的时候收到了数据,我们就把这个标志未置1,所以发送线程一开始为休眠,只有当为连接成功并且环形缓冲区有的时候就让写进程发送数据,那么写进程是何时被唤醒呢?

只有当我们应用层调用netprint的时候把数据放入环形缓冲区之后就唤醒写进程
那么何时发送缓冲区的数据呢?
只有唤醒了并且缓冲区有数据才发送。

Q1:能不能在客户端连接成功的时候唤醒呢?而不是在应用层调用netprint的时候把数据写入缓冲区才唤醒;
Q2:能不能在判断缓冲区是否不为空的时候唤醒呢?和在应用层调用netprint的时候把数据写入缓冲区才唤醒有区别么;
Q3:我们为什么要创建两个线程,我们不是还有个主线程么,不能让主线程来接收数据么?写两个线程有什么好处呢?

所以上面的代码修改如下:
在这里插入图片描述

NetDbgPrint函数

同理StdoutDebugPrint,这个NetDbgPrint最开始设计的时候也是个变参,由于我们这里是网络打印,我们这个函数就要实现:

  • 先把数据放到一个缓冲区
  • 等待客户端连接之后,在把数据传输出去

那么我们怎么实现把数据放到缓冲区呢?要注意的是现在还是在NetDbgPrint是变参的情况,我们还是参照glibc里面的springf函数,这个函数实现的就是把数据存到一个缓冲区去。
在这里插入图片描述
可以man 3 sprintf看他的原型就知道他是把数据存到一个buff里面。

参照修改为的代码是:
在这里插入图片描述
这里的s就是一个零时的缓冲区,然后再把这个临时缓冲区的数据放到环形缓冲区中去:
在这里插入图片描述
后面就是把数据通过唤醒线程把数据通过发送线程发送出去,但是最终的代码是:
在这里插入图片描述
上面的临时缓冲区不见了,是我们最终放到了debug_manger里面实现的。

发送线程的函数NetDbgSendTreadFunction

在这里插入图片描述
发送线程一开始是要让她一直休眠的,只有应用层调用netprint的时候并把数据放到了环形缓冲区,那么这个线程就会被唤醒,当被唤醒并且客户端已连接后就开始发送数据,但是一次发送多少呢?这里就要看下udp发送多少比较稳定了,网上可以查阅,我们这里设置512。

接收线程的函数NetDbgRecvTreadFunction

在这里插入图片描述
这上面接受数据之后要对他进行解析,上面方框分别是获取客户端信息,设置打印级别,设置打印通道。

NetDbgExit函数

我们这里要实现:关闭socket、释放环形缓冲区。
在这里插入图片描述

环形缓冲区

为什么要用环形缓冲区呢:由于环形缓冲区的读写分开特性,当两个线程进行通信的时候,可以采用环形缓冲区进行交流,一个进程读取,一个进程写入,由于读写的位置不同,并不需要加锁进行并发控制,也就减少了锁的时间开销。
在这里插入图片描述
先来理解下:

假设我的环形缓冲区大小为10的数组,我们写缓冲区的起始位置是可以随意写的,刚开始的时候肯定是空的,这个时候r=w;

现在我们考虑两种极端的情况,我们先写3个数据,假设从0开始写,然后一直读,那么是如何判断是空还是满的?

假设我们缓冲区的长度是10,当我们写到3的时候,写完之后,写指针++,所以指向了4;
当我们读到3的时候,读指针之后也指向了4,这是r=w,但是r!=(w+1)%LEN,所以这时候就为空了。

如果我们一直写,但是不读的话,当我们写到读指针的前一个位置的时候,就会满足r!=(w+1)%LEN,所以会导致满。

满的条件:
正常理解是我写了10个数据就满了,即w=10,又因为要考虑我的读指针,所以正常理解满是指写指针比读指针大10,即w=r+10。

又由于是环形缓冲区,写比读大10,实际上就是w=r,但这种情况和空的情况冲突了,所以我们采用环形缓冲中某一个不写,也就是实际写指针比读指针大LEN-1 就代表满了。
那么如何用代码表示这种判断呢?

就是(w+1)%LEN = r,满就是让w和r绕操场跑步,w要比r多跑一圈追上r才算满,空就是r就是在同一圈的情况下,r追上w,r是永远不会比w快的,因为相等了就已经满足空的情况了。

实际代码就是不断的判断是否为空或满再执行操作,所以空了,就不会有继续动作了,这就是r永远不会比w快的原因。

空的条件为:w=r。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我么下面实现环形缓冲区的操作函数:
在这里插入图片描述

3.5、debug_manger.c

设置打印级别操作函数,之前我们在netprint.c中的接收线程要解析数据,我们先把设置打印级别的函数实现:
在这里插入图片描述
我们之前有规定打印级别客户端发送的命令是dbglevel=xxx,我们先把=后面的数据取出来,我们仿照内核,假设有8个打印级别:
在这里插入图片描述
一开始我们让打印级别最高,只要打印级别小于8就都会打印,打印级别值越小级别越高。
在这里插入图片描述
用传入的第10个参数就是打印级别,和0的ascii码做减法即可。

设置打印通道函数,我们客户端对于这个命令传过来的值是:
在这里插入图片描述
我们就要根据上面的数据=前面的值来判断是哪个通道,再根据=后面的值把该通道结构体中的iscanuse成员设置。

字符串操作函数:

  • 从一个字符串里查找一个字符:
    在这里插入图片描述
  • 把一个字符串里的前 n 个字符串拷贝进另外一个字符串:
    在这里插入图片描述
  • 比较两个字符串:
    *
    上面的框中是取到了名字然后给他一个结束符,构成一个字符串。

我们要给上次一个打印函数接口:
在这里插入图片描述
由于我们这层用的也是变参,我们就可以让下面的stdio.cnetprint.c里面就不用变参了,我们直接在这层把数据处理好就可以了,所以我们的stdio.cnetprint.c里面分别改为:
在这里插入图片描述
在这里插入图片描述
netprint.c中这里我们还把之前的做的一个临时缓冲区省了,直接把传入的数据给环形缓冲区就好了,这样我们在debug_manager里面实现把要打印的数据放到一个一个临时缓冲去,和在netprint.c中一样。
在这里插入图片描述
上面的函数分三部分:

  • 1、用vsprintf先把要打印的数据放到一个临时缓冲区里面去,这部分就已经实现了处理变参这里最后有添加一个结束符:
    在这里插入图片描述
    这是以防万一vsprintf没有实现结束符。
  • 2、根据打印级别来判断是否打印
    先判断要打印的数据的第一个字符和第3个字符看是否为打印级别的语句,再判断打印级别有没有超过我们设置的范围,说明这句可以打印出来,我们就把前面的打印级别的几个字符给去掉,后面的打印出来,所以加3,如果没有设置打印级别,我们就要给他一个默认的打印级别,我们先在.h里面设置一个默认打印级别。
    在这里插入图片描述
    在内核或者应用层中打印信息前面都会加有一个打印级别,其实这个打印级别就是一个字符串
    printk(KERN_INFO”%S:…………\n”,………);#defined KERN_INFO “<6>”
    上面展开为就是”<6>””%s….\n”,这就是两个双引号的字符串合并在一起写,就是一个字符串,上面的字符串,前三个字符就是打印级别。
  • 3、分别调用stdoutnetprint的打印语句
    打印调试信息函数:在上面的一层,用变参函数把参数处理好,存入 strTmpBuf 指向的数组里:
    在这里插入图片描述
    下面一层的打印调试信息函数就不需要用变参了:
    在这里插入图片描述

4、编写客户端程序

#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> dbglevel=<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> dbglevel=<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 */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

5、测试

1、编译sever端程序:make

2、编译client端程序:gcc -o netprint_client netprint_client.c

3、装载触摸屏驱动:insmod s3c_ts.ko

4、设置环境变量(确定是哪个设备节点对应触摸屏)

export TSLIB_TSDEVICE=/dev/event0
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5、较准:ts_calibrate

6、开发板上运行sever端./show_file -s 24 -d fb -f ./MSYH.TTF ./utf8.txt

7、ubuntu上运行client端./netprint_client
在这里插入图片描述
在开发板上按下nu上一页、下一页。

上一节:6、数码相框之网络编程

下一节:8、数码相框之libjpeg的使用

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/263389
推荐阅读
相关标签
  

闽ICP备14008679号