IPC: Unix Domain Socket

要讓系統中的兩個 process 彼此溝通該如何處理?過去常見的守法有 pipeline、shared memory 等,不過其實我一個都沒有用過。我比較喜歡的作法是 socket programming,在兩個 process 間建立 network socket,因為這樣的概念最簡單(我一向很懶)。

問題是,使用 network socket 會造成效率上的浪費,儘管兩個 process 都在同一台機器(localhost)上,但是還是會到 kernel 的 TCP/IP Stack 裏面逛個一圈,這在執行上勢必會有效能的浪費。既然都已經在本機了,那又何必經過網路層的處理呢?今天要介紹的 IPC 技術就是一個解決這問題的方案: Unix Domain Socket。從下面的範例上來看,寫法和 network socket 幾乎一模一樣。所以就不多做介紹了,直接看程式吧。

#include <stdio.h>          // for printf() and fprintf() 
#include <sys/socket.h>     // for socket(), bind(), and connect() 
#include <sys/un.h>   // for struct sockaddr_un
#include <stdlib.h>         // for atoi() and exit() 
#include <string.h>         // for memset() 
#include <unistd.h>         // for close() 
#include <sys/time.h>       // for struct timeval {} 
#include <fcntl.h>          // for fcntl() 

#include <sys/epoll.h>

int main( int argc, char *argv[] )
{
    struct sockaddr_un   unixSocketAddr;   // Unix Domain Socket
    int      unfd;     // Unix Socket FD
    struct sockaddr_un      unixSocketDstAddr;      // Unix Domain Socket
    struct sockaddr_un      unixSocketSrcAddr;      // Unix Domain Socket
    socklen_t     unixSocketAddrLen;  // Socket Length
    
    int                     epfd;                   // EPOLL File Descriptor. 
    struct epoll_event      ev;                     // Used for EPOLL.
    struct epoll_event      events[120];            // Used for EPOLL.
    int                     noEvents;               // EPOLL event number.
    
    int      i, j, k, len, running = 1;
    const int    cBufferSize = 1024;
    char     msg[cBufferSize];
    
    if ( argc < 3 )     
    {
        fprintf( stderr, "Usage:  %s   ...\n", argv[0]);
        exit(1);
    }
    
    // Create epoll file descriptor.
    // Standard Input + Bind Socket
    epfd = epoll_create( 2 );
    
    // Create Unix domain socket + Bind + add to the EPOLL set
    
    memset( &unixSocketAddr, 0, sizeof( unixSocketAddr ) );
    unixSocketAddr.sun_family = AF_UNIX;
    strcpy( unixSocketAddr.sun_path, argv[1] );
    
    if( ( unfd = socket( AF_UNIX, SOCK_DGRAM, 0 ) ) < 0 ) 
    {
        perror("socket error.");
        exit(1);
    }
    
    unlink( argv[1] );
    if ( bind( unfd, (struct sockaddr *)&unixSocketAddr, sizeof( unixSocketAddr ) ) == -1 ) 
    {
        perror("bind error.");
        exit(1);
    }
    
    ev.data.fd = unfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl( epfd, EPOLL_CTL_ADD, unfd, &ev ); 
    
    // Create destination Unix domain socket
    
    memset( &unixSocketDstAddr, 0, sizeof( unixSocketDstAddr ) );
    unixSocketDstAddr.sun_family = AF_UNIX;
    strcpy( unixSocketDstAddr.sun_path, argv[2] );
    
    // Add STDIN into the EPOLL set.
    ev.data.fd = STDIN_FILENO;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl( epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev ); 
    
    printf( "Starting server:  \n" );
    
    while ( running )
    {
        printf( "Input (q to quit): " );
        fflush( stdout ); 
        
        noEvents = epoll_wait( epfd, events, FD_SETSIZE , -1 );
        
        printf( "Get %d events.\n ", noEvents );
        
        for( i = 0 ; i < noEvents; i++ )
        {
            if( events[i].events & EPOLLIN && STDIN_FILENO == events[i].data.fd )
            {
                memset( msg, cBufferSize, 0 );
                fgets( msg, cBufferSize, stdin );
                
                if( strlen( msg ) == 2 && msg[0] == 'q' && msg[1] == '\n' )
                {
                    running = 0;
                    continue;
                }  
                
                len = sendto( unfd, msg, strlen( msg ), 0, ( struct sockaddr * ) &unixSocketDstAddr, sizeof( unixSocketDstAddr ) );
                
                if( len < 0 )
                {
                    perror( "Send Error!!\n" );
                }
            }
            else if( events[i].events & EPOLLIN && unfd == events[i].data.fd )
            {
                memset( msg, cBufferSize, 0 );
                len = recvfrom( unfd, msg, cBufferSize, 0, ( struct sockaddr * ) &unixSocketSrcAddr, &unixSocketAddrLen );
                
                if( len < 0 )
                {
                    perror( "Recv Error!!\n" );
                }
                
                for( j = 0 ; j < len ; j++ )
                {
                    printf( "%02X ", msg[j] );
                    
                    if( j % 16 == 15 )
                    {
                        printf( "\n" );
                    } 
                }
                
                printf( "\n" );
            }
            else
            {
                printf( "Unknown %d!\n", events[i].data.fd );
            }
        }
    }
    
    close( unfd );
    
    return 0;
}
上面的程式應該很好懂,基本上建立 socket 的流程和過去 network socket 的流程一模一樣,除了使用 AF_UNIX 以及 address 上給的是檔案名稱這兩件事情。在程式範例裏面,寫法類似 UDP,但其實還可以使用 connect 以及 send 來處理,因為概念類似,就不另外提供範例了。

留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

如何利用 Wireshark 來監聽 IEEE 802.11 的管理封包

我弟家的新居感恩禮拜分享:善頌善禱