程式扎記: [C++ 文章收集] Socket 通訊 實作範例

標籤

2011年4月8日 星期五

[C++ 文章收集] Socket 通訊 實作範例


轉載自 這裡
前言 :
難得找到一篇不錯的 Socket 的實作文章, 故整理在此方便日後參考. 底下是原作的說明 :
這個主題, 會針對socket通訊方面做一些簡單的說明. 由於講求的是portable, 因此winsock許多功能我並不會特別的說明 (特別是非同步通訊), 但我列出的程式, 一定是Windows/Linux (包括Solaris/AIX等等Unix系統), 一體適用.
socket運用方面有很多, 這邊我只專就於tcp/ip, block方式的通訊, 其他通訊的方式, 請自行研究 (全寫的話就太多了). 有英文閱讀能力的人, 請先看看底下這篇IBM寫的文章:
http://www-128.ibm.com/developerworks/linux/librar....html?ca=dgr-lnxw01BoostSocket
其中一部份, 我會在後面提及, 但不是全部.

標頭檔定義與說明 :
原作對平台的支援, 在標頭檔有定義如下 :
  1. //CPU型式  
  2. //#define INTEL  
  3. //#define SUN  
  4. //#define AIX  
  5.   
  6. //作業系統  
  7. //#define WINDOWS  
  8. //#define SOLARIS  
  9. //#define LINUX  
  10. //#define AIX  
  11.   
  12. #ifdef SUN  
  13. #define HIGH_BYTE_FIRST  
  14. #define NEED_DATA_ALIGN  
  15. #endif  
  16.   
  17. #ifdef AIX  
  18. #define HIGH_BYTE_FIRST  
  19. #endif  
而底下是作者的說明 :
這些是我曾porting過的cpu與作業系統. 作業系統部份, 主要區分為Windows與Unix, 其中的差異在於呼叫函數不太一樣. cpu方面比較麻煩. 這裡我定義了兩個參數:
- HIGH_BYTE_FIRST: 就是記憶體中是高位元組在前. 一般常見的INTEL CPU是低位元組在前, 例如一個長整數0x12345678, 在記憶體中的排列是0x78, 0x56, 0x34, 0x12, 但若是高位元組在前, 則是0x12, 0x34, 0x56, 0x78. 由於socket傳送的資料, 是以byte為觀念, 如果要傳送一個長整數, 便要考慮到記憶體中排列的格式.
- NEED_DATA_ALIGN: 也就是資料存取時, 必須配合記憶體Align的地方. 這部份舉例比較快些
如果要將資料放入緩衝區裡, 如果是先放一個char資料 (1 byte), 再放一個long資料 (4 byte), 那麼普通寫法是
  1. char data1;  
  2. long data2;  
  3. char* buffer = new char[...];  
  4. buffer[0] = data1;  
  5. *((long*)(buffer+1)) = data2;  
這樣的寫法在 NEED_DATA_ALIGN 的CPU中會引起Exception, 因為buffer+1的位址不是在4 byte align之處, 此時去存取long就會出問題. 解決方法就是利用memcpy, 直接將資料拷入, 讀取時也是要用memcpy方式讀出 (比較麻煩些).

