Android native进程间通信实例-socket本地通信篇之——基本通信功能

Android native进程间通信实例-socket本地通信篇之——基本通信功能

导读:

网上看了很多篇有关socket本地通信的示例,很多都是调通服务端和客户端通信功能后就没有下文了,不太实用,真正开发中遇到的问题以及程序稳定性部分没有涉及,代码健壮性不够,本系列(socket本地通信篇)会先直接调通linux本地socket通信,提供最基本的服务端和客户端代码,然后根据实际开发中遇到的问题和优化建议,再提供一版健壮版本的服务端代码。再次明确一点,本篇博文不会搬移太多概念性的东西,比如三次握手协议,还有各个unix系统调用的功能。

1.服务端:

先捋清调用的一个时间顺序,UNIX中服务端的标准API设置如下:

a. socket设置通信域等信息获取一个fd(文件描述符)

b. bind设置相关参数,如获取的fd,sockaddr_un信息等

c. listen标记fd,查看这个fd是否ok

d. accept填入fd和即将连接的客户端的sockaddr_un信息,阻塞着,直到有客户端连接,消除阻塞

(注意:以上具体API信息可以查看man手册,如listen,在ubuntu系统中输入man 2 listen即可查阅)

明确了以上信息后,就可以开始着手写代码了!代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <cutils/sockets.h>
#include <utils/Log.h>
#include <android/log.h>
#include <linux/tcp.h>

#define MYSOCKET_PATH "/tmp/mysocket"

void *client_thread(void *arg)
{
    int clifd = *(int *)arg;char *s = "hello mysocketclient\n";
    while(1)
    {
        usleep(1000000);
        write(clifd,s,strlen(s));//也可以使用send(clifd,s,strlen(s),0);
    }

    return (void *)0;
}


int main(int argc,char **arg)
{
    int servfd;
    int ret;
    
    servfd = socket(AF_LOCAL,SOCK_STREAM,0);
    if(-1 == servfd)
    {
        perror("Can not create socket");
        return -1;
    }
    
    struct sockaddr_un servaddr;
    bzero(&servaddr, sizeof(servaddr));
    strcpy(servaddr.sun_path+1, MYSOCKET_PATH);
    servaddr.sun_family = AF_LOCAL;
    socklen_t addrlen = 1 + strlen(MYSOCKET_PATH) + sizeof(servaddr.sun_family);

    ret = bind(servfd, (struct sockaddr *)&servaddr, addrlen);
    if(-1 == ret)
    {
        perror("bind failed");
        return -1;
    }

    ret = listen(servfd, 100);
    if(-1 == ret)
    {
        perror("listen failed");
        return -1;
    }

    int clifd = -1;;
    pthread_t tid;
    struct sockaddr_un cliaddr;
    while(1)
    {
        printf("wait for next client connect \n");
        memset(&cliaddr,0,sizeof(cliaddr));
        clifd = accept(servfd,(struct sockaddr *)&cliaddr,&addrlen);
        if(clifd == -1)
        {
            printf("accept connect failed\n");
            continue;
        }
        ret = pthread_create(&tid,NULL,client_thread,&clifd);
        if(0 != ret)
        {
            printf("pthread_create failed \n");
        }

        ret = pthread_join(tid, NULL);
        if(0 != ret)
        {
            printf("pthread_join failed \n");
        }
        printf("exit thread\n");
        /*
        ret = pthread_detach(tid);
        if(0 != ret)
        {
            printf("pthread_detach failed \n");
            return -1;
        }*/
    }

    return 0;
}

可知 #define MYSOCKET_PATH “/tmp/mysocket” 定义的字符串用于本地socket通信服务端和客户端关联。

再看看while(1)里面做了什么:

代码运行阻塞在accept这里,等着客户端的连接,如果客户端连接上以后,创建一个线程传入客户端的fd,然后在线程里面处理客户端的信息,这里创建的线程默认一直发送字符串给客户端。

线程的回收可以用到pthread_join或者pthrea_detach,然后可以等待下一个客户端连接,最大连接数在listen的时候有设置。

服务端代码还无法验证,因为客户端代码还没写好。

2.客户端:

客户端代码更好写一点,捋清顺序:

  1. socket设置通信域等信息获取一个fd(文件描述符)

  2. connect根据socket设置的信息来连接服务端,信息中包括那个关键字符串MYSOCKET_PATH

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <cutils/sockets.h>
#include <utils/Log.h>
#include <android/log.h>

#define SOCKET_PATH "/tmp/mysocket"

int main(int argc,char **arg)
{
    int clifd;
    int ret;

    clifd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if(-1 == clifd)
    {
        perror("socket create failed\n");
        return -1;
    }

    struct sockaddr_un cileddr;
    bzero(&cileddr, sizeof(cileddr));
    strcpy(cileddr.sun_path + 1, SOCKET_PATH);
    cileddr.sun_family = AF_LOCAL;
    socklen_t addrlen = sizeof(cileddr.sun_family) + strlen(SOCKET_PATH) + 1;

    ret = connect(clifd, (struct sockaddr *)&cileddr, addrlen);
    if(ret == -1) {
        perror("Connect fail\n");
        return -1;
    }

    char getData[100];    
    while(1)
    {
        bzero(&getData,sizeof(getData));
        ret = read(clifd,&getData,sizeof(getData));
        if(ret > 0)
        {
            printf("rcv message: %s\n", getData);
        }
    }

    return 0;
}

