1. 程式人生 > >Linux下IPC機制之Socket通訊總結

Linux下IPC機制之Socket通訊總結

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本地通訊方法,僅供參考.