靈修分享:亞略巴古的演講

亞略巴古的演說是使徒行傳一篇非常著名的講道。基督徒往往用這段經文來說明上帝的屬性,同時也會參考這段經文作為和外邦人互動的一個參考方式。最近因為團契查經查到這一段,重新思想後看到了以前沒有考慮過的面向,在這邊記錄一下。

徒17:16-34保羅在雅典等候他們的時候,看見滿城都是偶像,就心裏着急;於是在會堂裏與猶太人和虔敬的人,並每日在市上所遇見的人,辯論。還有伊壁鳩魯和斯多亞兩門的學士,與他爭論。有的說:「這胡言亂語的要說甚麼?」有的說:「他似乎是傳說外邦鬼神的。」這話是因保羅傳講耶穌與復活的道。他們就把他帶到亞略‧巴古,說:「你所講的這新道,我們也可以知道嗎?因為你有些奇怪的事傳到我們耳中,我們願意知道這些事是甚麼意思。」(雅典人和住在那裏的客人都不顧別的事,只將新聞說說聽聽。)
保羅站在亞略‧巴古當中,說:「眾位雅典人哪,我看你們凡事很敬畏鬼神。我遊行的時候,觀看你們所敬拜的,遇見一座壇,上面寫着『未識之神』。你們所不認識而敬拜的,我現在告訴你們。創造宇宙和其中萬物的神,既是天地的主,就不住人手所造的殿,也不用人手服事,好像缺少甚麼;自己倒將生命、氣息、萬物,賜給萬人。他從一本本:有古卷是血脈造出萬族的人,住在全地上,並且預先定準他們的年限和所住的疆界,要叫他們尋求神,或者可以揣摩而得,其實他離我們各人不遠;我們生活、動作、存留,都在乎他。就如你們作詩的,有人說:『我們也是他所生的。』我們既是神所生的,就不當以為神的神性像人用手藝、心思所雕刻的金、銀、石。世人蒙昧無知的時候,神並不監察,如今卻吩咐各處的人都要悔改。因為他已經定了日子,要藉着他所設立的人按公義審判天下,並且叫他從死裏復活,給萬人作可信的憑據。」
眾人聽見從死裏復活的話,就有譏誚他的;又有人說:「我們再聽你講這個吧!」於是保羅從他們當中出去了。 但有幾個人貼近他,信了主,其中有亞略‧巴古的官丟尼修,並一個婦人,名叫大馬哩,還有別人一同信從。

對基督徒來說,保羅這篇講道講的真好,不但講出了上帝的超越性(像是不住人手所造的殿、也不是金銀石的彫刻),也帶出了耶穌基督復活的大能。但最近我才注意到眾人的反應:「眾人聽見從死裏復活的話,就有譏誚他的;又有人說:「我們再聽你講這個吧!」」不曉得你看到這一段有什麼感想?可能我是玻璃心吧,如果我是保羅,我大概會非常非常的難過,看起來似乎沒有人理會這個福音、這篇講道。這篇被基督…

TCP 和 UDP 共用同一個 Port?

這幾天在做網路實驗,利用 iperf3 來觀測流量的時候,發現一個神奇的地方,那就是 iperf3 在設定 server 端的時候,不用指定是 tcp 還是 udp,這跟 iperf 第2版以前是不一樣的。

iperf2:

SYNOPSIS
      iperf -s [ options ]
      iperf -u -s [ options ]
GENERAL OPTIONS
      -u, --udp
           use UDP rather than TCP

iperf3:

SYNOPSIS
       iperf3 -s [ options ]
       iperf3 -c server [ options ]
CLIENT SPECIFIC OPTIONS
      -u, --udp
           use UDP rather than TCP

這代表 TCP 和 UDP 的 Socket 是可以同時使用同一個 Port 的。其實我一直以為是不行的(這就叫作孤陋寡聞 ...)。為了實驗起見,我自己寫了一個小小的 Echo Server,在指定的 Port 上同時監聽 TCP 和 UDP,來看看是否能夠運作(Client的程式就不附在上面了)。
 
#include <stdio.h>          // for printf() and fprintf() 
#include <sys/socket.h>     // for socket(), bind(), and connect() 
#include <arpa/inet.h>      // for sockaddr_in and inet_ntoa() 
#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>

#define MAXPENDING 5        // Maximum outstanding connection requests
#define MAXCLIENT  100      // Maximum client connections
#define RCVBUFSIZE 1024     // Size of receive buffer 

int     CreateTCPServerSocket( unsigned short );
int     CreateUDPServerSocket( unsigned short );
int     AcceptTCPConnection( int );
int     HandleTCPClient( int );
int     HandleUDPClient( int );

