Iperf 源代碼分析(四)

Socket 類
    Socket的定義和實現分別在文件Socket.hpp和 Socket.cpp中。它的主要功能是封裝了socket文件描述符、此socket對應的端口號,以及socket接口中的listen, accept, connect和close等函數,爲用戶提供了一個簡單易用而又統一的接口。同時 做爲其餘派生類的基類。
    Socket類的定義以下:
服務器

#ifndef SOCKET_H
#define SOCKET_H

#include "headers.h"
#include "SocketAddr.hpp"

/* ------------------------------------------------------------------- */
class Socket {
public:
    // stores server port and TCP/UDP mode
    Socket( unsigned short inPort, bool inUDP = false );

    // destructor
    virtual ~Socket();

protected:
    // get local address
    SocketAddr getLocalAddress( void );

    // get remote address
    SocketAddr getRemoteAddress( void );

    // server bind and listen
    void Listen( const char *inLocalhost = NULL, bool isIPv6 = false );

    // server accept
    int Accept( void );

    // client connect
    void Connect( const char *inHostname, const char *inLocalhost = NULL );

    // close the socket
    void Close( void );

    // to put setsockopt calls before the listen() and connect() calls
    virtual void SetSocketOptions( void ) {
    }

    // join the multicast group
    void McastJoin( SocketAddr &inAddr );

    // set the multicast ttl
    void McastSetTTL( int val, SocketAddr &inAddr );

    int   mSock;             // socket file descriptor (sockfd)
    unsigned short mPort;    // port to listen to
    bool  mUDP;              // true for UDP, false for TCP

}; // end class Socket

#endif // SOCKET_H

    Socket類主要提供了四個函數:Listen,Accept,Connect和Close。getLocalAddress和GetRemoteAddress的做用分別是得到socket本端的地址和對端的地址,兩個函數均返回一個SocketAddr實例。SetSocketOptions的做用是設置socket的屬性,它是一個虛函數,所以不一樣的socket的派生類在實現此函數時會執行不一樣的操做。下面重點看一下Socket類的幾個函數的實現。網絡

getLoaclAddress和GetRemoteAddress函數dom

SocketAddr Socket::getLocalAddress( void ) {
    iperf_sockaddr sock; 
    Socklen_t len = sizeof(sock);
    int rc = getsockname( mSock, (struct sockaddr*) &sock, &len );
    FAIL_errno( rc == SOCKET_ERROR, "getsockname" );

    return SocketAddr( (struct sockaddr*) &sock, len );
}

// get remote address
SocketAddr Socket::getRemoteAddress( void ) {
    iperf_sockaddr peer; 

    Socklen_t len = sizeof(peer);
    int rc = getpeername( mSock, (struct sockaddr*) &peer, &len );
    FAIL_errno( rc == SOCKET_ERROR, "getpeername" );

    return SocketAddr( (struct sockaddr*) &peer, len );
}

getsockname和getRemote函數都是系統提供的,所以能夠直接使用!
socket

Listen 函數函數

/* -------------------------------------------------------------------
 * Setup a socket listening on a port.
 * For TCP, this calls bind() and listen().
 * For UDP, this just calls bind().
 * If inLocalhost is not null, bind to that address rather than the
 * wildcard server address, specifying what incoming interface to
 * accept connections on.
 * ------------------------------------------------------------------- */

