文章目录
电子书涉及知识点:
字符编码
点阵显示
矢量字体显示
模块化编程
多线程编程
代码见:第 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、分配设置一个结构体:
对于标准输入和标准输出我们不需要做什么init
和exit
,一开始我们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函数
其实里面要完成的就是UDP
的server端
的socket
、bind
函数。
由于我们要实现向客户端发送数据,但是数据可能已经准备好了,但是客户端还没连接成功,所以我们必须先将数据放入一个缓冲中,等待客户端连接成功才能发送,由于我们缓冲区的数据要一边产生一边消耗,所以这样我们就引入了环形缓冲区。由于我们想通过一个环形缓冲区来实现一个存储数据的池子,而环形缓冲区有连个互不干扰的指针,读和写,而且创建之前必须分配一个环形缓冲区的内存,另外我们还要实现从客户端接收命令然后发送指定的数据,所以我们可以分别创建两个线程,一个用来读,一个用来。
写进程是用来向客户端发送数据的,所以必须等待客户端连接成功才能发送,那么什么时候客户端连接成功了呢?
只有做个全局变量,然后在接收线程的时候收到了数据,我们就把这个标志未置1,所以发送线程一开始为休眠,只有当为连接成功并且环形缓冲区有的时候就让写进程发送数据,那么写进程是何时被唤醒呢?
只有当我们应用层调用netprint
的时候把数据放入环形缓冲区之后就唤醒写进程
那么何时发送缓冲区的数据呢?
只有唤醒了并且缓冲区有数据才发送。
Q1:能不能在客户端连接成功的时候唤醒呢?而不是在应用层调用netprint
的时候把数据写入缓冲区才唤醒;
Q2:能不能在判断缓冲区是否不为空的时候唤醒呢?和在应用层调用netprint
的时候把数据写入缓冲区才唤醒有区别么;
Q3:我们为什么要创建两个线程,我们不是还有个主线程么,不能让主线程来接收数据么?写两个线程有什么好处呢?
所以上面的代码修改如下:
NetDbgPrint函数
同理