赞
踩
代码见:第 1 个项目数码相框全部源码_图片_文档\源码(含讲课过程中即时编写的文档)\10.远程打印\source\12.show_file_input_netprint
要实现的功能:
第4点的理解:如果用stdou.c
来打印的话肯定很快,但是用netprint
来打印的话还会涉及客户端和服务端,不会一开始马上就打印出来。所以要先把数据存入buffer
,当客户端连接之后再传输数据。
第5点理解:参照内核printk
的实现的功能,实现设置和显示打印级别。
定义结构体成员的时候之所以用DebugPrint(const *format,***)
这是因为参照printf
的函数实现是这样的,可以通过man 3 printf
来查看。
同之前一样,这里也是相当于新增一个模块,然后通过注册一个结构体来完成,先考虑这个结构体的成员有哪些。
要实现既可以通过串口打印
出调试信息也要通过网络传输
打印出调试信息,由于有多个方式,我们还要根据客户端选择打印的方式,肯定要有个名字: name
。
DebugInit
;DebugExit
;DebugPrint
isCanUse
。有了上面的思路先把相应的文件添加好,在include
里面添加一个.h
,定义上面说到的这些成员的结构体。
参照别的manager.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
打印出来就可以了。
先明确几个问题。
首先我们要确定我们这里采用TCP
还是UDP
。开发板是作为server端
还是client端
,我们这里采用网络通信使用 UDP
,至于把烧入开发板的程序当做 server 端
还是 client 端
由你自己来决定。
这里把烧入开发板
的程序当做 server 端
,因为开发板上你可以随便执行应用程序,然后你想在哪一台机器上打印,就用那台机器登录开发板就可以了。
结构体变量定义如下:
其实里面要完成的就是UDP
的server端
的socket
、bind
函数。
由于我们要实现向客户端发送数据,但是数据可能已经准备好了,但是客户端还没连接成功,所以我们必须先将数据放入一个缓冲中,等待客户端连接成功才能发送,由于我们缓冲区的数据要一边产生一边消耗,所以这样我们就引入了环形缓冲区。由于我们想通过一个环形缓冲区来实现一个存储数据的池子,而环形缓冲区有连个互不干扰的指针,读和写,而且创建之前必须分配一个环形缓冲区的内存,另外我们还要实现从客户端接收命令然后发送指定的数据,所以我们可以分别创建两个线程,一个用来读,一个用来。
写进程是用来向客户端发送数据的,所以必须等待客户端连接成功才能发送,那么什么时候客户端连接成功了呢?
只有做个全局变量,然后在接收线程的时候收到了数据,我们就把这个标志未置1,所以发送线程一开始为休眠,只有当为连接成功并且环形缓冲区有的时候就让写进程发送数据,那么写进程是何时被唤醒呢?
只有当我们应用层调用netprint
的时候把数据放入环形缓冲区之后就唤醒写进程
那么何时发送缓冲区的数据呢?
只有唤醒了并且缓冲区有数据才发送。
Q1:能不能在客户端连接成功的时候唤醒呢?而不是在应用层调用netprint
的时候把数据写入缓冲区才唤醒;
Q2:能不能在判断缓冲区是否不为空的时候唤醒呢?和在应用层调用netprint
的时候把数据写入缓冲区才唤醒有区别么;
Q3:我们为什么要创建两个线程,我们不是还有个主线程么,不能让主线程来接收数据么?写两个线程有什么好处呢?
所以上面的代码修改如下:
同理StdoutDebugPrint
,这个NetDbgPrint
最开始设计的时候也是个变参,由于我们这里是网络打印,我们这个函数就要实现:
那么我们怎么实现把数据放到缓冲区呢?要注意的是现在还是在NetDbgPrint
是变参的情况,我们还是参照glibc
里面的springf
函数,这个函数实现的就是把数据存到一个缓冲区去。
可以man 3 sprintf
看他的原型就知道他是把数据存到一个buff
里面。
参照修改为的代码是:
这里的s就是一个零时的缓冲区,然后再把这个临时缓冲区的数据放到环形缓冲区中去:
后面就是把数据通过唤醒线程把数据通过发送线程发送出去,但是最终的代码是:
上面的临时缓冲区不见了,是我们最终放到了debug_manger
里面实现的。
发送线程一开始是要让她一直休眠的,只有应用层调用netprint
的时候并把数据放到了环形缓冲区,那么这个线程就会被唤醒,当被唤醒并且客户端已连接后就开始发送数据,但是一次发送多少呢?这里就要看下udp
发送多少比较稳定了,网上可以查阅,我们这里设置512。
这上面接受数据之后要对他进行解析,上面方框分别是获取客户端信息,设置打印级别,设置打印通道。
我们这里要实现:关闭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。
我么下面实现环形缓冲区的操作函数:
设置打印级别操作函数,之前我们在netprint.c
中的接收线程要解析数据,我们先把设置打印级别的函数实现:
我们之前有规定打印级别客户端发送的命令是dbglevel=xxx
,我们先把=
后面的数据取出来,我们仿照内核,假设有8个打印级别:
一开始我们让打印级别最高,只要打印级别小于8就都会打印,打印级别值越小级别越高。
用传入的第10个参数就是打印级别,和0的ascii码
做减法即可。
设置打印通道函数,我们客户端对于这个命令传过来的值是:
我们就要根据上面的数据=
前面的值来判断是哪个通道,再根据=
后面的值把该通道结构体中的iscanuse
成员设置。
字符串操作函数:
我们要给上次一个打印函数接口:
由于我们这层用的也是变参,我们就可以让下面的stdio.c
和netprint.c
里面就不用变参了,我们直接在这层把数据处理好就可以了,所以我们的stdio.c
和netprint.c
里面分别改为:
在netprint.c
中这里我们还把之前的做的一个临时缓冲区省了,直接把传入的数据给环形缓冲区就好了,这样我们在debug_manager
里面实现把要打印的数据放到一个一个临时缓冲去,和在netprint.c
中一样。
上面的函数分三部分:
vsprintf
先把要打印的数据放到一个临时缓冲区里面去,这部分就已经实现了处理变参这里最后有添加一个结束符:vsprintf
没有实现结束符。.h
里面设置一个默认打印级别。printk(KERN_INFO”%S:…………\n”,………);
,#defined KERN_INFO “<6>”
”<6>””%s….\n”
,这就是两个双引号的字符串合并在一起写,就是一个字符串,上面的字符串,前三个字符就是打印级别。stdout
和netprint
的打印语句strTmpBuf
指向的数组里:#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、编译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
5、较准:ts_calibrate
6、开发板上运行sever端
:./show_file -s 24 -d fb -f ./MSYH.TTF ./utf8.txt
7、ubuntu上运行client端
:./netprint_client
在开发板上按下n
和u
上一页、下一页。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。