一个已经串联起来一堆功能的程序,现在想修改其中某一个模块的功能,比如音视频编码在程序已经运行的过程中动态调整码率,没有完善的单元测试接口,着实苦恼,想到一个简单的方式,在现有的代码上嵌入一个local socket 服务,另外写一个小程序connect进去给它发信息,来触发我们实现的功能调用,如此即可手动在线调试。 (这不是gdb等的debug调试,也就是一个进程间通信的应用)
上代码:
个人在android上调试jni库的一个功能调用使用,用ndk编译之后将编译的debug_client可执行文件push到设备运行,发信息给主程序中的server来触发接口调用。
有几处坑:
1.0 该套接字就像 pipe管道一样,需要一个路径创建该文件,(以下代码中的宏定义SOCKET_FILE_PATH)在android系统中,
放/storage/emulated/0/ 目录,不可行,该目录所有的文件都没有执行权限
放 system/ 目录也不行,最后个人是放在了 /data 目录。另外,服务端启动前要把
selinux 关掉,#setenforce 0 不然在accept的时候会报错,参数不合法. 可执行文件debug_client 执行前修改权限chmod 777 debug_client, 也不能在/storage/emulated/0/目录, 不然根本修改不了其权限。
2.0 客户端 scanf, scanf完之后要把stdin 多余的数据清空,不然会影响下一次输入
3.0 Android.mk 需要加 LOCAL_LDFLAGS += -pie -fPIE, 位置无关码,不然在adroid 5.0 以上会提示错误
//DebugOnline.h
#ifndef __Debugonline_h__
#define __Debugonline_h__
namespace android {
class DebugOnline{
public :
DebugOnline();
~DebugOnline();
};
}
#endif
//canok 20210121
//server.cpp 服务端,用来嵌入到原代码
//#define LOG_TAG "DebugOnline"
//#include <utils/Log.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include"DebugOnline.h"
namespace android {
struct s_cmd{
int type;
char c_data;
int i_data0;
int i_data1;
char s_data[128];
};
int readSocket( int socket, char* buffer, unsigned bufferSize,
struct sockaddr *fromAddress){
socklen_t addrlen = sizeof( struct sockaddr);
int bytesRead = recvfrom(socket, buffer, bufferSize, 0,
fromAddress, &addrlen);
if(bufferSize < 0){//erro
printf("err:%d:%s\n",errno,strerror(errno));
}
return bytesRead;
}
int writeSocket(int socket,char* buffer, unsigned bufferSize,
struct sockaddr * destAddress) {
socklen_t addrlen = sizeof( struct sockaddr);
int bytesSent = sendto(socket, (char*)buffer, bufferSize, 0,
destAddress, addrlen);
if (bytesSent != (int)bufferSize) {//erro
printf("writeSocket erro %d/%d \n",bytesSent,bufferSize);
}
return bytesSent;
}
//返回 true表示成功,false,表示没有成功读到所需长度的数据量,比如对方socket已经关闭,返回false
bool loopRead(int fd, char*buf, int size){
int ret =0;
int count =0;
int trytimes = 50;//在读不到指定长度数据,重复读取的次数。
while(count < size){
ret = readSocket(fd,buf+count,size-count,NULL);
printf("readed data ret:%d\n",ret);
if(ret <= 0){
//读出错
printf("erro read \n");
return false;
}
if(!trytimes--){
printf("read times out!!\n");
return false;
}
count+=ret;
};
return true;
}
bool loopWrite(int fd,char *buf, int size){
int ret =0;
int count =0;
int trytimes = 50;//重复的次数。
while(count < size){
ret = writeSocket(fd,buf+count,size-count,NULL);
if(ret <= 0){
//写出错
printf("erro");
return false;
}
if(!trytimes--){
printf("write times out!!");
return false;
}
count+=ret;
};
return true;
}
#define SOCKET_FILE_PATH "/data/DEBUG_SERVER"
void* DebugOnline_thread (void *p){
DebugOnline* debug=(DebugOnline*)p;
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address; /*声明一个UNIX域套接字结构*/
struct sockaddr_un client_address;
int i, bytes;
char ch_send, ch_recv;
unlink (SOCKET_FILE_PATH); /*删除原有server_socket对象*/
/*创建 socket, 通信协议为AF_UNIX, SCK_STREAM 数据方式*/
server_sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
/*配置服务器信息(通信协议)*/
server_address.sun_family = AF_UNIX;
/*配置服务器信息(socket 对象)*/
strcpy (server_address.sun_path, SOCKET_FILE_PATH);
/*配置服务器信息(服务器地址长度)*/
server_len = sizeof (server_address);
/*绑定 socket 对象*/
bind (server_sockfd, (struct sockaddr *)&server_address, server_len);
/*监听网络,队列数为1 只允许一个用户控制*/
listen (server_sockfd, 1);
client_len = sizeof (client_address);
while(1){
printf ("canok Server is waiting for client connect...\n");
/*接受客户端请求; 第2个参数用来存储客户端地址; 第3个参数用来存储客户端地址的大小*/
/*建立(返回)一个到客户端的文件描述符,用以对客户端的读写操作*/
client_sockfd = accept (server_sockfd, (struct sockaddr *)&server_address, (socklen_t *)&client_len);
if (client_sockfd == -1) {
perror ("accept");
printf("err:%d:%s\n",errno,strerror(errno));
return NULL;
}
printf ("canok The server is waiting for client data...\n");
char buf[256]={0};
while(1){//处理当前客户端的请求。
memset(buf,0,sizeof(buf));
if(!loopRead(client_sockfd,buf,sizeof(struct s_cmd))){
//客户端已经关闭
break;
}
struct s_cmd *pCmd = (struct s_cmd*)buf;
printf("type:%d, c_data %c, idata:%d,%d, s_data:%s\n",pCmd->type,pCmd->c_data,pCmd->i_data0,pCmd->i_data1,pCmd->s_data);
if(pCmd->type == 1 ){
//在这里调用要动态调试的接口
printf("get cmd1\n");
}else if(pCmd->type ==2){
printf("get cmd2 ,send result\n");
//可以返回调用后的结果
char*pBuffer = (char*)pCmd;
loopWrite(client_sockfd,pBuffer,sizeof(struct s_cmd));
}
}
close (client_sockfd);
}
close(server_sockfd);
unlink (SOCKET_FILE_PATH);
}
DebugOnline::DebugOnline(){
pthread_t pid;
pthread_create(&pid,NULL,DebugOnline_thread,this);
pthread_detach(pid);
}
DebugOnline::~DebugOnline(){
}
}
using namespace android;
int main(){
DebugOnline * debug = new DebugOnline();
while(1){
usleep(1000);
}
}
//canok 20210121
//cleent.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
struct s_cmd{
int type;
char c_data;
int i_data0;
int i_data1;
char s_data[128];
};
int readSocket( int socket, char* buffer, unsigned bufferSize,
struct sockaddr *fromAddress){
socklen_t addrlen = sizeof( struct sockaddr);
int bytesRead = recvfrom(socket, buffer, bufferSize, 0,
fromAddress, &addrlen);
if(bufferSize < 0)
{//erro
printf("err:%d:%s\n",errno,strerror(errno));
}
return bytesRead;
}
int writeSocket(int socket,char* buffer, unsigned bufferSize,
struct sockaddr * destAddress) {
socklen_t addrlen = sizeof( struct sockaddr);
int bytesSent = sendto(socket, (char*)buffer, bufferSize, 0,
destAddress, addrlen);
if (bytesSent != (int)bufferSize)
{//erro
printf("writeSocket erro %d/%d \n",bytesSent,bufferSize);
}
return bytesSent;
}
//返回 true表示成功,false,表示没有成功读到所需长度的数据量,比如对方socket已经关闭,返回false
bool loopRead(int fd, char*buf, int size){
int ret =0;
int count =0;
int trytimes = 50;//在读不到指定长度数据,重复读取的次数。
while(count < size){
ret = readSocket(fd,buf+count,size-count,NULL);
//printf("readed data ret:%d\n",ret);
if(ret <= 0){
//读出错
printf("erro read \n");
return false;
}
if(!trytimes--){
printf("read times out!!\n");
return false;
}
count+=ret;
};
return true;
}
bool loopWrite(int fd,char *buf, int size){
int ret =0;
int count =0;
int trytimes = 50;//重复的次数。
while(count < size){
ret = writeSocket(fd,buf+count,size-count,NULL);
if(ret <= 0){
//写出错
printf("erro write\n");
return false;
}
if(!trytimes--){
printf("write times out!!\n");
return false;
}
count+=ret;
};
return true;
}
#define SOCKET_FILE_PATH "/data/DEBUG_SERVER"
int main (int argc, char *argv[]){
struct sockaddr_un address;
int sockfd;
int len;
int result;
/*创建socket,AF_UNIX通信协议,SOCK_STREAM数据方式*/
if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror ("socket");
exit (EXIT_FAILURE);
}
address.sun_family = AF_UNIX;
strcpy (address.sun_path, SOCKET_FILE_PATH);
len = sizeof (address);
/*向服务器发送连接请求*/
result = connect (sockfd, (struct sockaddr *)&address, len);
if (result == -1) {
printf ("ensure the server is up SOCEKT_FILE:%s\n",SOCKET_FILE_PATH);
perror ("connect");
exit (EXIT_FAILURE);
}
while(1){
struct s_cmd cmd={0};
char *pBuffer = (char*)&cmd;
printf(">>>>>>>>>>>>>>>>>>input:type,intdata0,sdata\n");
scanf("%d,%d,%s",&cmd.type,&cmd.i_data0,cmd.s_data);
//清空之前的缓冲区,不然 会把上一次残余的当做输入
//setbuf(stdin,NULL);
unsigned char getBuff;
while(getBuff=getchar()!='\n' && getBuff!=EOF);
//printf(">>>>>>>>type:%c data0:%d s_data:%s\n",cmd.type,cmd.i_data0,cmd.s_data);
loopWrite(sockfd,pBuffer,sizeof(struct s_cmd));
if(cmd.type==2){
loopRead(sockfd,pBuffer,sizeof(struct s_cmd));
printf("get result [type:%d,c_data:%c,idata:%d,%d,s_data:%s]\n",cmd.type,cmd.c_data,cmd.i_data0,cmd.i_data1,cmd.s_data);
}
}
close (sockfd);
return 0;
}
#Android.mk 使用ndk编译,可以先建一个jni目录,然后把这几个文件都拷贝到jni目录下,进jni目录执行ndk-build
LOCAL_SRC_FILES := client.cpp
LOCAL_LDFLAGS += -pie -fPIE
include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
LOCAL_MODULE := debug_server
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := server.cpp
LOCAL_LDFLAGS += -pie -fPIE
#LOCAL_SHARED_LIBERAYRS:= libpthread
include $(BUILD_EXECUTABLE)
在android设备上运行,(需要root权限和 setenforce 0)