Linux下IPC机制有很多种,Socket算得上比较广泛的一种,在不使用像D-Bus之类的重量级消息总线之前采用socket作为两个进程之间的通话算得上比较不错的选择,因此它的用途比较广泛.这里稍微做下总结吧.
1:常规用法
//初始化MyLink进程 int initMylinkMsgServer() { #ifdef LINUX_EVN pthread_mutex_init(&my_link_fd_mutex, NULL); static pthread_t tServer; if(pthread_create(&tServer, NULL, mylinkMsgServer,NULL) != 0) { pError("\n initMylinkMsgServer error!\n"); return -1; } #endif return 0; }
//MirrorLink线程 void *mylinkMsgServer(void * arg) { #ifdef LINUX_EVN socklen_t clt_addr_len; int ret; int len; struct sockaddr_un clt_addr; struct sockaddr_un srv_addr; int server_sockfd; //pthread_t rid; int *cfd; server_sockfd = socket(PF_UNIX, SOCK_STREAM, 0); //创建本地SOCKET if(server_sockfd < 0){ pError("cannot create communication socket!\n"); return ; } //set server addr_param srv_addr.sun_family = AF_UNIX; strncpy(srv_addr.sun_path, MY_SOCKET_PATH, sizeof(srv_addr.sun_path) - 1); unlink(MY_SOCKET_PATH); //bind sockfd & addr ret = bind(server_sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); //绑定SOCKET if(ret == -1){ pError("cannot bind server socket!\n"); close(server_sockfd); unlink(MY_SOCKET_PATH); return ; } //listen sockfd ret = listen(server_sockfd, 1); //监听SOCKET事件 pError("\nServer listen !\n"); if(ret == -1){ pError("cannot listen the client connect reques !\n"); //perror("cannot listen the client connect request\n"); close(server_sockfd); unlink(MY_SOCKET_PATH); return ; } len = sizeof(clt_addr); static int old_cli_fd = -1; while(1) { pthread_t rid; new_cli_fd = accept( server_sockfd, ( struct sockaddr * )&( clt_addr ), &len );//接受连接请求 if(new_cli_fd < 0){ new_cli_fd = -1; pError("fail to accpet!\n"); continue; } pError("new connect [%d]\n",new_cli_fd); if(old_cli_fd != -1) { old_cli_fd = -1; } int ret = pthread_create(&rid, NULL, &RecvFormClient, (void *)(&new_cli_fd));//需要针对这个连接创建一接收线程 if(ret != 0) { debug("[%d][%s]\n",ret,strerror(ret)); debug("Client pthread_create error.\n"); } if(pthread_detach(rid)) { debug("Client pthread_detach error.\n"); } old_cli_fd = new_cli_fd; } close(server_sockfd); unlink(MY_SOCKET_PATH); pthread_exit((void *)1); #endif return; }
这里:
#define MY_SOCKET_PATH "/var/tmp/mylink.txt"接收消息时采用单独线程:
//接收消息 void *RecvFormClient(void *arg) { #ifdef LINUX_EVN MsgInfo_t msg; int s_fd = *(int *)arg; int num; //read and printf sent client info while(s_fd > 0){ memset(&msg, 0, sizeof(MsgInfo_t)); num = recv(s_fd, &msg, sizeof(MsgInfo_t), 0); if(-1 == num) { pError("fail to receivel!\n"); close(s_fd); //*s_fd = -1; break; } else if(0 == num) { pError("the connect has been closed!\n"); close(s_fd); //*s_fd = -1; break; } else { printf("Server recv Event: [%d] dataLen:[%d]!\n",msg.MsgType,num); int msgType=msg.MsgType; switch(msgType) { case WM_START: //... break; case xxx: //... break; default: //... break; } } } pthread_exit((void *)1); #endif return; }
而发送消息时,则可以直接发送:
//发送消息给MyLink进程 ssize_t sendMsgToClient(MsgInfo_t *msg) { #ifdef LINUX_EVN ssize_t ret = 0; pthread_mutex_lock(&my_link_fd_mutex); if(-1 != new_cli_fd) { ret = send(new_cli_fd, msg, sizeof(MsgInfo_t), 0); } else { pError("No MyLink\n"); } pthread_mutex_unlock(&my_link_fd_mutex); return ret; #endif }此方法在Linux下的使用得比较普遍.
2 抽象命名法:
上述方法很好,但是存在一个前提,收发双方都必须对做为文件路径的标志必须具有读写权限,但是在Android的中间件下采用上述方法有可能行不通,因此Android比较严格的权限控制很容易造成无法通信,虽然可以通信一些其它方式来解决,但还是不如直接像第一种方式通信来得痛快.下面介绍的这种抽象命名法就是解决这种问题.服务端示例:
hmi_server.h:
#ifndef __HMI_SERVER_H__ #define __HMI_SERVER_H__ #define DEBUG_MODE #define SERVER_NAME "@server_socket" #define EPOLL_SIZE 1024 #define BUF_SIZE 1024 #define EPOLL_RUN_TIMEOUT -1 // Macros - exit in any error (eval < 0) case #define CHK(eval) if(eval < 0){perror("eval"); exit(-1);} // Macros - same as above, but save the result(res) of expression(eval) #define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);} #endif
hmi_server.c:
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <sys/un.h> #include <unistd.h> #include <stdlib.h> #include <stddef.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #include <time.h> #include "hmi_server.h" //int makeAddr(const char* name, struct sockaddr_un* pAddr, socklen_t* pSockLen) //{ // int nameLen = strlen(name); // if (nameLen >= (int) sizeof(pAddr->sun_path) -1) /* too long? */ // return -1; // pAddr->sun_path[0] = '\0'; /* abstract namespace */ // strcpy(pAddr->sun_path+1, name); // pAddr->sun_family = AF_UNIX; // *pSockLen = 1 + nameLen + offsetof(struct sockaddr_un, sun_path); // return 0; //} static int setnonblocking(int sockfd) { CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK)); return 0; } // *** Handle incoming message from clients static int handle_message(int client,struct epoll_event *ev) { char buf[BUF_SIZE], message[BUF_SIZE]; int len; bzero(buf, BUF_SIZE); bzero(message, BUF_SIZE); if(ev->events&EPOLLERR || ev->events&EPOLLHUP) { printf("Client with fd: %d closed! \n", client); CHK(close(client)); return 0; } #ifdef DEBUG_MODE printf("Try to read from fd(%d)\n", client); #endif CHK2(len,recv(client, buf, BUF_SIZE, 0)); // zero size of len mean the client closed connection if(len == 0) { CHK(close(client)); #ifdef DEBUG_MODE printf("Client with fd: %d closed! \n", client); #endif } else { buf[len] ='\0'; printf("message:%s\n", buf); } return 0; } int main() { int listener, client_sockfd; socklen_t server_len, client_len; struct sockaddr_un server_addr; struct sockaddr_un client_addr; static struct epoll_event ev, events[EPOLL_SIZE]; ev.events = EPOLLIN | EPOLLET|EPOLLERR|EPOLLHUP; char message[BUF_SIZE]; int epfd; clock_t tStart; int client, res, epoll_events_count; //delete the old server socket //unlink("server_socket"); //create socket CHK2(listener, socket(AF_UNIX, SOCK_STREAM, 0)); setnonblocking(listener); server_addr.sun_family = AF_UNIX; strcpy(server_addr.sun_path, SERVER_NAME); server_addr.sun_path[0]=0; server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path); //makeAddr("server_socket", &server_addr, &server_len); CHK(bind(listener, (struct sockaddr *)&server_addr, server_len)); CHK(listen(listener, 5)); CHK2(epfd,epoll_create(EPOLL_SIZE)); ev.data.fd = listener; CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev)); while(1) { int i; CHK2(epoll_events_count,epoll_wait(epfd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT)); tStart = clock(); for(i = 0; i < epoll_events_count ; i++) { if(events[i].data.fd == listener) { CHK2(client,accept(listener, (struct sockaddr *) &client_addr, &client_len)); setnonblocking(client); ev.data.fd = client; CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev)); //clients_list.push_back(client); bzero(message, BUF_SIZE); res = sprintf(message, "my test", client); CHK2(res, send(client, message, BUF_SIZE, 0)); } else { CHK2(res,handle_message(events[i].data.fd,&events[i])); } } printf("Statistics: %d events handled at: %.2f second(s)\n", epoll_events_count, (double)(clock() - tStart)/CLOCKS_PER_SEC); } printf("hmi_server stop\n"); close(listener); close(epfd); return 0; }
客户端示例代码:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <pthread.h>
#define SERVER_NAME "@server_socket" //@为占位符
#define EPOLL_SIZE 1024
#define BUF_SIZE 1024
#define EPOLL_RUN_TIMEOUT -1
#define CLIENT_RECORD_MAX 5
#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}
#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}
static int setnonblocking(int sockfd)
{
CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));
return 0;
}
static int listener =-1;
static void *send_thread(void *param)
{
char send_buf[1024];
while(1)
{
printf("[client] input content to send:");
scanf("%s",send_buf);
strncat(send_buf,"\r\n",sizeof(send_buf));
if(!strcmp(send_buf,"exit\r\n"))
{
exit(1);
}
if(listener >1)
{
write(listener, send_buf, strlen(send_buf));
}
else
{
printf("[client] server already close!\r\n");
}
}
}
int main()
{
socklen_t len;
struct sockaddr_un address;
int result;
int epoll_events_count;
int epfd;
static struct epoll_event ev, events[EPOLL_SIZE];
ev.events = EPOLLIN | EPOLLET|EPOLLERR|EPOLLHUP;
CHK2(listener, socket(AF_UNIX, SOCK_STREAM, 0));
setnonblocking(listener); //设置为非阻塞线程
address.sun_family = AF_UNIX;
strcpy(address.sun_path, SERVER_NAME);
address.sun_path[0]=0;
len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);
CHK(connect(listener, (struct sockaddr*)&address, len));
CHK2(epfd,epoll_create(EPOLL_SIZE)); //创建一epoll来监听socket事件
ev.data.fd = listener;
CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev));
{
pthread_t mid;
int ret;
ret = pthread_create(&mid,NULL,send_thread,NULL);
if(ret != 0){
printf("[client] can't creat hmi_int %s\n",strerror(ret));
exit(1);
}
}
char recv_buf[1024];
int nread =0;
int i,res;
while(1)
{
CHK2(epoll_events_count,epoll_wait(epfd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT));
for(i = 0; i < epoll_events_count ; i++)
{
if(events[i].data.fd == listener)
{
// if(ev.events&EPOLLHUP)
// {
// printf("[client] server closed1!: %d \n", listener);
// CHK(close(listener));
// listener =-1;
// return 0;
// }
nread =recv(listener, recv_buf, sizeof(recv_buf), 0);
if(nread <= 0)
{
printf("[client] server closed2!: %d,nread=%d\n", listener,nread);
CHK(close(listener));
listener =-1;
return 0;
}
else
{
recv_buf[nread] ='\0';
printf("[client] recv message:%s\n", recv_buf);
}
}
}
}
exit(0);
}
3 MiniGUI的注册socket事件
这里之所以提出MiniGUI,那是因为在MiniGUI下可以将socket通信事件注册为窗口事件,利用窗口的消息队列来处理.
//导航socket初始化 int NaviSocketInit(HWND hWnd) { //监听导航socket printf("NaviSocketInit...\n"); if (!listen_socket_navi(hWnd)) { printf ("listen navi socket error!\n"); return -1; } }
//监听导航socket BOOL listen_socket_navi (HWND hwnd) { #ifdef _SOCKET //创建一个监听socket,这里serv_listen是minigui API接口 if((listen_fd_navi = serv_listen (LISTEN_SOCKET_NAVI))<0) { printf("serv_listen err!\n"); return FALSE; } printf("serv_listen OK\n"); printf ("listen_fd_navi is %d\n",listen_fd_navi); //向miniGUI注册监听socket,RegisterListenFD是minigui接口 if(!RegisterListenFD (listen_fd_navi,POLLIN,hwnd, NULL)) { printf("RegisterListenFD failed\r\n"); return FALSE; } printf("RegisterListenFD OK!\r\n"); #endif return TRUE; }
使用RegisterListenFD函数向MiniGUI系统注册监听Socket事件后, 每当产生 socket事件时,都会产生一个类型为MSG_FDEVENT事件:
case MSG_FDEVENT: NaviFdEventFunc(hWnd, message, wParam,lParam); return 0;
//接收socket数据处理例程 int NaviFdEventFunc(HWND hWnd, int message, WPARAM wParam, LPARAM lParam) { //printf("receive navi socket event!flag_navi:%d,LOWORD(wParam):%d\r\n",flag_navi,LOWORD (wParam)); #ifdef _SOCKET if(LOWORD(wParam) ==listen_fd_navi) /* 来自监听套接字 */ { pid_t pid; uid_t uid; s_conn_fd_navi = serv_accept (listen_fd_navi, &pid, &uid); if (s_conn_fd_navi >= 0) { RegisterListenFD (s_conn_fd_navi, POLLIN, hWnd, NULL); printf("navi new socket connect!:%d\n",s_conn_fd_navi); } } else/* 来自已连接套接字 */ { int ret =0; fd_recv = LOWORD(wParam); memset(socket_str_c,0,sizeof(socket_str_c)); //printf("try to read socket data,fd_recv:%d\r\n",fd_recv); /* 处理来自客户的数据 */ ret =sock_read_t (fd_recv,socket_str_c,sizeof(socket_str_c),0); //printf("sock_read_t ret=%d\n",ret); if(ret>0) { test_char_c[ret]='\0'; printf ("navi socket receive:%s,fd=%d\n",socket_str_c,fd_recv); if (!strcmp (socket_str_c,"Navi_To_HMI\r\n")) //返回主界面 { //... } else if(!strcmp (socket_str_c,"Start_Success\r\n"))//启动成功 { //... } //else if(...) else { //... } } } #endif return 0; }
三种socket本地通信方法,仅供参考.