电子书完善二:多种方式(轮询/select/多线程)实现多输入(终端和触摸屏)

电子书完善二:多种方式[轮询/select/多线程]实现多输入[终端和触摸屏]

一、电子书支持多输入的框架
要想电子书支持多输入,如触摸屏、控制终端等,轻而易举。因为前面分层分离的思想搭建起的框架拓展性很好。
可以很灵活的添加。只需按面向对象编程方式模块化实现即可。即像前面三个子模块的实现一样。框架图如下

添加输入子模块后的电子书框架

二、轮询方式
轮询实现最简单,但也最浪费CPU。
对于输入子模块的实现,概括解释下stdinc.c 和 touchscreen.c 的函数功能:
1)在stdin.c 里,在DeviceInit初始化函数里要设置终端属性,即输入一个字符就立刻返回,而无需回车。
	注意,在DeviceExit函数 恢复回去。
	如果终端没有数据,如何立即返回呢?这里巧妙用了select,将超时时间设置为0,并监听标准输入,
	通过FD_ISSET判断有没事件发生,没有就立刻返回
2)对于 touchscreen.c 文件,在DeviceInit()里调用tslib提供的ts_open打开触摸屏设备,并做一些配置
	(可参考tslib-1.4版本的ts_print.c 文件)
	触摸屏如何读数据呢?也是调用tslib提供的函数,ts_read(xxx, 1 )设置为1为非阻塞,即如果没有数据
	立刻返回,而不是休眠。
3)input_manager.c 中,如何实现轮询呢?因为前面两个的读都是立即返回的,因此在main.c里,
	不断执行读,不就是轮询了嘛?因此,轮询很简单。具体细节看下面源码实现。
#include <input_manager.h>
#include <config.h>
#include <string.h>  // strcmp

/* g_ptInputOprHead 全局链表头指针 */
static PT_InputOpr g_ptInputOprHead;

int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
    PT_InputOpr ptTmp;
    if(!g_ptInputOprHead)
    {
        g_ptInputOprHead = ptInputOpr;
        ptInputOpr->ptNext = NULL;
    }
    else
    {
        ptTmp = g_ptInputOprHead;
        while (ptTmp->ptNext)
        {
            ptTmp = ptTmp->ptNext;
        }
        ptTmp->ptNext = ptInputOpr;
        ptInputOpr->ptNext = NULL;
    }
    ptTmp = NULL;
    return 0;
}
int AllInputDevicesInit(void)
{
 	PT_InputOpr ptTmp = g_ptInputOprHead;
 	int iError = -1;
 	while (ptTmp)
 	{
  		if (0 == ptTmp->DeviceInit())
  		{
   			iError = 0;
  		}
  		ptTmp = ptTmp->ptNext;
 	}
 	return iError;
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
 	/* 把链表中的InputOpr的GetInputEvent都调用一次,一旦有数据即返回 */
 	PT_InputOpr ptTmp = g_ptInputOprHead;
 	while (ptTmp)
 	{
 		if (0 == ptTmp->GetInputEvent(ptInputEvent))
  		{
   			return 0;
  		}
  		ptTmp = ptTmp->ptNext;
 	}
 	return -1;
}
void ShowInputOpr(void)
{
    int i = 0;
    PT_InputOpr ptTmp = g_ptInputOprHead;
    while(ptTmp)
    {
        printf("%02d %s\n", i++, ptTmp->name);
        ptTmp = ptTmp->ptNext;
    }
    ptTmp = NULL;
}
int InputInit(void)
{
    int iError;
    iError = StdinInit();
    iError |= TouchScreenInit();
    return iError;
}
#include <input_manager.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
static int StdinDeviceInit(void);
static int StdinDeviceExit(void);
static int GetStdinInputEvent(PT_InputEvent ptInputEvent);