int main( int argc, char *argv[] )
{
    int             tcpServSock;     // Socket descriptors for TCP server 
    int             udpServSock;     // Socket descriptors for UDP server 
    long            timeout;         // Timeout value given on command-line 
    
    int cliSock[MAXCLIENT];          // Client Socket Set
    
    int running = 1;                 // 1 if server should be running; 0 otherwise 
    
    unsigned short portNo;           // Actual port number 
    
    int i, j;                        // For loop use
    
    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.

    
    // Test for correct number of arguments
    
    if ( argc != 3 )     
    {
        fprintf( stderr, "Usage:  %s <Timeout (secs.)> <Port> \n", argv[0] );
        fprintf( stderr, "Description: \n\t" );
        fprintf( stderr, "This is an echo server program which runs both TCP and UDP\n\t" );
        fprintf( stderr, "on the same port.\n" );
        exit(1);
    }

    timeout = atol( argv[1] );      // First arg: Timeout 
    portNo = atoi( argv[2] );

    // Create epoll file descriptor.
    // MAXCLIENT + 3 = MAXCLIENT + TCP + UDP +STD_IN.
    epfd = epoll_create( MAXCLIENT + 3 );    

    // Create port socket 
    tcpServSock = CreateTCPServerSocket( portNo );
    udpServSock = CreateUDPServerSocket( portNo );
        
    // Add to the epoll
    ev.data.fd = tcpServSock;
    ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
    epoll_ctl( epfd, EPOLL_CTL_ADD, tcpServSock, &ev );
    ev.data.fd = udpServSock;
    ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
    epoll_ctl( epfd, EPOLL_CTL_ADD, udpServSock, &ev );        
        
    // Add STDIN into the EPOLL set.
    ev.data.fd = STDIN_FILENO;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl( epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev );     
    
    // Initialize the client socket pool
    for( i = 0 ; i < MAXCLIENT ; i++ )
    {
        cliSock[i] = -1;
    }
    
    printf( "Starting server:  Hit return to shutdown\n" );
    while ( running )
    {
        // Wait for events.
        // int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
        // Specifying a timeout of -1 makes epoll_wait() wait indefinitely.
        noEvents = epoll_wait( epfd, events, FD_SETSIZE , ( timeout * 1000 ) );
        
        if ( noEvents <= 0 ) 
        {
            printf("No echo requests for %ld secs...Server still alive\n", timeout);
            continue; 
        }
        
        for( i = 0 ; i < noEvents; i++ )
        {
            if( events[i].events & EPOLLIN && STDIN_FILENO == events[i].data.fd )
            {
                printf("Shutting down server\n");
                getchar();
                running = 0;
                continue;
            }
            else if( ( events[i].events & EPOLLIN ) && 
                     ( events[i].data.fd == tcpServSock ) )
            {
                for( i = 0 ; i < MAXCLIENT ; i++ )
                {
                    if( cliSock[i] < 0 ) 
                    {
                        cliSock[i] = AcceptTCPConnection( tcpServSock );                        
                        
                        // Add the client socket to the epoll fdset. 
                        ev.data.fd = cliSock[i];
                        ev.events = EPOLLIN | EPOLLET;
                        epoll_ctl( epfd, EPOLL_CTL_ADD, cliSock[i], &ev ); 
                        
                        i = MAXCLIENT;
                    }
                }
            }
            else if( ( events[i].events & EPOLLIN ) && 
                     ( events[i].data.fd == udpServSock ) )
            {
                HandleUDPClient( events[i].data.fd );
            }
            else if ( events[i].events & EPOLLIN )
            {
                if( HandleTCPClient( events[i].data.fd ) == 0 )
                {
                    printf( "Connection %d Shudown.\n", events[i].data.fd );
                    
                    // We do not need to del fd from the epfd set.
                    // Close the fd will remove it from epfd automatically.

                    close( events[i].data.fd );

                    for( j = 0 ; j < MAXCLIENT; j++ )
                    {
                        if( cliSock[j] == events[i].data.fd )
                        {
                            cliSock[j] = -1;
                            j = MAXCLIENT;
                        }
                    }
                }
                                
            }           
        }
    }

    // Close sockets 
    close( tcpServSock );
    close( udpServSock );
    
    for ( i = 0; i < MAXCLIENT; i++ )
    {
        if( cliSock[i] > 0 )
        {
            close( cliSock[i] );
        }
    }
    
    // Free list of sockets 
    close( epfd );

    return 0;
}

int CreateTCPServerSocket(unsigned short port)
{
    int sock;                        // socket to create 
    struct sockaddr_in echoServAddr; // Local address 

    // Create socket for incoming connections 
    if ( ( sock = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ) ) < 0 )
    {
        perror( "socket() failed" );
        exit(1);
    }
      
    // Construct local address structure 
    memset( &echoServAddr, 0, sizeof( echoServAddr ) );     // Zero out structure 
    echoServAddr.sin_family = AF_INET;                      // Internet address family 
    echoServAddr.sin_addr.s_addr = htonl( INADDR_ANY );     // Any incoming interface 
    echoServAddr.sin_port = htons( port );                  // Local port 

    // Bind to the local address 
    if ( bind(sock, (struct sockaddr *) &echoServAddr, sizeof( echoServAddr ) ) < 0 )
    {
        perror( "bind() failed" );
        exit(1);
    }
    
    // Mark the socket so it will listen for incoming connections 
    if ( listen( sock, MAXPENDING ) < 0 )
    {
        perror( "listen() failed" );
        exit(1);
    }
    
    return sock;
}

