導讀:
網上看了很多篇有關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
編寫完后編譯,把兩個可執行文件導入設備中,先執行服務端,然后再執行客戶端,發現通信正常,服務端發送,客戶端接收,似乎完美?
但是當客戶端強行退出后,服務端進程居然退出了!!!怎么辦,且聽下篇分解。