static T_InputOpr g_tStdinOpr = {
    .name = "stdin",
    .DeviceInit = StdinDeviceInit,
    .DeviceExit = StdinDeviceExit,
    .GetInputEvent = GetStdinInputEvent,
};
static int StdinDeviceInit(void)
{
    struct termios ttystate;
    //get the terminal state
    tcgetattr(STDIN_FILENO, &ttystate);
    //turn off canonical mode
    ttystate.c_lflag &= ~ICANON;
    //minimum of number input read.
    ttystate.c_cc[VMIN] = 1;
    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
    return 0;
}
static int StdinDeviceExit(void)
{
    struct termios ttystate;
    //get the terminal state
    tcgetattr(STDIN_FILENO, &ttystate);
    //turn on canonical mode
    ttystate.c_lflag |= ICANON;
    //set the terminal attributes.
    tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
    return 0;
}
static int GetStdinInputEvent(PT_InputEvent ptInputEvent)
{
    fd_set fds;
    char c;
    struct timeval tTV;
    tTV.tv_sec = 0;
    tTV.tv_usec = 0;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
    select(STDIN_FILENO+1, &fds, NULL, NULL, &tTV);
    
    if(FD_ISSET(STDIN_FILENO, &fds))
    {
        /* гаЪ§Он */
        ptInputEvent->type = INPUT_EVENT_TYPE_STDIN;
        gettimeofday(&ptInputEvent->time, NULL);
        c = fgetc(stdin);
        if(c == 'u')
        {
            ptInputEvent->code = INPUT_EVENT_UP;
        }
        else if(c == 'n')
        {
            ptInputEvent->code = INPUT_EVENT_DOWN;
        }
        else if(c == 'q')
        {
            ptInputEvent->code = INPUT_EVENT_EXIT;
        }
        return 0;
    }
    else
    {
        return -1;
    }
    return 0;
}
int StdinInit(void)
{
    return RegisterInputOpr(&g_tStdinOpr);
}
#include <input_manager.h>
#include <config.h>
#include <draw.h>
#include <stdlib.h>
#include <tslib.h>

static struct tsdev *g_tTSDev;
static int giXres;
static int giYres;
static int TouchScreenDeviceInit(void);
static int TouchScreenDeviceExit(void);
static int GetTouchScreenInputEvent(PT_InputEvent ptInputEvent);

static T_InputOpr g_tTouchScreenOpr = {
    .name = "touchscreen",
    .DeviceInit = TouchScreenDeviceInit,
    .DeviceExit = TouchScreenDeviceExit,
    .GetInputEvent = GetTouchScreenInputEvent,
};
static int TouchScreenDeviceInit(void)
{
    	/* 参考自 tslib-1.4 的 ts_print.c */
	char *tsdevice=NULL;
    	if( (tsdevice = getenv("TSLIB_TSDEVICE")) != NULL ) {
        	g_tTSDev = ts_open(tsdevice, 1);
    	} else {
        	g_tTSDev = ts_open("/dev/event0", 1);
    	}
    	if (!g_tTSDev) {
  		DBG_PRINTF("ts_open error!\n");
  		return -1;
 	}
 	if (ts_config(g_tTSDev)) {
  		DBG_PRINTF("ts_config error!\n");
  		return -1;
 	}
    	if (GetDispResolution(&giXres, &giYres))
 	{
  		return -1;
 	}
    	return 0;
}
static int TouchScreenDeviceExit(void)
{
    return 0;
}
static int isOutOf500ms(struct timeval *ptPreTime, struct timeval *ptNowTime)
{
 	int iPreMs;
 	int iNowMs;
 	iPreMs = ptPreTime->tv_sec * 1000 + ptPreTime->tv_usec / 1000;
 	iNowMs = ptNowTime->tv_sec * 1000 + ptNowTime->tv_usec / 1000;
 	return (iNowMs > iPreMs + 500);
}
static int GetTouchScreenInputEvent(PT_InputEvent ptInputEvent)
{
    struct ts_sample tSamp;
    static struct timeval tPreTime;
    int iRet;
    iRet = ts_read(g_tTSDev, &tSamp, 1);
    if (iRet < 0) {
  	return -1;
    }
    DBG_PRINTF("Before call isOutOf500ms\n");
    if (isOutOf500ms(&tPreTime, &tSamp.tv))
    {
        DBG_PRINTF("isOutOf500ms\n");
        tPreTime = tSamp.tv;
        ptInputEvent->time = tSamp.tv;
        ptInputEvent->type = INPUT_EVENT_TYPE_TOUCHSCREEN;
        if(tSamp.y < giYres/3)
        {
            ptInputEvent->code = INPUT_EVENT_UP;
        }
        else if(tSamp.y > 2*giYres/3)
        {
            ptInputEvent->code = INPUT_EVENT_DOWN;
        }
        else
        {
            ptInputEvent->code = INPUT_EVENT_UNKNOWN;
        }
        return 0;
    }
    else
    {
        return -1;
    }
    return 0;
}
int TouchScreenInit(void)
{
    return RegisterInputOpr(&g_tTouchScreenOpr);
}
三、select机制
何为select机制呢?
简单来说,就是调用select监听多个文件,当其一文件有读/写/异常事情发生或超时,内核会自动唤醒该进程
然后只需判断是那个文件发生了什么事情即可。反之,如果没啥事情或未超时,则进程会睡眠。
因此,相比前面的轮询,大大降低了CPU占用率.

