在【1.7 数码相册—电子书(5)—多线程支持多输入】中实现了多线程支持多输入,在此基础上,实现多线程支持多输出:标准输出与UDP网络打印输出。
对于网络程序,与我们之前编写的应用程序最大的区别就是网络程序有两个部分组成:客户端和服务器端,两者可通过局域网连接,进行数据的接受与发送。
服务器端:被动的等待外面的程序来和自己通讯的程序,称为服务端程序。客户端:主动的与外界的程序进行通信的程序,称为客户端程序。举一个简单的例子,服务器端可以类比为一家餐厅,客服端类比为顾客。 “服务器餐厅”准备好菜单(这里的菜单就是服务端程序中服务器的设置:ip地址…)打开门来等待“客户端顾客”的“帮衬(连接)”,同时“客户端顾客”也会准备好钱(这里的钱就是客户端程序中客户端的设置:ip地址…)来埋单。 对于“服务器餐厅”与“客户端顾客”,二者都可以选择怎样的顾客进行接待与进入到何种餐厅进行消费(在客户端和服务器端都可以通过设置,选择连接特定的网段或类型)。
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,其中可靠的特点使得他在一些重要的数据通信中使用的很广泛。
提问:如何使用这种协议呢? 回答:通过类比文件读写来理解:
对于文件读写,其最终要的三个要素就是:文件描述符(源)、存储读写信息的变量(终)、读写信息的大小或长度,对于网络程序,(源)就是客户端与服务端的IP地址或Socket套接字、(终)就是客户端与服务端的IP地址或Socket套接字、发送或接收信息的内容与大小。 服务器端: socket-->bind-->listen-->accept 客户端: socket-->connect对于Internet 的传输层有两个主要协议,除了TCP协议,就是现在介绍的UDP协议。UDP协议为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法,其中报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。 理解这种传输协议,同样可以与文件读写来进行类比:
对于文件读写,其最终要的三个要素就是:文件描述符(源)、存储读写信息的变量(终)、读写信息的大小或长度,对于网络程序,(源)就是客户端与服务端的IP地址或Socket套接字、(终)就是客户端与服务端的IP地址或Socket套接字、发送或接收信息的内容与大小。 服务器端: socket-->bind 客户端: socket-->connect在之前的基础上新增一个debug调试部分,由debug_manager.c管理者负责管理下层的stdout标准输出和netprint网络打印输出。
对于上述完成主要功能的6个部分:encoding编码部分、fonts获取字体点阵部分、display显示部分、input输入部分、draw协调部分、debug调试部分
encoding编码部分:去文件中获得编码信息,对于每个文件,保存的时候,系统对自动或手动的根据编码规范,对文件的信息进行编码:如保存为ASCII、GBK、UTF-8、UTF16LE、UTF16BE;fonts获取字体点阵部分:根据获得的编码信息得到字体数据(LCD上表示为点阵);display显示部分:把字体数据(点阵)显示在LCD上;input输入部分:管理不同的输入方式,定制不同的输入方式所实现的控制,满足多种控制场合;draw协调部分 :组织各个模块部分进行合作,实现电子书的显示、翻页功能等。debug调试部分:设置打印等级和打印通道,通过打印等级来控制程序打印的调试信息、错误信息、警告信息等,通过打印通道设置来控制程序打印的输出的流向是标准输出还是网络打印输出。由于整个分为如下3部分:顶层目录的Makefile、顶层目录的Makefile.build、各级子目录的Makefile。
所以对于新添加的debug输入部分需要进行如下修改:其目录下也需要添加该目录下的Makefile,对于顶层目录的Makeifle也需要添加obj-y += /debug,具体的在下面会具体介绍。
而对于顶层的Makefile.build,不需要进行修改,因为它是一个比较通用的文件,根据顶层目录的Makefile信息进入到各个子目录下进行预处理、编译、汇编,最后每个子目录与顶层得到的built-in.o进行链接,最后得到可执行文件show_file.
在这个文件:
定义一个结构体变量,拓展文件可以根据自己的情况:分配结构体、设置结构体、注册结构体;定义一些函数,供外部文件调用。 #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 */在这个函数中,为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); }在这里采用的是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); }执行make,得到可执行文件show_file
由于使用到触摸屏,需要调用tslib库来进行校准
执行./show_file -l,显示出当前支持的设备,发现提示一下错误信息:这里显示绑定地址已在使用中 通过执行netstat -nap,发现开发板原来有一个UDP协议的端口 杀掉进程后kill -9 874,重新执行./show_file -l成功
客户端输出的提示信息
服务器端输出的打印信息
可以看到此时有五个线程:主线程、标准输入子线程、触摸屏输入子线程、标准输出子线程、网络打印输出子线程
客户端设置网络打印无效,标准串口输出有效:
客户端设置网络打印无效,标准串口输出无效:
客户端设置网络打印有,标准串口输出无效: