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.客户端:
客户端代码更好写一点,捋清顺序:
-
socket设置通信域等信息获取一个fd(文件描述符)
-
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本地通信篇之——服务端进程异常退出解决办法
导读:
好难受啊,为什么服务端说挂就挂,明明只是客户端关闭而已,服务端怎么能挂呢?
想想,如果手机上使用一个聊天程序的时候,手机端关闭了聊天程序,那么远端服务器程序总不能说挂就挂吧!所以一定要查明真相。
- 跟踪代码查找到进程退出的源头
之前服务端源码: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);
}
}
执行结果如下:
过程分析,
-
先执行服务端程序,然后运行客户端程序,客户端程序强制退出(通过快捷键ctrl+c),服务端client_thread中write返回-1,线程正常退出。
-
这时候服务端程序还阻塞在accpet等待下一次的客户端连接请求,运行新的客户端程序,然后强制退出客户端,发现服务端进程居然直接退出了!
咋办啊!感觉代码没有任何问题了,为啥还会出错,虽然很明确一定是write的时候没能写进客户端导致的进程奔溃,但是却无从下手。
(注意:为了解决这个问题,笔者绞尽脑汁修改,比如添加
shutdown(clifd, SHUT_RDWR);
又或者添加getsockopt来实时获取连接状态
)效果都不佳,无法解决问题。
- 添加捕获异常来再次加强容错机制
绞尽脑汁似乎没有什么效果,抓耳挠腮看看吧,好好翻翻书,看看能不能找到灵感。
从网上找到一本和UNIX系统编程有关的书籍《UNIX环境高级编程_第二版中文》,因为android是基于linux开发的操作系统,linux又是从UNIX那边衍射出来的,
所以linux系统编程这块参考这本书特别靠谱。
看到一个和信号有关的章节,确定了要用signal来检测异常,可检测的信号可真多啊!
图3.1 参考UNIX环境高级编程第二版中文第10章表1
然后不小心看到这点
好吧,灵感来了,开始写代码,直接添加头文件
#include <signal.h>
然后再main函数中添加signal(SIGPIPE, SIG_IGN);
运行服务端,再运行客户端,不管客户端怎么退出重启,服务端都不受影响了。
任务完成!