void Socket::Listen( const char *inLocalhost, bool isIPv6 ) {
    int rc;

    //Function setHostname() transfer the Hostname(ASCII) to Hostname(binary),
    //Socket building. 
    SocketAddr serverAddr( inLocalhost, mPort, isIPv6 );

    // create an internet TCP socket
    int type = (mUDP  ?  SOCK_DGRAM  :  SOCK_STREAM);
    int domain = (serverAddr.isIPv6() ? 
#ifdef IPV6
                  AF_INET6
#else
                  AF_INET
#endif
                  : AF_INET);

#ifdef DBG_MJZ

    // DBG MJZ
    fprintf(stderr, "inLocalhost=%s domain=%d\n", inLocalhost, domain);
#endif /* DBG_MJZ */

#ifdef WIN32
    if ( mUDP  &&  serverAddr.isMulticast() ) {
        mSock = WSASocket( domain, type, 0, 0, 0, WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF );
        FAIL_errno( mSock == INVALID_SOCKET, "socket" );

    } else
#endif
    {
        mSock = socket( domain, type, 0 );
        FAIL_errno( mSock == INVALID_SOCKET, "socket" );
    } 

    SetSocketOptions();

    // reuse the address, so we can run if a former server was killed off
    int boolean = 1;
    Socklen_t len = sizeof(boolean);
    // this (char*) cast is for old headers that don't use (void*)
    setsockopt( mSock, SOL_SOCKET, SO_REUSEADDR, (char*) &boolean, len );

#ifdef DBG_MJZ
    {
        // DBG MJZ
        struct sockaddr *sa = serverAddr.get_sockaddr();
        int len = serverAddr.get_sizeof_sockaddr();
        fprintf(stderr, "len: %d salen: %d fam: %d addr chars %2x %2x %2x %2x ...\n",
                len, sa->sa_len, sa->sa_family, sa->sa_data[0], sa->sa_data[1],
                sa->sa_data[2], sa->sa_data[3]);

    }
#endif /* DBG_MJZ */
    // bind socket to server address
#ifdef WIN32
    if ( serverAddr.isMulticast() ) {
        rc = WSAJoinLeaf( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_sockaddr(),0,0,0,0,JL_BOTH);
        FAIL_errno( rc == SOCKET_ERROR, "bind" );
    } else
#endif
    {
        rc = bind( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_sockaddr());
        FAIL_errno( rc == SOCKET_ERROR, "bind" );
    }
    // listen for connections (TCP only).
    // default backlog traditionally 5
    if ( ! mUDP ) {
        rc = listen( mSock, 5 );
        FAIL_errno( rc == SOCKET_ERROR, "listen" );
    }

#ifndef WIN32
    // if multicast, join the group
    if ( mUDP  &&  serverAddr.isMulticast() ) {
        McastJoin( serverAddr );
    }
#endif
} // end Listen

    首先構造一個包含本地服務器地址結構的 SocketAddr實例,inLocalhost是本地IP地址(點分十進制字符串或URL,後者在建立SocketAddr實例是完成地址解析),mPort是Socket構造函數中設置的端口。再經過socket系統調用建立一個socket。SetSocketOptions方法設置此 socket的屬性。由於SetSocketOptions是虛函數,在Socket類的實現中是一個空函數,而不一樣的Socket的派生類在覆蓋 (overwrite)該函數執行的操做是不一樣的,這是多態特性的應用。此後設置socket的可重用(reuse)屬性,使服務器在重啓後能夠重用之前的地址和端口。此時該socket尚未綁定到某個網絡端點(IP地址、端口對)上,bind系統調用完成此功能。最後,若是該socket用於一個 TCP鏈接,則調用listen函數,一來向系統說明能夠接受到socket綁定端口上的鏈接請求,二來設定請求等待隊列的長度爲5。
Socket的Listen方法將地址解析(地址結構生成)、socket、bind和listen等系統調用組合爲一個函數。在應用時,調用一個Listen方法就能夠完成Server端socket初始化的全部工做。
ui

Accept函數this

Accept函數是Server完成socket初始化,等待鏈接請求時調用的函數。代碼以下:spa

/* -------------------------------------------------------------------
 * After Listen() has setup mSock, this will block
 * until a new connection arrives. Handles interupted accepts.
 * Returns the newly connected socket.
 * ------------------------------------------------------------------- */

int Socket::Accept( void ) {
    iperf_sockaddr clientAddr; 
    Socklen_t addrLen;
    int connectedSock;

    while ( true ) {
        // accept a connection
        addrLen = sizeof( clientAddr );
        connectedSock = accept( mSock, (struct sockaddr*) &clientAddr, &addrLen );

        // handle accept being interupted
        if ( connectedSock == INVALID_SOCKET  &&  errno == EINTR ) {
            continue;
        }

        return connectedSock;
    }

} // end Accept

    Accept函數爲accept系統調用增添了在中斷後自動重啓的功能。Server線程在執行accept函數是後被阻塞,直到有請求到達,或是接收到某個信號。如果後面一種狀況,accept會返回 INVALID_SOCKET並置errno爲EINTR。Accept方法檢查這種狀況,並從新調用accept函數。線程

Connect函數指針

Connect函數Client端調用的函數,其做用是鏈接指定的Server。代碼以下:

/* -------------------------------------------------------------------
 * Setup a socket connected to a server.
 * If inLocalhost is not null, bind to that address, specifying
 * which outgoing interface to use.
 * ------------------------------------------------------------------- */