int CreateUDPServerSocket(unsigned short port)
{
    int sock;                        // socket to create 
    struct sockaddr_in echoServAddr; // Local address 

    // Create socket for incoming connections 
    if ( ( sock = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) < 0 )
    {
        perror( "socket() failed" );
        exit(1);
    }
      
    // Construct local address structure 
    memset( &echoServAddr, 0, sizeof( echoServAddr ) );     // Zero out structure 
    echoServAddr.sin_family = AF_INET;                      // Internet address family 
    echoServAddr.sin_addr.s_addr = htonl( INADDR_ANY );     // Any incoming interface 
    echoServAddr.sin_port = htons( port );                  // Local port 

    // Bind to the local address 
    if ( bind(sock, (struct sockaddr *) &echoServAddr, sizeof( echoServAddr ) ) < 0 )
    {
        perror( "bind() failed" );
        exit(1);
    }    
    
    return sock;
}

int AcceptTCPConnection( int servSock )
{
    int                 clntSock;     // Socket descriptor for client 
    struct sockaddr_in  echoClntAddr; // Client address 
    unsigned int        clntLen;      // Length of client address data structure 

    // Set the size of the in-out parameter 
    clntLen = sizeof( echoClntAddr );
    
    // Wait for a client to connect 
    if ( ( clntSock = accept( servSock, (struct sockaddr *) &echoClntAddr, &clntLen ) ) < 0 )
    {
        perror("accept() failed");
        exit(1);
    }
    
    // clntSock is connected to a client! 
    
    printf( "Handling client %s:%d (%d)\n", inet_ntoa( echoClntAddr.sin_addr ), 
            ntohs( echoClntAddr.sin_port ), clntSock );

    return clntSock;
}

int HandleTCPClient( int clntSocket )
{
    char    echoBuffer[RCVBUFSIZE];        // Buffer for echo string 
    int     recvMsgSize;                   // Size of received message 
    
    bzero( echoBuffer, RCVBUFSIZE );
    
    // Receive message from client 
    if ( ( recvMsgSize = recv( clntSocket, echoBuffer, RCVBUFSIZE, 0 ) ) < 0 )
    {
        perror("recv() failed");
        exit(1);
    }

    // Send received string and receive again until end of transmission 
    if ( recvMsgSize > 0 )      // zero indicates end of transmission 
    {
        printf( "Recv(%d): %s\n", recvMsgSize, echoBuffer );
        
        // Echo message back to client 
        if ( send( clntSocket, echoBuffer, recvMsgSize, 0) != recvMsgSize )
        {
            perror( "send() failed" );
            exit(1);
        }
    }
    
    return recvMsgSize;
}

int HandleUDPClient( int clntSocket )
{
    char    echoBuffer[RCVBUFSIZE];        // Buffer for echo string 
    int     recvMsgSize;                   // Size of received message
    int     len = sizeof( struct sockaddr_in );

    struct sockaddr_in echoClntAddr; 
    
    bzero( echoBuffer, RCVBUFSIZE );

    // Receive message from client 
    if ( ( recvMsgSize = recvfrom( clntSocket, echoBuffer, RCVBUFSIZE, 0,
                                   ( struct sockaddr * )&echoClntAddr, &len ) ) < 0 )
    {
        perror("recv() failed");
        exit(1);
    }

    // print out the address of the sender
    printf( "Got a UDP packet from %s port %d\n",
            inet_ntoa( echoClntAddr.sin_addr ), ntohs( echoClntAddr.sin_port ) );

    // Send received string and receive again until end of transmission 
    if ( recvMsgSize > 0 )      // zero indicates end of transmission 
    {
        printf( "Recv(%d): %s\n", recvMsgSize, echoBuffer );
        
        // Echo message back to client 
        if ( sendto( clntSocket, echoBuffer, recvMsgSize, 0, 
                     ( struct sockaddr * )&echoClntAddr, sizeof( echoClntAddr ) ) != recvMsgSize )
        {
            perror( "send() failed" );
            exit(1);
        }
    }
    
    return recvMsgSize;
}

結論是 TCP 和 UDP 可以同時在一個 Port 上開 Socket。 我沒有去看到底 Kernel 是怎麼實作的,不過猜測 IP_Proto 也被列在 Socket 的 Tuple 判斷之內。有同事提醒我可以看看 /etc/services,也可以觀察到這個現象。好了,所以又學到一件事情了。

留言

這個網誌中的熱門文章

如何將Linux打造成OpenFlow Switch:Openvswitch

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

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