编程思路:
围绕“ int select(int nfds, fd_set *readfds, fd_set *writefds,
              		fd_set *exceptfds, struct timeval *timeout);”  的参数设置。
1> 既然需要用到fd,则把fd当作 InputOpr结构体的成员,然后再stdin.c和touchscreen.c的
	DeviceInit函数初始化各自的fd
	
2> 在input_manager.c里,在AllInputDeviceInit()函数里在DeviceInit初始化后使用FD_SET设置g_rfdset;

3> 在 GetInputEvent函数里调用select开始监听,当有事件发生时,判断哪个fd再调用对应的结构体的
	GetInputEvent并return0

这里并不贴出全部代码,只指出修改的地方
// include/input_manager.h 文件
typedef struct InputOpr{
    char * name;
    int  iFd;   // 新增这一成员
    int (*DeviceInit)(void);
    int (*DeviceExit)(void);
    int (*GetInputEvent)(PT_InputEvent ptInputEvent);
    struct InputOpr * ptNext;
}T_InputOpr, *PT_InputOpr;
// touchscreen.c 文件
static int TouchScreenDeviceInit(void)
{
	/*...*/
	g_tTouchScreenOpr.iFd = ts_fd(g_tTSDev);
	return 0;
}
// stdin.c 文件
static int StdinDeviceInit(void)
{
	/* ... */
	g_tStdinOpr.iFd = STDIN_FILENO; // 新增这一行
	return 0;
}
/* g_ptInputOprHead 全局链表头指针 */
static PT_InputOpr g_ptInputOprHead;
static struct fd_set g_rfdSet;  // 新增
static int g_MaxFd = -1;        // 新增

int AllInputDevicesInit(void)
{
 	PT_InputOpr ptTmp = g_ptInputOprHead;
 	int iError = -1;
    	FD_ZERO(g_rfdSet);
    	
	while (ptTmp)
 	{
  		if (0 == ptTmp->DeviceInit())
  		{
            		FD_SET(ptTmp->iFd, &g_rfdSet);  // 新增
            		if(ptTmp->iFd > g_MaxFd)        // 新增
                		g_MaxFd = ptTmp->iFd;       // 新增
   	    		iError = 0;
  		}
  		ptTmp = ptTmp->ptNext;
 	}
    	g_MaxFd++;
 	return iError;
}

int GetInputEvent(PT_InputEvent ptInputEvent)
{
    int iRet;
    fd_set tRFds;
    PT_InputOpr pTmp = g_ptInputOprHead;
    tRFds = g_rfdSet;
    
    iRet = select(g_MaxFd, &tRFds, NULL, NULL, NULL);
    if(iRet > 0)
    {  
        while(pTmp)
        {
            if(FD_ISSET(pTmp->iFd, &tRFds))
            {
                if(0 == pTmp->GetInputEvent(ptInputEvent))
                    return 0;
            }
            pTmp = pTmp->ptNext;
        }
    }
    return -1;
}
四、多线程
前面的实现都是单线程,要么轮询,要么休眠,只干一件事。
而使用多线程可以充分利用CPU,而不会傻傻的轮询或休眠等待。
我觉得多线程的实现更简单。只需用到一把互斥锁(子线程需要访问临界资源) + 一个条件变量(用于唤醒/休眠主线程)