而完整的 Header 檔內容如下 (我忽略的 OS 差異的部分) , 該標頭檔包含了類別 WgSocket 的定義 :
- WgSocket.h :
  1. #ifndef __WGSOCKET_H__  
  2. #define __WGSOCKET_H__  
  3. #ifdef WIN32  
  4.     #include   
  5.     #include   
  6. #define SD_RECEIVE 0  
  7. #define SD_SEND 1  
  8. #define SD_BOTH 2  
  9. #pragma comment(lib, "ws2_32.lib")  
  10. #else  
  11.     #include   
  12.     #include   
  13.     #include   
  14.     #include   
  15.     typedef int SOCKET;  
  16.     #define SOCKET_ERROR -1  
  17.     #define INVALID_SOCKET -1  
  18.     #define SHUT_RD 0 /* No more receptions.  */  
  19.     #define SHUT_WR 1 /* No more transmissions.  */  
  20.     #define SHUT_RDWR 2 /* No more receptions or transmissions.  */  
  21. #endif  
  22.   
  23. /* 
  24. * Reference : http://www.programmer-club.com/pc2020v5/Forum/ShowSameTitleN.asp?URL=N&board_pc2020=vc&id=28140 
  25. */  
  26. class WgSocket  
  27. {  
  28.     private:  
  29.         SOCKET m_Socket;  
  30.     public:  
  31.         bool Initialize();  
  32.         void Terminate();  
  33.         bool IsLocalHost(const char* hostname);  
  34.         bool GetHostIP(const char* hostname, int &ip1, int &ip2, int &ip3, int &ip4);  
  35.         WgSocket(void);  
  36.         ~WgSocket();  
  37.         bool IsOpened(voidconst;  
  38.         void SetSocket(SOCKET socket);  
  39.         bool Open(const char* hostname, int port);  
  40.         void Close(void);  
  41.         bool WaitInputData(int seconds);  
  42.         bool Read(void* buffer, long len, long &ret_len);  
  43.         bool Write(const void* buffer, long len);  
  44.         bool Listen(int port);  
  45.         bool Accept(SOCKET &socket);  
  46.         bool SetNoDelay(void);  
  47. };  
  48.   
  49. #endif  

Socket的使用流程 (Block模式) :
在說明WgSocket各函數之前, 先說明一下socket的使用流程.
- client端 :
1.取得socket並連線 (Open)
2.傳接資料 (WaitInputData, Read, Write)
3.斷線 (Close)

- server端:
1.取得socket, bind到某個通訊埠, 然後listen (Listen)
2.接收client端的連線 (Accept, SetSocket)
3.傳接資料並加以處理 (WaitInputData, Read, Write) -> 視情況要用多重執行緒
4.終止與client端的連線 (Close)
5.回到步驟3, 直到應結束
6.斷線 (Close)

其實socket通訊的函數, 基本上並不難, 比較難的是傳接雙方的通訊協定如何去定義, 這部份等談完socket通訊函數後, 再來說明. 底下是上面呼叫的流程示意圖 :


Socket 連線基本常識介紹 :
原作有針對 Socket 連線的現像進行說明與介紹, 先看看原作怎麼說吧 :
通常當Server端沒有開機時, 會傳回連線逾時, 大約需等數十秒才會傳回, 如何縮短這個逾時時間, 我並沒有深入去研究, 主要是很少碰到. 如果Server端有開機, 但Server通訊程式沒執行, 則會立即傳回無法連線. 由於每次連線需要約20-30ms, 因此對於講求效率的通訊程式而言, 一般會採用connect-alive方式處理, 這部份後面再來說明. 至於WSAEADDRINUSE的實際意義, 就是所有可用的port都處理TIME_WAIT狀態, 無法再拿來使用. 關於port的狀態值, 下面是相關的基礎知識.

- 連線與關閉的狀態與過程
當socket port尚未使用時,它的狀態是CLOSED。Server端必須先listen、bind和accept,以準備接收Client端的連線,此時的狀態進入LISTEN.
當Client端要連線到Server端時,首先會送一個SYN封包到Server端,同時進入SYN_SENT狀態。Server端收到後,便回送SYN-ACK封包,並進入SYN_RCVD狀態。Client端收到這個封包之後,便進入ESTABLISHED狀態,並回送ACK封包,表示連線成功,否則在過一段時間後,直接回到CLOSED狀態,並傳回錯誤。Server端收到Client端送來的ACK封包後,進入ESTABLISHED狀態,表示連線成功,由Accept函數返回.
Socket的關閉有兩種方式,一種是主動關閉,一種是被動關閉。以下為其過程 :
(1)主動關閉(active socket closure)
當程式呼叫closesocketshutdown(SD_SEND)時,首先送出FIN封包給對方,同時狀態變成FIN_WAIT_1。接著等待對方的回應封包.
a. 如果回應的是FIN封包,表示對方也正好要關閉連線,此時送出ACK封包給對方,狀態也進入CLOSING,此時必須等對方回應ACK封包後,進入TIME_WAIT狀態。
b. 如果回應的是ACK封包,表示對方尚未關閉連線,此時便進入FIN_WAIT_2狀態,並等待對方送來FIN封包。收到後送出ACK封包給對方,即進入TIME_WAIT狀態。
c. 如果同時收到FIN與ACK封包,表示對方已進入等待連線關閉的狀態,送出ACK封包給對方後,便進入TIME_WAIT狀態

(2)被動關閉(passive closure)
當socket port收到對方送來FIN封包時,表示對方要關閉連線,此時送回ACK封包後,進入CLOSE_WAITING狀態,等待程式呼叫closesocket。當程式呼叫closesocket後,送出FIN封包給對方,並進入LAST_ACK狀態,等待對方回送ACK封包後,便進入TIME_WAIT狀態.

無論是何種連線關閉的過程,都會進入TIME_WAIT狀態。當進入這種狀態時,socket port是無法再被使用的,必須等待一段時間後,才能再被使用。這個等待時間的目的,是為了讓網路上殘留該ip的所有封包能夠因timeout而被全數捨棄所設,因此至少應該>=2MSL。MSL即Maximum Segment Lifetime,也就是網路上各Router所設定的封包最大殘留時間.
由於socket port進入TIME_WAIT狀態時, 必須等待一段時間後才能再度被使用, 因此如果有大量的連線/斷線, 往往會導致所有的socket port都被用光了. 這時便需要修改Windows的設定, 增加可使用的socket port數量, 或縮短TIME_WAIT的等待時間. 更改的方式是設定register key:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Paremeters
裡面有兩個參數:
MaxUserPort: 最大可用的socket port數目, 將之改成65534
TcpTimedWaitDelay: TIME_WAIT的等待時間, 將之改成30 (秒)

WgSocket 實作說明 :
底下會針對 WgSocket 的實作進行部分說明與介紹.
- 建構/解建構
在建構時將 m_Socket 設為 INVALID_SOCKET. 在解建構時呼叫 Close() 關閉 Socket.

- bool WgSocket::Open(const char* hostname, int port)
開啟與Server的連線. 首先判斷 hostname 是否為 localhost. 接著呼叫 gethostbyname() 函式取回解析完的 address 訊息後設定 struct sockaddr_in. 最後呼叫函式 connect()進行 Socket 連接. 如果失敗則呼叫 Close() 並返回 false. 底下是原作的說明 :
對Server連線的方式, 便是解開Server Hostname的位址, 取得socket, 然後連線. 這邊我將一些錯誤處理的部份都會掉了, 需要的人請自行加入. Windows上是由WSAGetLastError函數取得錯誤碼, Unix上則是由errno公用變數取得. 以下簡單說明一下Windows上常見的錯誤 :
WSAECONNREFUSED (10061): 無法連線
WSAEADDRINUSE (10048): 沒有可用的port
WSAETIMEDOUT (10060): 連線逾時

- void WgSocket::Close(void)
如果 Socket 尚未 connect 就直接返回, 否則呼叫 shutdown() 函式關閉 Socket send 的操作 (第二個參數給 SD_SEND)

- bool WgSocket::Listen(int port)
Server端的接聽過程, 主要是bind, listen (以上只需一次), 然後利用 accept 來與 Client 端建立連線 (以上可以多次).
在一開始先呼叫 Close() 關閉已經開啟的 Socket 連線, 接著使用 setsockopt() 函式設定 Socket 的 Options (SO_REUSEADDR). 然後呼叫 bind() 將 Socket 與 address 作關聯並於成功 bind 後呼叫 listen() 讓 Socket 開始頃聽 incoming Connection. 底下是原作說明 :
注意這裡我用setsockopt將SO_REUSEADDR設立起來, 這樣的話, 無論該socket port是處於什麼狀態, 都會被拿來重複使用. 因此在呼叫前要確定Server通訊程式沒有重複執行. 為什麼要設定這個屬性呢? 因為在Unix裡, 如果程式因特殊狀況跳出, 或是用kill方式停掉Server通訊程式, 由於socket port沒有正常close, 必須停上一段時間後才能再度使用該port, 如此一來, Server通訊程式便無法立即再度啟用了

- bool WgSocket::Accept(SOCKET &socket)
在這裡等待接收Client 的連線. 如果 Socket 的連線尚未建立則立即返回 false. 否則呼叫 accept() 接收來自 Client 的連線. 接著如果收到的 Socket 是合法的則返回 true, 否則返回 false (socket = INVALID_SOCKET). 底下是原作的說明 :
如果要得知Client端的ip位址, 可以從accept傳回的from結構裡取得傳出. accept一但呼叫, 必須要等到Client端有連線過來時才會返回. 如果Server程式想要結束, 可以用LocalHost的方式對自己開啟一個連線, 即可由accept返回, 然後再進行其他處理.

- int WgSocket::WaitInputData(int seconds)
呼叫 select() 函式的返回值來決定 Socket 的 status. 而其返回值說明如下 :
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred. If the return value is SOCKET_ERROR, WSAGetLastError can be used to retrieve a specific error code.

所以使用其返回值小於等於0 來說明 Socket 是處於異常的狀態. 而就不繼續接下來的操作. 底下是原作的說明 :
由於read函數必須等到對方有送來資料時才會返回, 因此在呼叫read前最好先偵測是否有資料進來, 以便進行timeout處理. 這裡我只以"秒"為timeout的基準, 需要更細微的時間, 請自行修改.

- bool WgSocket::Read(void* data, wglong len, wglong &ret_len)
如果 Socket 尚未連接則直接返回 false (IsOpened()==false). 否則呼叫 recv() 函式進行數據的接收. 如果接收長度為0, 表示對方已斷線.

- bool WgSocket::Write(const void* data, long len)
如果 Socket 尚未連接則直接返回 false (IsOpened()==false). 否則呼叫 send() 函數發送數據. 底下為原作說明 :
注意這裡我加了一個signal將SIGPIPE訊號停掉. 在Unix上, 若對方已斷線, 而己方還送出資料時, 便會引發一個SIGPIPE訊號, 這個訊號的內定處理方式便是終止程式. 如果不加上這行, 程式便會常常無預警地跳出結束. 原則上, 接收時並不會引發這個訊號, 但為保險起見, 我還是加上去.

- bool WgSocket::SetNoDelay(void)
Nagle Algorithm的詳細說明, 請參考MSDN "Nagle Algorithm"一文, 這個演算法主要是避免過多零散的送出資料, 將之收集後再一次送出. 對於非講究效率且非一次性資料送出的通訊程式而言 (例如TTY, telnet等), 這個演算法可以大量降低網路的資料傳輸量. 但若是已設計好一次性封包的通訊軟體而言, 這個演算法反而會嚴重影響效率. 在
http://www-128.ibm.com/developerworks/linux/librar....html?ca=dgr-lnxw01BoostSocket

這篇所提到4種增加Linux通訊效率的方法中, 其中前2種也是適用於Windows系統的 :
1. 將所有要送出的資料集結在一起, 然後一次透過send函數送出. MSDN裡也有提到, 儘可能不要採用Send-Send-Receive的資料傳接方式.
2. 停掉Nagle Algorithm, 這也是本函數的主要目的.

實作與測試 :
底下是 WgSocket 的完整實作 :
- WgSocket.cpp :
  1. #include "WgSocket.h"  
  2. #include   
  3. #include   
  4.   
  5. static bool m_Init_Flag=false;  
  6.   
  7. bool WgSocket::Initialize()  
  8. {  
  9. #ifdef WIN32  
  10.        if (!m_Init_Flag)  
  11.        {  
  12.            WSAData wsa_data;   
  13.             if (WSAStartup(0x202,&wsa_data) != 0return false// 初始化失敗 //  
  14.            m_Init_Flag = true;  
  15.        }  
  16. #endif  
  17.        return true;  
  18. }  
  19.   
  20.   
  21. void WgSocket::Terminate()  
  22. {  
  23. #ifdef WIN32  
  24.     if (m_Init_Flag)  
  25.     {  
  26.         WSACleanup();  
  27.         m_Init_Flag = false;  
  28.     }  
  29. #endif  
  30. }  
  31.   
  32. WgSocket::WgSocket(void)  
  33. //說明:建構Socket物件  
  34. {  
  35.     m_Socket = INVALID_SOCKET;  
  36. }  
  37.   
  38. WgSocket::~WgSocket()  
  39. //說明:解構Socket物件  
  40. {  
  41.     Close();  
  42. }  
  43.   
  44. bool WgSocket::IsLocalHost(const char* hostname)  
  45. //說明:檢查是否為localhost呼叫  
  46. //輸入:hostname = Server位址  
  47. //傳回:是否為localhost呼叫  
  48. {  
  49.    if (hostname == NULL) return true;  
  50.    if (*hostname == 0return true;  
  51.    if (stricmp(hostname,"localhost") == 0return true;  
  52.    if (strcmp(hostname,"127.0.0.1") == 0return true;  
  53.    return false;  
  54. }  
  55.   
  56. bool WgSocket::IsOpened(voidconst  
  57. //說明:檢測Socket是否已開啟  
  58. //傳回:檢測結果  
  59. {  
  60.    if (m_Socket == INVALID_SOCKET) return false;  
  61.    return true;  
  62. }  
  63.   
  64. /* 
  65. * WSAECONNREFUSED (10061): 無法連線 
  66. * WSAEADDRINUSE (10048): 沒有可用的port 
  67. * WSAETIMEDOUT (10060): 連線逾時 
  68. */  
  69. bool WgSocket::Open(const char* hostname, int port)  
  70. //說明:開啟與Server的連線  
  71. //輸入:hostname,port = Server位址與通訊埠  
  72. //傳回:失敗傳回false  
  73. {  
  74.    Close();  
  75.    if (!Initialize()) return false;  
  76.    struct sockaddr_in sock_addr;  
  77.    // 解出socket address //  
  78.    if (IsLocalHost(hostname)) hostname = "127.0.0.1";  
  79.    sock_addr.sin_family = AF_INET;  
  80.    sock_addr.sin_port = htons(port);  
  81.    struct hostent *hostinfo = gethostbyname(hostname);  
  82.    if (hostinfo == NULL) return false;  
  83.    sock_addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;  
  84.    // 建立socket //  
  85.    try  
  86.    {  
  87.      m_Socket = socket(AF_INET,SOCK_STREAM,0);  
  88.    }  
  89.    catch(...)  
  90.    {  
  91.      m_Socket = INVALID_SOCKET;  
  92.      return false;  
  93.    }  
  94.    if (m_Socket == INVALID_SOCKET) return false;  
  95.    // 開始連線 //  
  96.    try  
  97.    {  
  98.      if (connect(m_Socket,(struct sockaddr*)&sock_addr,sizeof(sock_addr)) >= 0return true;  
  99.    }  
  100.    catch(...)  
  101.    {  
  102.    }  
  103.    // 此處可以加入一些錯誤處理... //  
  104.    Close();  
  105.    return false;  
  106. }  
  107.   
  108. void WgSocket::Close(void)  
  109. //說明:關閉與Server的連線  
  110. {  
  111.    if (!IsOpened()) return;  
  112.    try  
  113.    {  
  114. #ifdef WIN32  
  115.      // http://msdn.microsoft.com/en-us/library/ms740481(v=vs.85).aspx  
  116.      shutdown(m_Socket,SD_SEND);  
  117. #else  
  118.     // http://linux.die.net/man/3/shutdown  
  119.     shutdown(m_Socket,SHUT_WR);  
  120. #endif  
  121.    }  
  122.    catch(...)  
  123.    {  
  124.    }  
  125.    try  
  126.    {  
  127. #ifdef WIN32  
  128.      closesocket(m_Socket);  
  129. #else  
  130.     close(m_Socket);  
  131. #endif  
  132.    }  
  133.    catch(...)  
  134.    {  
  135.    }  
  136.    m_Socket = INVALID_SOCKET;  
  137. }  
  138.   
  139. /* 
  140. * - Server端的接聽過程 
  141. *      Server端的接聽過程, 主要是bind, listen (以上只需一次), 然後利用accept來與Client 
  142. *      端建立連線 (以上可以多次). 
  143. */  
  144. bool WgSocket::Listen(int port)  
  145. //說明:接聽某個Port  
  146. //輸入:port = 接聽Port  
  147. //傳回:失敗傳回false  
  148. {  
  149.     Close();  
  150.     if (!Initialize()) return false;  
  151.     struct sockaddr_in sock_addr;  
  152.     sock_addr.sin_family = AF_INET;  
  153.     sock_addr.sin_addr.s_addr = INADDR_ANY;  
  154.     sock_addr.sin_port = htons(port);  
  155.     // 建立socket //  
  156.     try  
  157.     {  
  158.         m_Socket = socket(AF_INET,SOCK_STREAM,0);  
  159.     }  
  160.     catch(...)  
  161.     {  
  162.         m_Socket = INVALID_SOCKET;  
  163.         return false;  
  164.     }  
  165.     if (m_Socket == INVALID_SOCKET) return false;  
  166.     // Bind socket //  
  167.     int on = 1;  
  168.     // http://msdn.microsoft.com/en-us/library/ms740476(v=vs.85).aspx  
  169.     setsockopt(m_Socket,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on));  
  170.     int rc;  
  171.     try  
  172.     {  
  173.         // http://msdn.microsoft.com/en-us/library/ms737550(v=vs.85).aspx  
  174.         rc = bind(m_Socket,(struct sockaddr*)&sock_addr,sizeof(sock_addr));  
  175.     }  
  176.     catch(...)  
  177.     {  
  178.         rc = SOCKET_ERROR;  
  179.     }  
  180.     if (rc == SOCKET_ERROR)   
  181.     {  
  182.         Close();  
  183.         return false;  
  184.     }  
  185.     // Listen socket //  
  186.     try  
  187.     {  
  188.         // http://msdn.microsoft.com/en-us/library/ms739168(v=vs.85).aspx  
  189.         rc = listen(m_Socket,SOMAXCONN);  
  190.     }  
  191.     catch(...)  
  192.     {  
  193.         rc = SOCKET_ERROR;  
  194.     }  
  195.     if (rc == SOCKET_ERROR)   
  196.     {  
  197.         Close();  
  198.         return false;  
  199.     }  
  200.     return true;  
  201. }  
  202.   
  203. bool WgSocket::Accept(SOCKET &socket)  
  204. //說明:等待接收連線  
  205. //輸出:連線socket  
  206. //傳回:失敗傳回false  
  207. {  
  208.     socket = INVALID_SOCKET;  
  209.     if (!IsOpened()) return false;  
  210.     struct sockaddr_in from;  
  211. #ifdef WIN32  
  212.     int fromlen = sizeof(from);  
  213. #else  
  214.     socklen_t fromlen = sizeof(from);  
  215. #endif  
  216.     try  
  217.     {  
  218.         socket = accept(m_Socket,(struct sockaddr*)&from,&fromlen);  
  219.     }  
  220.     catch(...)  
  221.     {  
  222.         socket = INVALID_SOCKET;  
  223.         return false;  
  224.     }  
  225.     return true;  
  226. }  
  227.   
  228. void WgSocket::SetSocket(SOCKET socket)  
  229. //說明:設定連線的socket  
  230. //輸入:socket = 連線的socket  
  231. {  
  232.    Close();  
  233.    m_Socket = socket;  
  234. }  
  235.   
  236. /* 
  237. * 由於read函數必須等到對方有送來資料時才會返回, 因此在呼叫read前最好先偵測是否有資料進來,  
  238. * 以便進行timeout處理. 這裡我只以"秒"為timeout的基準, 需要更細微的時間, 請自行修改. 
  239. */  
  240. bool WgSocket::WaitInputData(int seconds)  
  241. //說明:等待對方送來資料  
  242. //輸入:seconds = 等待秒數  
  243. //傳回:沒有資料傳回false  
  244. {  
  245.     if (!IsOpened())   
  246.     {  
  247.         printf("\t[WaitInputData] Socket Not Open Yet!\n");  
  248.         return false;  
  249.     }  
  250.     // 設定descriptor sets //  
  251.     fd_set socket_set;  
  252.     FD_ZERO(&socket_set);  
  253.     FD_SET((unsigned int)m_Socket,&socket_set);  
  254.     // 設定timeout時間 //  
  255.     struct timeval timeout;  
  256.     timeout.tv_sec = seconds;  
  257.     timeout.tv_usec = 0;  
  258.     // 偵測是否有資料 //  
  259.     try  
  260.     {  
  261.         if (select(FD_SETSIZE,&socket_set,NULL,NULL,&timeout) <= 0)   
  262.         {  
  263.             printf("\t[WaitInputData] Timeout!\n");  
  264.             return false;  
  265.         }  
  266.     }  
  267.     catch(...)  
  268.     {  
  269.         printf("\t[WaitInputData] Exception!\n");  
  270.         return false;  
  271.     }  
  272.     return true;  
  273. }  
  274.   
  275. bool WgSocket::Read(void* data, long len, long &ret_len)  
  276. //說明:讀取資料  
  277. //輸入:data, len = 資料緩衝區與大小  
  278. //輸出:data = 讀取的資料, ret_len = 實際讀取的資料大小,0表對方已斷線  
  279. //傳回:失敗傳回false  
  280. //備註:本函數會一直等到有讀取資料或結束連線時才傳回  
  281. {  
  282.     ret_len = 0;  
  283.     if (!IsOpened()) return true;  
  284.     try  
  285.     {  
  286. #ifndef WIN32  
  287.         signal(SIGPIPE,SIG_IGN); // 避免SIGPIPE訊號終止程式 //  
  288. #endif  
  289.         // http://msdn.microsoft.com/en-us/library/ms740121(v=vs.85).aspx  
  290.         ret_len = recv(m_Socket,(char*)data,len,0);  
  291.     }  
  292.     catch(...)  
  293.     {  
  294.         ret_len = SOCKET_ERROR;  
  295.     }  
  296.     if (ret_len < 0)  
  297.     {  
  298.         ret_len = 0;  
  299.         return false;  
  300.     }  
  301.     return true;  
  302. }  
  303.   
  304. bool WgSocket::Write(const void* data, long len)  
  305. //說明:送出資料  
  306. //輸入:data, len = 資料緩衝區與大小  
  307. //傳回:失敗傳回false  
  308. {  
  309.     if (!IsOpened()) return false;  
  310.     if (len <= 0return true;  
  311.     int write_len;  
  312.     try  
  313.     {  
  314. #ifndef WIN32  
  315.         signal(SIGPIPE,SIG_IGN); // 避免SIGPIPE訊號終止程式 //  
  316. #endif  
  317.         write_len = send(m_Socket,(const char*)data,len,0);  
  318.     }  
  319.     catch(...)  
  320.     {  
  321.         write_len = SOCKET_ERROR;  
  322.     }  
  323.     if (write_len != len) return false;  
  324.     return true;  
  325. }  
  326.   
  327. bool WgSocket::GetHostIP(const char* hostname, int &ip1, int &ip2, int &ip3, int &ip4)  
  328. //說明:取得指定host的ip  
  329. //輸入:hostname = host位址  
  330. //輸出:ip1-4 = ip位址  
  331. //傳回:失敗傳回false  
  332. {  
  333.     if (IsLocalHost(hostname))  
  334.     {  
  335.         // 先取出實際的hostname //  
  336.         struct hostent *hostinfo = gethostbyname("localhost");  
  337.         if (hostinfo == NULL) return false;  
  338.         hostname = hostinfo->h_name;  
  339.     }  
  340.     struct hostent* hostinfo = gethostbyname(hostname);  
  341.     if (hostinfo == NULL) return false;  
  342.     char* addr = hostinfo->h_addr_list[0];  
  343.     ip1 = (unsigned char) addr[0];  
  344.     ip2 = (unsigned char) addr[1];  
  345.     ip3 = (unsigned char) addr[2];  
  346.     ip4 = (unsigned char) addr[3];  
  347.     return true;  
  348. }  
  349.   
  350. /* 
  351. * Nagle Algorithm的詳細說明, 請參考MSDN "Nagle Algorithm"一文, 這個演算法主要是避免過 
  352. * 多零散的送出資料, 將之收集後再一次送出. 對於非講究效率且非一次性資料送出的通訊程式 
  353. * 而言 (例如TTY, telnet等), 這個演算法可以大量降低網路的資料傳輸量. 但若是已設計好一次 
  354. * 性封包的通訊軟體而言, 這個演算法反而會嚴重影響效率.  
  355. * 而停掉Nagle Algorithm, 這也是本函數的主要目的. 
  356. * 更多可以參考 :  
  357. * http://www-128.ibm.com/developerworks/linux/library/l-hisock.html?ca=dgr-lnxw01BoostSocket 
  358. */  
  359. bool WgSocket::SetNoDelay(void)  
  360. //說明:設定不延遲傳送 (停掉Nagle Algorithm)  
  361. //傳回:設定失敗傳回false  
  362. {  
  363.     if (!IsOpened()) return false;  
  364.     int on = 1;  
  365.     if (setsockopt(m_Socket,IPPROTO_TCP,TCP_NODELAY,(char*)&on,sizeof(on)) != 0return false;  
  366.     return true;  
  367. }  

2 則留言:

  1. #include 角括號的內容被吃掉了

    回覆刪除
  2. #include 角括號的內容被吃掉了

    回覆刪除

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!