3. 运行程序及思考

android.mk的编写和之前一样,只需要更改cpp文件名以及LOCAL_MODULE名,传送门:https://www.cnblogs.com/songsongman/p/11097196.html

在这里插入图片描述

编写完后编译,把两个可执行文件导入设备中,先执行服务端,然后再执行客户端,发现通信正常,服务端发送,客户端接收,似乎完美?

Android native进程间通信实例-socket本地通信篇之——服务端进程异常退出解决办法

导读:

好难受啊,为什么服务端说挂就挂,明明只是客户端关闭而已,服务端怎么能挂呢?

想想,如果手机上使用一个聊天程序的时候,手机端关闭了聊天程序,那么远端服务器程序总不能说挂就挂吧!所以一定要查明真相。

  1. 跟踪代码查找到进程退出的源头

之前服务端源码:https://www.cnblogs.com/songsongman/p/11187844.html

查阅代码发现,代码主体在while(1)里面,所以最可疑的地方在于accpet,pthread_create, pthread_join和创建的线程client_thread了

明摆着就是client_thread中出了问题,因为accpet,pthread_create, pthread_join中都有根据函数返回值做是否出错的判断,还是认怂好好看看线程做了什么:

void *client_thread(void *arg)
{
    int clifd = *(int *)arg;char *s = "hello mysocketclient\n";

    while(1)
    {
        usleep(1000000);
        write(clifd,s,strlen(s));//send(clifd,s,strlen(s),0);
    }

    return (void *)0;
}

哇!居然使用write的时候没有添加返回值的判断,在ubuntu终端中输入man 2 write,可以看到write出错时候会返回-1;

2.简单完善代码容错机制

添加容错代码后以后看看效果如何,代码如下:

while(1)
{
    usleep(1000000);
    ret = write(clifd,s,strlen(s));//send(clifd,s,strlen(s),0);
    if(ret == -1)
    {
        printf("client thread write failed !\n");
       close(clifd);
        pthread_exit(NULL);
    }
}

执行结果如下:

在这里插入图片描述

过程分析,

  1. 先执行服务端程序,然后运行客户端程序,客户端程序强制退出(通过快捷键ctrl+c),服务端client_thread中write返回-1,线程正常退出。

  2. 这时候服务端程序还阻塞在accpet等待下一次的客户端连接请求,运行新的客户端程序,然后强制退出客户端,发现服务端进程居然直接退出了!

咋办啊!感觉代码没有任何问题了,为啥还会出错,虽然很明确一定是write的时候没能写进客户端导致的进程奔溃,但是却无从下手。

(注意:为了解决这个问题,笔者绞尽脑汁修改,比如添加

shutdown(clifd, SHUT_RDWR);

又或者添加getsockopt来实时获取连接状态

)效果都不佳,无法解决问题。

  1. 添加捕获异常来再次加强容错机制

绞尽脑汁似乎没有什么效果,抓耳挠腮看看吧,好好翻翻书,看看能不能找到灵感。

从网上找到一本和UNIX系统编程有关的书籍《UNIX环境高级编程_第二版中文》,因为android是基于linux开发的操作系统,linux又是从UNIX那边衍射出来的,

所以linux系统编程这块参考这本书特别靠谱。

看到一个和信号有关的章节,确定了要用signal来检测异常,可检测的信号可真多啊!

在这里插入图片描述
图3.1 参考UNIX环境高级编程第二版中文第10章表1

然后不小心看到这点
在这里插入图片描述

好吧,灵感来了,开始写代码,直接添加头文件

#include <signal.h>

然后再main函数中添加signal(SIGPIPE, SIG_IGN);

运行服务端,再运行客户端,不管客户端怎么退出重启,服务端都不受影响了。

任务完成!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Native的 `react-native-android-keyboard-adjust` 可以用来调整安卓键盘的行为。在0.63版本中,使用方法如下: 1. 安装 使用以下命令安装: ``` npm install react-native-android-keyboard-adjust ``` 2. 链接原生代码 使用以下命令来链接原生代码: ``` npx react-native link react-native-android-keyboard-adjust ``` 或者手动链接,按照以下步骤: (1) 在 `android/settings.gradle` 文件中添加以下代码: ``` include ':react-native-android-keyboard-adjust' project(':react-native-android-keyboard-adjust').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-android-keyboard-adjust/android') ``` (2) 在 `android/app/build.gradle` 文件中添加以下代码: ``` dependencies { // ... implementation project(':react-native-android-keyboard-adjust') } ``` (3) 在 `MainApplication.java` 文件中导入以下代码: ``` import com.reactnativeandroidkeyboardadjust.ReactNativeAndroidKeyboardAdjustPackage; ``` (4) 在 `MainApplication.java` 文件的 `getPackages()` 方法中添加以下代码: ``` new ReactNativeAndroidKeyboardAdjustPackage() ``` 3. 使用 在需要调整键盘行为的组件上,添加 `androidKeyboardAdjust` 属性,值可以为以下三种: - `none`: 不调整键盘行为 - `pan`: 键盘出现时,组件会向上滚动,以避免被键盘遮挡 - `resize`: 键盘出现时,组件会自动调整大小,以避免被键盘遮挡 例如: ```jsx <TextInput androidKeyboardAdjust="pan" // ... /> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值