编程思路如下:
1> 在调用AllInputDeviceInit()时,一旦调用ts或stdin的DeviceInit成功后,则创建子线程。
而它们两个干相同的事情,即尝试获得互斥锁,不能获得就休眠等待。一旦有事件发生,获得锁成功
然后唤醒主线程,最后释放锁
2> 对于主线程,先休眠(通过条件变量),被子线程唤醒后,就返回数据。

具体实现看下面源码
static T_InputEvent g_tInputEvent;
static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;

static void* InputEventThreadFunction(void* pVoid)
{
 	T_InputEvent tInputEvent;
 	int (*GetInputEvent)(PT_InputEvent ptInputEvent);
 	GetInputEvent = (int (*)(PT_InputEvent))pVoid;
 	while (1)
 	{
  		if(0 == GetInputEvent(&tInputEvent))
  		{
   			/* 唤醒主线程, 把tInputEvent的值赋给一个全局变量 */
   			/* 访问临界资源前,先获得互斥量 */
   			pthread_mutex_lock(&g_tMutex);
   			g_tInputEvent = tInputEvent;
   			/*  唤醒主线程 */
   			pthread_cond_signal(&g_tConVar);
   			/* 释放互斥量 */
   			pthread_mutex_unlock(&g_tMutex);
  		}
 	}
 	return NULL;
}
int AllInputDevicesInit(void)
{
 	PT_InputOpr ptTmp = g_ptInputOprHead;
 	int iError = -1;
 	while (ptTmp)
 	{
  		if (0 == ptTmp->DeviceInit())
  		{
   			pthread_create(&ptTmp->tThreadID, NULL, InputEventThreadFunction, 
   					ptTmp->GetInputEvent);
   			iError = 0;
  		}
  		ptTmp = ptTmp->ptNext;
 	}
 	return iError;
}
int GetInputEvent(PT_InputEvent ptInputEvent)
{
 	/* 休眠 */
	pthread_mutex_lock(&g_tMutex);
 	pthread_cond_wait(&g_tConVar, &g_tMutex); 
 	
 	/* 被唤醒后,返回数据 */
 	*ptInputEvent = g_tInputEvent;
 	pthread_mutex_unlock(&g_tMutex);
 	return 0;
}
五、实验方法
实验方法:
a. insmod  s3c_ts.ko
	确定是哪个设备节点对应触摸屏
b. 
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

nfs 30000000 192.168.1.103:/work/nfs_root/uImage_nots  
bootm 30000000

c. 较准
	ts_calibrate   
d. telnetd -l /bin/sh  //启动telnet服务,为了登录进去观察CPU占用率

e. ./show_file -s 24 -d fb -f ./MSYH.TTF ./utf8_novel.txt

f. telnet上开发板执行top命令观察

说明:来源自韦东山老师课堂笔记
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目简介: 采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室! OS:Ubuntu 15.04 IDE:vim gcc make DB:Sqlite 3 Time:2015-12-09 ~ 2012-12-21 项目功能架构: 1. 采用client/server结构; 2. 给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出); 3. 多客户可同时连接服务器进行自己操作; ##服务器端## 1. server.c:服务器端主程序代码文件; 2. config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:服务器端公共函数的实现文件; 4. list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作; 5. register.c:服务器端实现用户注册; 6. login.c:服务器端实现用户登录; 7. chat.c:服务器端实现用户的聊天互动操作; 8. Makefile:服务器端make文件,控制台执行make命令可直接生成可执行文件server ##客户端## 1. client.c:客户端主程序代码文件; 2. config.h:客户端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:客户端公共函数的实现文件; 4. register.c:客户端实现用户注册; 5. login.c:客户端实现用户登录; 6. chat.c:客户端实现用户的聊天互动操作; 7. Makefile:客户端make文件,控制台执行make命令可直接生成可执行文件client;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值