Android系列
第一章 模拟按键:
远程办公需求之远程遥控
2021-10,最近因为新疆,甘肃等疫情,远程办公需求又一次收到刺激,最近公司在搭建远程办公环境,对于某些产品需要进行遥控控制操作,来实现问题定位与解决。接手了一个模拟遥控的任务
一、远程遥控是什么?
远程遥控实际上是一个控制软件,作为服务端来接收客户端的指令,并且通过调用按键事件来实现对中服务端所在的硬件进行相应遥控操作。服务端通过绑定端口地址,等待客户端的连接,然后接收按键,并解析按键,处理对应按键,调用sendevent事件来操作。
二、实现方案
0.流程图
贴一个流程图
1.通讯实现
控制通过使用socket来实现的,分为客户端和服务端,客户端为一个遥控器程序,python+qt5来实现界面和逻辑,服务端是一个运行程序,在被控制的终端上跑,在云桌面通过客户端模拟的键值按下,将socket消息发送给服务端,服务端根据接收到的消息,截取其中的按键位,取按键位并根据按键表映射到本端处理。
绑定服务:
int Serverbind(void)
{
int Res = -1;
int optVal = 1;
vctlog(LOG_LEVEL_DEBUG_T,"Info:Serverbind enter\n");
memset(&tserverinfo,0,sizeof(tserverinfo));
tserverinfo.socketAddr.sin_family = AF_INET;
tserverinfo.socketAddr.sin_port = htons(SIMULA_KEYBOARD_PORT); //57620
tserverinfo.cSocketId = socket(AF_INET, SOCK_STREAM, 0);
vctlog(LOG_LEVEL_DEBUG_T,"Info:Serverbind socket:%d\n",tserverinfo.cSocketId);
if(tserverinfo.cSocketId < 0)
{
vctlog(LOG_LEVEL_DEBUG_T,"Error:Create serversocket error.\n");
return Res;
}
//套接字设置:可重用端口
if ( setsockopt(tserverinfo.cSocketId, SOL_SOCKET, SO_REUSEADDR, (char*)&optVal, sizeof(optVal)) != 0)
{
vctlog(LOG_LEVEL_DEBUG_T,"Error:setsockopt fail.\n");
goto EXIT;
}
if(bind(tserverinfo.cSocketId, (struct sockaddr *)&tserverinfo.socketAddr, sizeof(tserverinfo.socketAddr)) != 0)
{
vctlog(LOG_LEVEL_DEBUG_T, "Error:bind serversocket error: %s\n",strerror(errno));
goto EXIT;
}
if (listen(tserverinfo.cSocketId, MAX_CONNECT_NUM) != 0)
{
vctlog(LOG_LEVEL_DEBUG_T, "Error:listen serversocket error: %s\n",strerror(errno));
goto EXIT;
}
vctlog(LOG_LEVEL_DEBUG_T, "Info:ServerSocket end, bind port is %d\n",SIMULA_KEYBOARD_PORT);
MaxSockSize = MAX(MaxSockSize, tserverinfo.cSocketId);
return tserverinfo.cSocketId;
EXIT:
close(tserverinfo.cSocketId);
vctlog(LOG_LEVEL_DEBUG_T, "Error:shut Serverbind\n");
return Res;
}
映射表:
{
REMOTE_KEY_FIVE , ANDROID_KEY_FIVE , "'5'" },
{
REMOTE_KEY_SIX , ANDROID_KEY_SIX , "'6'" },
{
REMOTE_KEY_SEVEN , ANDROID_KEY_SEVEN , "'7'" },
{
REMOTE_KEY_EIGHT , ANDROID_KEY_EIGHT , "'8'" },
{
REMOTE_KEY_NINE , ANDROID_KEY_NINE , "'9'" },
循环处理:
int LinkProc(void)
{
int iResult = 0;
int client_len = sizeof (tclientinfo.socketAddr);
memset(&tclientinfo.socketAddr,0,sizeof(tclientinfo.socketAddr));
g_tTimeout.tv_sec = 0; /*设置超时*/
g_tTimeout.tv_usec = 10000;
FD_ZERO(&cliendfd);
if(tclientinfo.cSocketId <= 0)
{
FD_SET(tserverinfo.cSocketId, &cliendfd);
}
if(tclientinfo.cSocketId > 0)
{
FD_SET(tclientinfo.cSocketId, &cliendfd);
}
MaxSockSize = MAX(MaxSockSize, tclientinfo.cSocketId);
iResult = select(MaxSockSize + 1,&cliendfd, NULL, NULL, &g_tTimeout);
if(iResult == 0) /*超时*/
{
/* 客户端没有发消息的时候,一直处于超时状态 */
}
if(iResult < 0) /*出错*/
{
vctlog(LOG_LEVEL_DEBUG_T, "Error:select return err.\n");
return -1;
}
if(iResult > 0)
{
if( FD_ISSET(tserverinfo.cSocketId, &cliendfd) && (tclientinfo.cSocketId <= 0)) /* 处理客户端连接申请 */
{
if((tclientinfo.cSocketId = accept(tserverinfo.cSocketId, (struct sockaddr *)&tclientinfo.socketAddr,&client_len)) > 0)
{
if(!FD_ISSET(tclientinfo.cSocketId, &cliendfd))
{
FD_SET(tclientinfo.cSocketId, &cliendfd);
cunrrentclientnum = 1;
vctlog(LOG_LEVEL_DEBUG_T, "Info:accept tclientinfo.cSocketId:%d,sin_addr:%s,sin_port:%d\n",tclientinfo.cSocketId,inet_ntoa(tclientinfo.socketAddr.sin_addr),tclientinfo.socketAddr.sin_port);
}
}
else
{
vctlog(LOG_LEVEL_DEBUG_T, "Error:accept tclientinfo fail.\n");
cunrrentclientnum = 0;
}
}
if(!FD_ISSET(tserverinfo.cSocketId, &cliendfd))
{
FD_SET(tserverinfo.cSocketId, &cliendfd);
}
if( FD_ISSET(tserverinfo.cSocketId, &cliendfd) && FD_ISSET(tclientinfo.cSocketId, &cliendfd)!=0 && (tclientinfo.cSocketId > 0)) /* 处理客户端按键消息 */
{
if( RecvMsg(tclientinfo.cSocketId) > 0)
{
HandleMsg(KeyMsg);
}
else
{
vctlog(LOG_LEVEL_DEBUG_T, "Error:RecvMsg fail.\n");
cunrrentclientnum = 0;
}
}
}
if(cunrrentclientnum == 0)
{
if(tclientinfo.cSocketId > 0)
{
SendCloseMsgToClient(tclientinfo.cSocketId);
FD_CLR(tclientinfo.cSocketId, &cliendfd);
tclientinfo.cSocketId = 0;
vctlog(LOG_LEVEL_DEBUG_T, "Error:DIDNOT CONNECTED ANY CLIENT.\n");
return -1;
}
}
LinkProcRunTIMES = LinkProcRunTIMES + 1;
if(LinkProc_Run_TIMES_5MIN == LinkProcRunTIMES)
{
keepalive++;
LinkProcRunTIMES = 0;
//vctlog(LOG_LEVEL_DEBUG_T, "Warrn:LinkProc keepalive:%d,\n",keepalive);
}
return 0;
}
2.底层调用
具体实现是调用get/sendevent来实现具体的按键操作,分为控制码按下,控制码抬起,以及一起发送的载体码。
底层操作:
int UpDownLeftRight(int keyvalue)
{
char cmdstr[128] = {
0};
int keyraise = 1;
int i = 0;
if(22 == SdkVersion)
{
snprintf(cmdstr, sizeof(cmdstr), "%s %d %d %d", "sendevent /dev/input/event1",4,4,86);
system(cmdstr);
}
for(i=0;i<4;i++)
{
if((i+1)%2)
{
snprintf(cmdstr, sizeof(cmdstr), "%s %d %d %d", "sendevent /dev/input/event1",1,keyvalue,keyraise--);
}
else
{
snprintf(cmdstr, sizeof(cmdstr), "%s %d %d %d", "sendevent /dev/input/event1",0,0,0);
}
system(cmdstr);
}
return 0;
}
总结
之前调用的是/system/bin/input keyevent,这里发现调用这个事件会出现延迟很大,达到5.600ms,并且当光标陷入输入框的时候,无法通过上下左右键切换光标,很影响使用。