Linux下IPC机制之Socket通信总结

时间:2022-09-02 16:16:08

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_EVNsocklen_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);//创建本地SOCKETif(server_sockfd < 0){pError("cannot create communication socket!\n");return ;}//set server addr_paramsrv_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 & addrret = bind(server_sockfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)); //绑定SOCKETif(ret == -1){pError("cannot bind server socket!\n");close(server_sockfd);unlink(MY_SOCKET_PATH);return ;}//listen sockfdret = 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);#endifreturn;}

这里:

#define MY_SOCKET_PATH"/var/tmp/mylink.txt"
接收消息时采用单独线程:

//接收消息void *RecvFormClient(void *arg){#ifdef LINUX_EVNMsgInfo_t msg;int s_fd = *(int *)arg;int num;//read and printf sent client infowhile(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);#endifreturn;}

而发送消息时,则可以直接发送:

//发送消息给MyLink进程ssize_t sendMsgToClient(MsgInfo_t *msg){#ifdef LINUX_EVNssize_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 clientsstatic 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){//监听导航socketprintf("NaviSocketInit...\n");if (!listen_socket_navi(hWnd)){printf ("listen navi socket error!\n");return -1;}}

//监听导航socketBOOL 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");#endifreturn 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 _SOCKETif(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                        {                            //...                        }}}#endifreturn 0;}

三种socket本地通信方法,仅供参考.