void Socket::Connect( const char *inHostname, const char *inLocalhost ) {
    int rc;
    SocketAddr serverAddr( inHostname, mPort );

    assert( inHostname != NULL );

    // create an internet socket
    int type = (mUDP  ?  SOCK_DGRAM : SOCK_STREAM);

    int domain = (serverAddr.isIPv6() ? 
#ifdef IPV6
                  AF_INET6
#else
                  AF_INET
#endif
                  : AF_INET);

    // DBG MJZ
#ifdef DBG_MJZ
    fprintf(stderr, "inHostname=%s domain=%d\n", inHostname, domain);
#endif /* DBG_MJZ */

    mSock = socket( domain, type, 0 );
    FAIL_errno( mSock == INVALID_SOCKET, "socket" );

    SetSocketOptions();


    if ( inLocalhost != NULL ) {
        SocketAddr localAddr( inLocalhost );

#ifdef DBG_MJZ

        // DBG MJZ
        fprintf(stderr, "inLocalhost=%s sockaddrlen=%d\n", inHostname, localAddr.get_sizeof_sockaddr());

        {
            // DBG MJZ
            struct sockaddr *sa = localAddr.get_sockaddr();
            int len = localAddr.get_sizeof_sockaddr();
            fprintf(stderr, "LOC len: %d salen: %d fam: %d data chars %2x %2x %2x %2x ...\n",
                    len, sa->sa_len, sa->sa_family, sa->sa_data[0], sa->sa_data[1],
                    sa->sa_data[2], sa->sa_data[3]);

        }
#endif /* DBG_MJZ */
        // bind socket to local address
        rc = bind( mSock, localAddr.get_sockaddr(), localAddr.get_sizeof_sockaddr());
        FAIL_errno( rc == SOCKET_ERROR, "bind" );
    }

#ifdef DBG_MJZ
    {
        // DBG MJZ
        struct sockaddr *sa = serverAddr.get_sockaddr();
        int len = serverAddr.get_sizeof_sockaddr();
        fprintf(stderr, "SRV len: %d salen: %d fam: %d data chars %2x %2x %2x %2x ...\n",
                len, sa->sa_len, sa->sa_family, sa->sa_data[0], sa->sa_data[1],
                sa->sa_data[2], sa->sa_data[3]);

    }
#endif /* DBG_MJZ */
    // connect socket
    rc = connect( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_sockaddr());
    FAIL_errno( rc == SOCKET_ERROR, "connect" );

} // end Connect

    首先構造一個SocketAddr實例保存Server端的地址(IP地址,端口對),同時按需完成地址解析。socket系統調用生成 socket接口。虛函數 SetSocketOptions利用多態特性使不一樣的派生類按須要設置socket屬性。若是傳入的inLocalhost參數不是空指針,說明調用者但願指定某個本地接口做爲鏈接的本地端點,此時經過bind系統調用把該socket綁定到這個接口對應的IP地址上。最後調用 connect函數完成與遠端Server的鏈接。

討論: TCP和UDP在調用connect函數是的操做有何不一樣?
    對於TCP鏈接,調用connect函數會發起創建TCP鏈接的三次握手(3-way handshaking)過程。當connect調用返回時,此過程已經完成,鏈接已經創建。由於TCP鏈接使用字符流模型,所以在創建好的鏈接上交換數據時,就好像從一個字符流中讀取,向另外一個字符流中寫入同樣。
    而UDP是無鏈接的協議,使用數據報而不是鏈接的模型,所以調用connect函數並不發起鏈接的過程,也沒有任何數據向Server發送,而只是通知操 做系統,發往該地址和端口的數據報都送到這個socket鏈接上來,也就是說,把這個(地址、端口)對和該socket關聯起來。UDP在IP協議的基礎 上提供了多路訪問(multiplex)的服務,UDP的connect系統調用對這種多路提供了socket接口與對端地址間的對應關係。在UDP鏈接 中,connect提功的這種功能是頗有用的。例如,Server能夠在接收到一個 Client的數據報後,分配一個線程執行connect函數與該 Client綁定,處理與該client的後繼交互,其餘的線程繼續在原來的UDP端口上監聽新的請求.由於在Client端和Server端都執行了 connect函數,因此一個Server與多個Client間的鏈接不會發生混亂。在 Iperf對UDP的處理中,就使用了這種技巧。

    以上簡要討論了Iperf提供的庫中幾個比較重要的類的定義與實現。

相關文章
相關標籤/搜索