Socket程式設計基礎

你的財神爺發表於2018-10-25

 

                                                           Socket網路程式設計及cocos2dx中的使用

                                                                                                                        vince

https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1540464375016&di=4fa360e8a87d4b86676471832ba68c03&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201603%2F16%2F20160316213225_ejXJn.thumb.700_0.jpeg

一、問如何瞭解Linux Socket程式設計基礎

答:推薦書籍《實戰Linux Socket 程式設計》

 

二、TCP/IP、UDP 是什麼?各自有什麼特點?有什麼區別?

         答:請看書……..

三、Socket套接字 是什麼?就是用一個小整數作為描述符來標識這個套接字嗎?

答:socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –>

讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的檔案,一些socket函式就是對其進行的操作(讀/寫IO、開啟、關閉).說白了Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。

   

如果還不明白?

再答:Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層

https://i.iter01.com/images/b26498c38cc1e75144292c42a8a10beaa7ec866fd89ef9c26daac57033d2da2b.jpg

如果還還還還不不明白?那沒辦法請看書……..

四、基本的SOCKET介面函式 

    答:請後續聽下去……..

socket()函式

int  socket(int protofamily, int type, int protocol);
//返回sockfdsockfd是描述符。 socket函式對應於普通檔案的開啟操作。普通檔案的開啟操作返回一個檔案描述字,而socket()用於建立一個socket描述符(socket descriptor),它唯一標識一個socket。這個socket描述字跟檔案描述字一樣,後續的操作都有用到它,把它作為引數,通過它來進行一些讀寫操作。正如可以給fopen的傳入不同引數值,以開啟不同的檔案。建立socket的時候,也可以指定不同的引數建立不同的socket描述符,socket函式的三個引數分別為:
  • protofamily:即協議域,又稱為協議族(family)。常用的協議族有,AF_INET(IPV4)AF_INET6(IPV6)AF_LOCAL(或稱AF_UNIXUnixsocket)、AF_ROUTE等等。協議族決定了socket的地址型別,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與埠號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名作為地址。
  • type:指定socket型別。常用的socket型別有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的型別有哪些?)。
  • protocol:故名思意,就是指定協議。常用的協議有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議(這個協議我將會單獨開篇討論!)。

注意:並不是上面的typeprotocol可以隨意組合的,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當protocol0時,會自動選擇type型別對應的預設協議。

當我們呼叫socket建立一個socket時,返回的socket描述字它存在於協議族(address familyAF_XXX)空間中,但沒有一個具體的地址。如果想要給它賦值一個地址,就必須呼叫bind()函式,否則就當呼叫connect()listen()時系統會自動隨機分配一個埠。

bind()函式

正如上面所說bind()函式把一個地址族中的特定地址賦給socket。例如對應AF_INETAF_INET6就是把一個ipv4ipv6地址和埠號組合賦給socket

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函式的三個引數分別為:

  • sockfd:即socket描述字,它是通過socket()函式建立了,唯一標識一個socketbind()函式就是將給這個描述字繫結一個名字。
  • addr:一個const struct sockaddr *指標,指向要繫結給sockfd的協議地址。這個地址結構根據地址建立socket時的地址協議族的不同而不同。ipv4對應的是: 
  • struct sockaddr_in {
  •     sa_family_t    sin_family; /* address family: AF_INET */
  •     in_port_t      sin_port;   /* port in network byte order */
  •     struct in_addr sin_addr;   /* internet address */
  • };
  •  
  • /* Internet address. */
  • struct in_addr {
  •     uint32_t       s_addr;     /* address in network byte order */
};

ipv6對應的是: 

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};
 
struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};
  • addrlen:對應的是地址的長度。

通常伺服器在啟動的時候都會繫結一個眾所周知的地址(如ip地址+埠號),用於提供服務,客戶就可以通過它來接連伺服器;而客戶端就不用指定,有系統自動分配一個埠號和自身的ip地址組合。這就是為什麼通常伺服器端在listen之前會呼叫bind(),而客戶端就不會呼叫,而是在connect()時由系統隨機生成一個。

listen()connect()函式

如果作為一個伺服器,在呼叫socket()bind()之後就會呼叫listen()來監聽這個socket,如果客戶端這時呼叫connect()發出連線請求,伺服器端就會接收到這個請求。

int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函式的第一個引數即為要監聽的socket描述字,第二個引數為相應socket可以排隊的最大連線個數。socket()函式建立的socket預設是一個主動型別的,listen函式將socket變為被動型別的,等待客戶的連線請求。

connect函式的第一個引數即為客戶端的socket描述字,第二引數為伺服器的socket地址,第三個引數為socket地址的長度。客戶端通過呼叫connect函式來建立與TCP伺服器的連線。

accept()函式

TCP伺服器端依次呼叫socket()bind()listen()之後,就會監聽指定的socket地址了。TCP客戶端依次呼叫socket()connect()之後就向TCP伺服器傳送了一個連線請求。

TCP伺服器監聽到這個請求之後,就會呼叫accept()函式取接收請求,這樣連線就建立好了。之後就可以開始網路I/O操作了,即類同於普通檔案的讀寫I/O操作。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回連線connect_fd

如果accept成功返回,則伺服器與客戶已經正確建立連線了,此時伺服器通過accept返回的套接字來完成與客戶的通訊。

read()write()等函式

可以呼叫網路I/O進行讀寫操作了網路I/O操作有下面幾組:

  • read()/write()
  • recv()/send()
  • readv()/writev()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()

五、其他注意點

網路位元組序與主機位元組序

主機位元組序:就我們平常說的大端和小端模式:不同的CPU有不同的位元組序型別,這些位元組序是指整數在記憶體中儲存的順序,這個叫做主機序。引用標準的Big-EndianLittle-Endian的定義如下:

  a) Little-Endian就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。

  b) Big-Endian就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。

網路位元組序4個位元組的32 bit值以下面的次序傳輸:首先是07bit,其次815bit,然後1623bit,最後是24~31bit。這種傳輸次序稱作大端位元組序由於TCP/IP首部中所有的二進位制整數在網路中傳輸時都要求以這種次序,因此它又稱作網路位元組序。位元組序,顧名思義位元組的順序,就是大於一個位元組型別的資料在記憶體中的存放順序,一個位元組的資料沒有順序的問題了。

所以:在將一個地址繫結到socket的時候,請先將主機位元組序轉換成為網路位元組序,而不要假定主機位元組序跟網路位元組序一樣使用的是Big-Endian。由於這個問題曾引發過血案!公司專案程式碼中由於存在這個問題,導致了很多莫名其妙的問題,所以請謹記對主機位元組序不要做任何假定,務必將其轉化為網路位元組序再賦給socket

 

Socket中TCP的建立連線的三次握手

TCP協議通過三個報文段完成連線的建立,這個過程稱為三次握手(three-way handshake)

第一次握手:建立連線時,客戶端傳送syn包(syn=j)到伺服器,並進入SYN_SEND狀態,等待伺服器確認。SYN:同步序列編號(Synchronize Sequence Numbers)。

第二次握手:伺服器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也傳送一個SYN包(syn=k),即SYN+ACK包,此時伺服器進入SYN_RECV狀態。

第三次握手:客戶端收到伺服器的SYN+ACK包,向伺服器傳送確認包ACK(ack=k+1),此包傳送完畢,客戶端和伺服器進入ESTABLISHED狀態,完成三次握手。
一個完整的三次握手也就是: 請求---應答---再次確認。


Socket中TCP連線的終止(四次握手釋放)

建立一個連線需要三次握手,而終止一個連線要經過四次握手,這是由TCP的半關閉(half-close)造成的。

問為什麼連線時3次,斷開時4次?

答:請看書………….

六、第三方的開源庫

      拿別人成熟的程式碼來實現自己專案的功能,這個我強烈推薦。目場景來決定是否要使用第三方庫還是自已用原生的寫。

ACE: http://www.cs.wustl.edu/~schmidt/ACE.html

ACE採用ACE_OS適配層遮蔽各種不同的、複雜繁瑣的作業系統API。

ACE是一個大型的中介軟體產品,程式碼20萬行左右,過於巨集大,一堆的設計模式,架構了一層又一層。它龐大、複雜,適合大型專案。開源、免費,不依賴第三方庫。使用的時候,要根據情況,看你從哪一層來進行使用。支援跨平臺。

C++ Sockets Library: http://www.alhem.net/Sockets/index.html

它是一個跨平臺的Sockets庫,實現包括TCP、UDP、ICMP、SCTP協議。已實現的應用協議包括有SMTP、HTTP(S)、Ajp。具有SOCKS客戶端實現以及匿名DNS,支援HTTP的GET/POST/PUT以及WebServer的框架。它封裝了sockets C API的C++類庫。支援SSL, IPv6, tcp和udp sockets, sctp sockets, http協議, 高度可定製的錯誤處理。

Asio C++ Library: http://think-async.com/

它是一個基於Boost開發的非同步IO庫,封裝了對Socket的常用操作,簡化了基於Socket程式的開發。它開源、免費、支援跨平臺。

 libevent: http://libevent.org/

它是一個C語言寫的網路庫,主要支援的是類Linux 作業系統,最新的版本新增了對Windows的IOCP的支援。由於IOCP是非同步IO,與Linux下的POLL模型,EPOLL模型,還有freebsd的KQUEUE等這些同步模型在用法上完全不一致,所以使用方法也不一樣,就好比ACE中的Reactor和Proactor模式一樣,使用起來需要轉變思路。如果對效能沒有特別的要求,那麼使用libevent中的select模型來實現跨平臺的操作,select模型可以橫跨Windows,Linux,Unix,Solaris等系統。

SimpleSocket: http://home.kpn.nl/lcbokkers/simsock.htm

這個類庫讓編寫基於Socket的客戶/伺服器程式更加容易。

libcurl: http://curl.haxx.se/libcurl/

libcurl是免費的輕量級的客戶端網路庫,支援DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS,POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet, TFTP.支援SSL, HTTPPOST,HTTPPUT, FTP上傳, HTTP form上傳,代理,cookies, 使用者名稱與密碼認證。

如果你開發的是客戶端,libcurl是一個不錯的選擇。

還有其它的語言的一些開源庫。

 

美圖來了,請釋放你的眼睛

 

 

 

https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1540463914770&di=b10aa76d48444d0ceb97864255f2b19f&imgtype=0&src=http%3A%2F%2Fimg10.ccnxs.cn%2Fuploadfile%2Fhbase%2F201709%2F0911%2FHBC59B64D0AD4B04.jpeg

 

實戰專案

拼音專案的挑戰遊戲裡用的是自己寫的一個socket客戶端,我的寫法是分為三個模組。

網路連線底層,control層,view

 

網路連線底層:處理網路層的網路資料。接收快取區和傳送快取區,一般會開啟新的執行緒來挖資料和塞資料。(形成自己的接收訊息佇列和傳送資料佇列)

 

//乾貨來了

半包,粘包與分包怎麼辦????

首先看兩個概念: 
短連線: 
連線->傳輸資料->關閉連線 
   HTTP
是無狀態的,瀏覽器和伺服器每進行一次HTTP操作,就建立一次連線,但任務結束就中斷連線。 
  
也可以這樣說:短連線是指SOCKET連線後傳送後接收完資料後馬上斷開連線。 

長連線: 
連線->傳輸資料->保持連線 -> 傳輸資料-> 。。。 ->關閉連線。 
長連線指建立SOCKET連線後不管是否使用都保持連線,但安全性較差。 

 

之所以出現粘包和半包現象,是因為TCP當中,只有流的概念,沒有包的概念. 

 


半包 
指接受方沒有接受到一個完整的包,只接受了部分,這種情況主要是由於TCP為提高傳輸效率,將一個包分配的足夠大,導致接受方並不能一次接受完。(在長連線和短連線中都會出現)。 

粘包

指傳送方傳送的若干包資料到接收方接收時粘成一包,從接收緩衝區看,後一包資料的頭緊接著前一包資料的尾。出現粘包現象的原因是多方面的,它既可能由傳送方造成,也可能由接收方造成。傳送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,傳送方往往要收集到足夠多的資料後才傳送一包資料。若連續幾次傳送的資料都很少,通常TCP會根據優化演算法把這些資料合成一包後一次傳送出去,這樣接收方就收到了粘包資料。接收方引起的粘包是由於接收方使用者程式不及時接收資料,從而導致粘包現象。這是因為接收方先把收到的資料放在系統接收緩衝區,使用者程式從該緩衝區取資料,若下一包資料到達時前一包資料尚未被使用者程式取走,則下一包資料放到系統接收緩衝區時就接到前一包資料之後,而使用者程式根據預先設定的緩衝區大小從系統接收緩衝區取資料,這樣就一次取到了多包資料。分包是指在出現粘包的時候我們的接收方要進行分包處理。(在長連線中都會出現) 

 

什麼時候需要考慮半包的情況? 
從備註中我們瞭解到Socket內部預設的收發緩衝區大小大概是8K,但是我們在實際中往往需要考慮效率問題,重新配置了這個值,來達到系統的最佳狀態。 
一個實際中的例子:用mina作為伺服器端,使用的快取大小為10k,這裡使用的是短連線,所有不用考慮那就要分包了》》》》》》

怎麼分?


粘包的問題。 
問題描述:在併發量比較大的情況下,就會出現一次接受並不能完整的獲取所有的資料。 
處理方式: 
1.通過包頭+包長+包體的協議形式,當伺服器端獲取到指定的包長時才說明獲取完整。 
2.指定包的結束標識,這樣當我們獲取到指定的標識時,說明包獲取完整。

這就要跟伺服器哥,約定資料嘍。

接收執行緒,recv讀接收快取區執行緒。這裡的話要了解下socket阻塞和非阻塞。

阻塞socket和非阻塞socket

建立連線
阻塞方式下,connect首先傳送SYN請求道伺服器,當客戶端收到伺服器返回的SYN的確認時,則connect返回.否則的話一直阻塞.
非阻塞方式,connect將啟用TCP協議的三次握手,但是connect函式並不等待連線建立好才返回,而是立即返回。返回的錯誤碼為EINPROGRESS,表示正在進行某
種過程.      
接收連線
對於阻塞方式的傾聽socket,accept在連線佇列中沒有建立好的連線時將阻塞,直到有可用的連線,才返回。

讀操作 主要介紹這個

對於阻塞的socket,socket的接收緩衝區中沒有資料時,read呼叫會一直阻塞住,直到有資料到來才返回。當socket緩衝區中的資料量小於期望讀取的資料量時,返回實際讀取的位元組數。當sockt的接收緩衝區中的資料大於期望讀取的位元組數時,讀取期望讀取的位元組數,返回實際讀取的長度。
對於非阻塞socket而言,socket的接收緩衝區中有沒有資料,read呼叫都會立刻返回。接收緩衝區中有資料時,與阻塞socket有資料的情況是一樣的,如果接收緩衝區中沒有資料,則返回錯誤號為EWOULDBLOCK,表示該操作本來應該阻塞的,但是由於本socket為非阻塞的socket,因此立刻返回,遇到這樣的情況,可以在下次接著去嘗試讀取。如果返回值是其它負值,則表明讀取錯誤。

寫操作
而對於阻塞Socket而言,如果傳送緩衝區沒有空間或者空間不足的話,write操作會直接阻塞住,如果有足夠空間,則拷貝所有資料到傳送緩衝區,然後返回.

對於寫操作write,原理是類似的,非阻塞socket在傳送緩衝區沒有空間時會直接返回錯誤號EWOULDBLOCK,表示沒有空間可寫資料,如果錯誤號是別的值,則表明傳送失敗。如果傳送緩衝區中有足夠空間或者是不足以拷貝所有待傳送資料的空間的話,則拷貝前面N個能夠容納的資料,返回實際拷貝的位元組數。
 

socket斷開時,如何及時通知客戶斷???阻塞和非阻塞有什麼區別?

發心跳包能不能解決這個問題呢???

 

control層:連線網路層和view層定時從訊息佇列來訊息資料進行處理。解析網路資料包。

      一般只向view層暴露三個介面。

Connect()

close()

Message()

網路協議(資料遊,json串,protobuf…?

 

View層:消費control層傳來的訊息。主要是消費網路連線事件,網路斷開事件,網路資料獲取事件。重新整理我們的遊戲邏輯。

 

如果使用第三方開源庫,那麼我們主要關注view層就行了,就算對網路協議資料有特別要求,那麼定製下特定的協議就行了,前面的工作就不用作了,那真是太爽了。哈哈哈哈。。。。。

怎麼選擇由你來定!!!!

 

 

#include <stdio.h>
#include "ODSocket.h"

#ifdef WIN32
#pragma comment(lib, "wsock32")
#else
#include <netinet/tcp.h>
#endif

ODSocket::ODSocket(SOCKET sock) {
    m_sock = sock;
}

ODSocket::~ODSocket() {
}

int ODSocket::Init() {
#ifdef WIN32
    /*
     http://msdn.microsoft.com/zh-cn/vstudio/ms741563(en-us,VS.85).aspx

     typedef struct WSAData {
     WORD wVersion;                                //winsock version
     WORD wHighVersion;                            //The highest version of the Windows Sockets specification that the Ws2_32.dll can support
     char szDescription[WSADESCRIPTION_LEN+1];
     char szSystemStatus[WSASYSSTATUS_LEN+1];
     unsigned short iMaxSockets;
     unsigned short iMaxUdpDg;
     char FAR * lpVendorInfo;
     }WSADATA, *LPWSADATA;
     */
    WSADATA wsaData;
    //#define MAKEWORD(a,b) ((WORD) (((BYTE) (a)) | ((WORD) ((BYTE) (b))) << 8))
    WORD version = MAKEWORD(2, 0);
    int ret = WSAStartup(version, &wsaData); //win sock start up
    if (ret) {
//        cerr << "Initilize winsock error !" << endl;
        return -1;
    }
#endif

    return 0;
}
//this is just for windows
int ODSocket::Clean() {
#ifdef WIN32
    return (WSACleanup());
#endif
    return 0;
}

ODSocket& ODSocket::operator =(SOCKET s) {
    m_sock = s;
    return (*this);
}

ODSocket::operator SOCKET() {
    return m_sock;
}

bool ODSocket::Connect(const char* domain, unsigned short port) {
    char ip[128];
    memset(ip, 0, sizeof(ip));
    strcpy(ip, domain);

    void* svraddr = nullptr;
    int error=-1, svraddr_len;
    bool ret = true;
    struct sockaddr_in svraddr_4;
    struct sockaddr_in6 svraddr_6;

    m_sock = -1;
    
    //��ȡ����Э��
    struct addrinfo *result;
    error = getaddrinfo(ip, NULL, NULL, &result);
    const struct sockaddr *sa = result->ai_addr;
    socklen_t maxlen = 128;
    switch (sa->sa_family) {
    case AF_INET://ipv4
        if ((m_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket create failed\n");
            ret = false;
            break;
        }
#ifdef WIN32
        if (inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),ip, maxlen) <  0){
#else
        if (inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),ip, maxlen) <  (char *)0){
#endif
            perror(ip);
            ret = false;
            break;
        }
        svraddr_4.sin_family = AF_INET;
        svraddr_4.sin_addr.s_addr = inet_addr(ip);
        svraddr_4.sin_port = htons(port);
        svraddr_len = sizeof(svraddr_4);
        svraddr = &svraddr_4;
        break;
    case AF_INET6://ipv6
        if ((m_sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
            perror("socket create failed\n");
            ret = false;
            break;
        }
        inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr), ip, maxlen);

        printf("socket created ipv6\n");

        memset(&svraddr_6, 0, sizeof(svraddr_6));
        svraddr_6.sin6_family = AF_INET6;
        svraddr_6.sin6_port = htons(port);
        if (inet_pton(AF_INET6, ip, &svraddr_6.sin6_addr) < 0) {
            perror(ip);
            ret = false;
            break;
        }
        svraddr_len = sizeof(svraddr_6);
        svraddr = &svraddr_6;
        break;

    default:
        printf("Unknown AF\n");
        ret = false;
    }
    freeaddrinfo(result);
    if (!ret)
    {
        fprintf(stderr, "Cannot Connect the server\n");
        return false;
    }
    int nret = connect(m_sock, (struct sockaddr*)svraddr, svraddr_len);
    if (nret == SOCKET_ERROR)
    {
        return false;
    }

    return true;
}

bool ODSocket::Bind(unsigned short port) {
    struct sockaddr_in svraddr;
    svraddr.sin_family = AF_INET;
    svraddr.sin_addr.s_addr = INADDR_ANY;
    svraddr.sin_port = htons(port);
    int opt = 1;
    if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (char*) &opt, sizeof(opt))
            < 0)
        return false;

    int ret = bind(m_sock, (struct sockaddr*) &svraddr, sizeof(svraddr));
    if (ret == SOCKET_ERROR) {
        return false;
    }
    return true;
}
//for server
bool ODSocket::Listen(int backlog) {
    int ret = listen(m_sock, backlog);
    if (ret == SOCKET_ERROR) {
        return false;
    }
    return true;
}

bool ODSocket::Accept(ODSocket& s, char* fromip) {
    struct sockaddr_in cliaddr;
    socklen_t addrlen = sizeof(cliaddr);
    SOCKET sock = accept(m_sock, (struct sockaddr*) &cliaddr, &addrlen);
    if (sock == SOCKET_ERROR) {
        return false;
    }

    s = sock;
    if (fromip != NULL)
        sprintf(fromip, "%s", inet_ntoa(cliaddr.sin_addr));
    return true;
}

int ODSocket::Select(){/*������*/
    //fd_set���Ͽ���ͨ��һЩ������Ϊ��������������ռ��� FD_ZERO(fd_set *)����һ���������ļ����������뼯��֮��FD_SET(int, fd_set *)��
    //��һ���������ļ��������Ӽ�����ɾ��FD_CLR(int, fd_set*)����鼯����ָ�����ļ��������Ƿ���Զ�дFD_ISSET(int, fd_set*)��
    FD_ZERO(&fdR);
    FD_SET(m_sock, &fdR);
    struct timeval mytimeout;
    mytimeout.tv_sec = 1;
    mytimeout.tv_usec = 0;
    int result = select(m_sock+1, &fdR, NULL, NULL, &mytimeout);
    // ��һ�������� 0 �� sockfd �е����ֵ��һ
    // �ڶ��������� ����, Ҳ���� sockset
    // ����, �ĸ�������д�����쳣��, �ڱ������ж�Ϊ��
    // ����������dz�ʱʱ��, ����ָ��ʱ������û�пɶ�, �����
    if(result == -1)
    {
        return -1;
    }
    else
    {
        if(FD_ISSET(m_sock,&fdR))    
        {//����m_sock�Ƿ�ɶ������Ƿ������������� 
            return -2;
        }
        else 
        {
            return -3;
        }
    }
}

int ODSocket::Send(const char* buf, int len, int flags) {
    int bytes;
    int count = 0;
    while (count < len) {
        const char* a= buf + count;
        bytes = send(m_sock, buf + count, len - count, flags);
        if (bytes == -1 || bytes == 0)
            return -1;
        count += bytes;
    }
    return count;
}

int ODSocket::Recv(char* buf, int len, int flags) {
    return (recv(m_sock, buf, len, flags));
}

int ODSocket::Close() {
    if (m_sock == -1)
    {
        printf("ODSocket::Close===m_sock is -1");
        return 0;
    }
#ifdef WIN32
    return (closesocket(m_sock));
#else
    printf("ODSocket::Close===m_sock is %d",m_sock);
    m_sock = -1;
    return close(m_sock);
#endif
}

int ODSocket::GetError() {
#ifdef WIN32
    return (WSAGetLastError());
#else
    return -1;
#endif
}

bool ODSocket::DnsParse(const char* domain, char* ip) {
    struct hostent* p;
    if ((p = gethostbyname(domain)) == NULL)
        return false;

    sprintf(ip, "%u.%u.%u.%u", (unsigned char) p->h_addr_list[0][0],
            (unsigned char) p->h_addr_list[0][1],
            (unsigned char) p->h_addr_list[0][2],
            (unsigned char) p->h_addr_list[0][3]);

    return true;
}

SOCKET ODSocket::getSocket()
{
    return m_sock;
}

bool ODSocket::isConnected()
{
    /*
#ifdef WIN32
    return true;
#else
    if (m_sock <= 0)
        return 0;
    struct tcp_info info;
    int len = sizeof(info);
    getsockopt(m_sock, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
    return info.tcpi_state == TCP_ESTABLISHED;
#endif*/
    return true;
}
 

//==========================================================

 

#include "KimoSocket.h"
#include <thread>
#include "../EventHelper.h"
#include "../UtilMethod.h"
#include "AndroidUtils.h"
#include "NetWork_Constant.h"
#include <stdio.h>
#define  BODY_LARGE_BYTE 2

static std::string HEART_BEAT_MSG = "{\"HI\":{}}";

KimoSocket::KimoSocket()
{
    clearData();
}

KimoSocket::~KimoSocket(void)
{
    clearQueue();
}

void KimoSocket::clearData()
{
    memset(messageBuf, 0, 10240);
    memset(m_SendBuff, 0, MAX_SEND_BUFFLEN);
    memset(m_RecvBuf, 0, MAX_RECV_BUFFLEN);
    memset(mCurMsg, 0, CUR_RECV_BUFFLEN);
    m_messageLen = 0;
    mCurMsgLength = 0;
    clearQueue();
}

bool KimoSocket::connect(std::string ip, int port)
{
    clearData();
    ODSocket cdSocket;
    cdSocket.Init();
    
    bool iscon = cdSocket.Connect(ip.c_str(), port);
    if (iscon)
    {
        state = KimoSocketStateSucc;
        csocket = cdSocket;
        CCLOG("create socket is %d",csocket.getSocket());
        sEventHelper->DispatchCustomEvent(NetWork_ConnectSucceed, nullptr);
        CCLOG("KimoSocket::connect:socket has been gracefully connected.");
        this->startReceiveThread();
    }
    else
    {
        sEventHelper->DispatchCustomEvent(NetWork_ConnectFailed, nullptr);
        state = KimoSocketStateFail;
        CCLOG("KimoSocket::connect:socket connected failed");
    }
    return iscon;
}

void KimoSocket::startReceiveThread()
{
    std::thread t1(&KimoSocket::ReceiveThread, this, this);
    t1.detach();
}

void KimoSocket::ReceiveThread(void* arg)
{
    KimoSocket* kimosocket = static_cast<KimoSocket*>(arg);

    struct timeval tv_b, tv_d;
    struct timeval recvStart, sendStart, recvEnd;
    unsigned long long timeconsumed = 0;
    if (kimosocket->state == KimoSocketStateSucc)
    {
        bool isBreak = false;
        while (1)
        {
            bool needBreak = false;
            int recved;
            gettimeofday(&tv_b, NULL);
            switch (kimosocket->getSocket().Select())
            {
            case 0:
                CCLOG("KimoSocket::ReceiveThread:select timeout");
                break;
            case -1:
                CCLOG("KimoSocket::ReceiveThread:select error");
                kimosocket->state = KimoSocketStateReconnect;
                break;
            case -2:
                recved = kimosocket->getSocket().Recv(kimosocket->mCurMsg + kimosocket->mCurMsgLength, 1024);
                CCLOG("receive thread is :%d", std::this_thread::get_id());
                if (recved <= 0)
                {
                    //if (recved == 0)
                    {
                        CCLOG("KimoSocket::ReceiveThread:socket has been gracefully closed recved = %d", recved);//沒網
                        kimosocket->mCurMsg[kimosocket->mCurMsgLength] = '\0';
                        //if (kimosocket->state != KimoSocketStateClose)
                        {
                            isBreak = true;
                            kimosocket->state = KimoSocketStateReconnect;
                        }
                    }
                }
                else
                {
                    gettimeofday(&recvStart, NULL);
                    gettimeofday(&sendStart, NULL);

                    kimosocket->mCurMsgLength += recved;
                    kimosocket->mCurMsg[kimosocket->mCurMsgLength] = '\0';
                    CCLOG("KimoSocket::ReceiveThread:socket recved = %d , mCurMsg = %s", recved, kimosocket->mCurMsg);
                    kimosocket->todoMessage();
                }
                break;
            default://
                CCLOG("KimoSocket::ReceiveThread:select no data");
                
                //if (!kimosocket->getSocket().isConnected())
                /*if(AndroidUtils::ConnectState() <= 0)//沒網
                {
                    CCLOG("KimoSocket::ReceiveThread:socket disconnect");
                    kimosocket->state = KimoSocketStateReconnect;
                }
                else*/
                {
                    gettimeofday(&recvEnd, NULL);
                    int time1 = recvEnd.tv_usec - sendStart.tv_usec;
                    int time2 = recvEnd.tv_usec - recvStart.tv_usec;
                    CCLOG("time1 is %d",time1);
                    CCLOG("time2 is %d",time2);
                    if (time1 > HEART_BEAT_TIME_MAX / 2)
                    {
                        CCLOG("KimoSocket::ReceiveThread:heart beat timeout");
                        gettimeofday(&sendStart, NULL);
                        sendMessage(HEART_BEAT_MSG.c_str());
                    }
                    else if (time2 > HEART_BEAT_TIME_MAX)
                    {
                        CCLOG("KimoSocket::ReceiveThread:heart beat disconnect");
                        isBreak = true;
                        kimosocket->state = KimoSocketStateReconnect;
                    }
                }
                
                break;
            }
            if(AndroidUtils::ConnectState() <= 0)//沒網
            {
                CCLOG("KimoSocket::ReceiveThread:socket disconnect");
                isBreak = true;
                kimosocket->state = KimoSocketStateReconnect;
            }
            if (kimosocket->state == KimoSocketStateClose)
            {
                isBreak = true;
            }
            if (isBreak)
            {
                break;
            }
            /*gettimeofday(&tv_d, NULL);
            timeconsumed = 33 - (tv_d.tv_usec - tv_b.tv_usec) / 1000.0f;
            if (timeconsumed > 0)
            {
                CCLOG("KimoSocket:ReceiveThread:timeconsumed = %d", timeconsumed);
                std::this_thread::sleep_for(std::chrono::milliseconds(timeconsumed));
            }*/
        }
        CCLOG("KimoSocket::ReceiveThread:read break out net thread, close socket ");
        if (kimosocket->state == KimoSocketStateReconnect)
        {
            CCLOG("KimoSocket::KimoSocketStateReconnect");
            kimosocket->reConnect();
        }
    }
    else
    {
        CCLOG("is not KimoSocketStateSucc");
    }
}

void KimoSocket::todoMessage()
{
    if (mCurMsgLength <= 2)
    {
        return;
    }
    int offsetLen = 0;
    int curMsgLen = 0;

    while (mCurMsgLength - offsetLen > 2)
    {
        curMsgLen = (((unsigned char)mCurMsg[offsetLen]) << 8) + (unsigned char)mCurMsg[offsetLen + 1];
        CCLOG("KimoSocket:todoMessage:curMsgLen = %d", curMsgLen);

        if (curMsgLen <= mCurMsgLength - offsetLen - 2)
        {
            offsetLen += 2;
            memcpy(messageBuf, mCurMsg + offsetLen, curMsgLen);
            messageBuf[curMsgLen] = '\0';
            offsetLen += curMsgLen;

            std::string temp = (char*)messageBuf;

            if (temp.compare(HEART_BEAT_MSG) == 0)
            {
                CCLOG("KimoSocket:todoMessage:HEART_BEAT_MSG = %s", temp.c_str());
            }
            else
            {
                CCLOG("KimoSocket:todoMessage: mMessageQuque.add = %s", temp.c_str());
                mMessageQuque.add(temp);
            }
        }
        else
        {
            break;
        }
    }
    mCurMsgLength -= offsetLen;
    if (mCurMsgLength > 0)
    {
        //move buf to head
        memcpy(mCurMsg, mCurMsg + offsetLen, mCurMsgLength);
    }
    mCurMsg[mCurMsgLength] = '\0';
}

void KimoSocket::updateRecvMessage(float dt)
{
    std::string packet = "";
    for (int i = 0; i < NetWork_Receive_MaxNum;i++)
    {
        if (mMessageQuque.next(packet))
        {
            CCLOG("KimoSocket:updateRecvMessage: packet = %s", packet.c_str());
            sEventHelper->DispatchCustomEvent(NetWork_ConnectMessageData, (void*)packet.c_str());
        }
    }
}

void KimoSocket::sendMessage(const char* message)
{
    CCLOG("KimoSocket:sendMessage:%s", message);
    m_messageLen = strlen(message);
    char send[1024];
    send[0] = m_messageLen/256;
    send[1] = m_messageLen%256;
    int len = strlen(message);
    for (int i = 0; i < len ;i++)
    {
        send[i + 2] = message[i];
    }
    m_messageLen += 2;
    for (int i = 0; i < m_messageLen; i++)
    {
        m_SendBuff[i] = send[i];
    }    
    startSendThread();
}

void KimoSocket::startSendThread()
{
    std::thread t2(&KimoSocket::SendThread, this, this);
    t2.detach();
}

void KimoSocket::SendThread(void* arg)
{
    KimoSocket* kimosocket = static_cast<KimoSocket*>(arg);
    int cout = csocket.Send(kimosocket->m_SendBuff, kimosocket->m_messageLen, 0);
    if (cout == SOCKET_ERROR)
    {
        CCLOG("KimoSocket:sendMessage:send error");
    }else if (cout == 0)
    {
        //reConnect();
        CCLOG("KimoSocket:sendMessage:send error1");
    }else
    {
        CCLOG("KimoSocket:sendMessage:send successful");
    }        
}

void KimoSocket::close()
{
    if (csocket.getSocket())
    {
        CCLOG("KimoSocket::close socket1");
        state = KimoSocketStateClose;
        csocket.Close();
        CCLOG("KimoSocket::close socket2");
    }else
    {
        CCLOG("KimoSocket::socket is null");
    }
    
}

void KimoSocket::reConnect()
{
    //close();
    state = KimoSocketStateReconnect;
    CCLOG("=======KimoSocket::reConnect======");
    Director::getInstance()->getScheduler()->performFunctionInCocosThread([=]()mutable {
        sEventHelper->PostEvent(NetWork_ReConnectionCallBack, nullptr);
    });    
}

ODSocket KimoSocket::getSocket()
{
    return csocket;
}

bool KimoSocket::isClose()
{
    if (csocket)
    {
        return (csocket.getSocket() <= SOCKET_ERROR) ? true : false;
    }
    return true;
}

void KimoSocket::clearQueue()
{
    mMessageQuque._queue.clear();
}
 

 

//=====================================

#include"SocketHelper.h"
#include "../EventHelper.h"
#include "NetWork_Constant.h"

USING_NS_CC;

SocketHelper* SocketHelper::mInstance = nullptr;

SocketHelper::SocketHelper()
{
    mKimoSocket = new KimoSocket();
    m_scheduler = new CCScheduler();
    m_scheduler->retain();
    Director::getInstance()->getScheduler()->scheduleUpdateForTarget(m_scheduler, 1, false);
}

SocketHelper::~SocketHelper()
{
    if (mKimoSocket)
    {
        delete mKimoSocket;
        mKimoSocket = nullptr;
    }    
    if (m_scheduler)
    {
        delete m_scheduler;
        m_scheduler = nullptr;
    }
    removeEvent();
    desInstance();
}

SocketHelper* SocketHelper::getInstance()
{
    if (nullptr == mInstance)
    {
        mInstance = new SocketHelper();
    }
    return mInstance;
}

void SocketHelper::desInstance()
{
    if (nullptr != mInstance)
    {
        delete mInstance;
        mInstance = nullptr;
    }
}

void SocketHelper::start()
{
    socketState = SOCKSTAT_CLOSE;
    ip = "";
    port = 0;
    initEvent();    
}

//建立連線
bool SocketHelper::connect(std::string ip, int port)
{
    this->ip = ip;
    this->port = port;
    socketState = SOCKSTAT_CONNECTING;
    return mKimoSocket->connect(ip, port);
}
//------訊息事件

void SocketHelper::ConnectSucceed(EventCustom* event)//連線成功
{
    socketState = SOCKSTAT_CONNECTED;
    m_scheduler->schedule(schedule_selector(SocketHelper::recvMessage), this, 0.03f, CC_REPEAT_FOREVER, 0, false);
}

bool SocketHelper::reconnect()
{
    m_scheduler->unschedule(schedule_selector(SocketHelper::recvMessage), this);
    removeEvent();
    initEvent();
    socketState = SOCKSTAT_CONNECTING;
    return mKimoSocket->connect(ip, port);    
}

//傳送資料
void SocketHelper::sendData(const char* message)
{
    mKimoSocket->sendMessage(message);
}

//訊息佇列輪詢函式
void SocketHelper::recvMessage(float dt)
{
    mKimoSocket->updateRecvMessage(dt);
}

//定時器監聽網路訊息
#include "ChallengeGameData/Pakect.h"
void SocketHelper::MessageData(EventCustom* event)
{
    char* message = (char*)event->getUserData();
    CCLOG("recvMessage:MessageData is %s", message);
    UtilMethod::getInstance()->getTimeStamp();
    socketState = SOCKSTAT_MESSAGE;
    //訊息處理結構
    MessagePackect messagePackect;
    std::string temp = message;
    temp = sUtilMethod->trimHeadBack(temp);
    int index = 0;
    index = temp.find("\":");
    if (index != string::npos && index > 2)
    {
        messagePackect.MessageName = temp.substr(2,index -2);
        messagePackect.MessageBody = temp;
        CCLOG("recvMessage:messagePackect.MessageName is %s", messagePackect.MessageName.c_str());
        Pakect mPakect;
        mPakect.setMessagePackect(messagePackect);
        if (messagePackect.MessageName.compare("Hello") == 0)
        {
            ConnectTime connectTime = mPakect.unPackConnectTime();
            sEventHelper->PostEvent(NetWork_ConnetTimeCallBack, &connectTime);
        }
        else if (messagePackect.MessageName.compare("ItemCDInfo") == 0)
        {
            ItemCDInfo itemCDInfo = mPakect.unItemCDInfo();
            sEventHelper->PostEvent(NetWork_ItemCDInfoCallBack, &itemCDInfo);
        }
        else if (messagePackect.MessageName.compare("LoginRet") == 0)
        {
            LoginRet loginRet = mPakect.unPackLogin();
            sEventHelper->PostEvent(NetWork_LoginRetCallBack, &loginRet);
        }
        else if (messagePackect.MessageName.compare("OptRet") == 0)
        {
            OptRet mOptRet = mPakect.unPackOpt();
            sEventHelper->PostEvent(NetWork_PlayerOptRetCallBack, &mOptRet);
        }
        else if(messagePackect.MessageName.compare("UpdateSelfStepRet") == 0)
        {
            UpdateSelfStepRet mUpdateSelfStepRet = mPakect.unPackUpdateSelfStep();
            sEventHelper->PostEvent(NetWork_PlayerUpdateSelfStepCallBack, &mUpdateSelfStepRet);
        }
        else if (messagePackect.MessageName.compare("UseItemRet") == 0)
        {
            UseItemRet mUseItemRet = mPakect.unPackUseItem();
            sEventHelper->PostEvent(NetWork_PlayerUseItemRetCallBack, &mUseItemRet);
        }
        else if (messagePackect.MessageName.compare("RankListRet") == 0)
        {
            RankListRet mRankListRet = mPakect.unPackRankList();
            sEventHelper->PostEvent(NetWork_PlayerRankListRetCallBack, &mRankListRet);
        }
        else if (messagePackect.MessageName.compare("RoomInfoRet") == 0)
        {
            RoomInfoRet mRoomInfoRet = mPakect.unPackRoomInfo();
            sEventHelper->PostEvent(NetWork_RoomInfoRetCallBack, &mRoomInfoRet);
        }
        else if (messagePackect.MessageName.compare("PlayerEnterRet") == 0)
        {
            PlayerEnterRet mPlayerEnterRet = mPakect.unPackPlayerEnter();    
            sEventHelper->PostEvent(NetWork_PlayerEnterRetCallBack, &mPlayerEnterRet);
        }
        else if (messagePackect.MessageName.compare("GameStartRet") == 0)
        {
            sEventHelper->PostEvent(NetWork_PlayerGameStartRetCallBack, nullptr);
        }
        else if (messagePackect.MessageName.compare("GameFinishRet") == 0)
        {
            sEventHelper->PostEvent(NetWork_PlayerGameFinishRetCallBack, nullptr);
        }
        else if (messagePackect.MessageName.compare("LeaveGameRet") == 0)
        {
            sEventHelper->PostEvent(NetWork_PlayerLeaveGameRetCallBack, nullptr);
        }
        else if (messagePackect.MessageName.compare("OtherLeaveGameRet") == 0)
        {
            OtherLeaveGameRet mOtherLeaveGameRet = mPakect.unPackOtherLeaveGame();
            sEventHelper->PostEvent(NetWork_PlayerOtherLeaveGameRetCallBack, &mOtherLeaveGameRet);
        }
        else
        {
            log("recvMessage:have no messagePackect.MessageName ");
        }
        
    }
    else
    {
        log("recvMessage:messagePackect.MessageName is error");
    }
    
}


//--------------------------------------------------------------------------------
//清除連線
void SocketHelper::clearNet()
{
    CCLOG("SocketHelper::clearNet1");
    close();
    CCLOG("SocketHelper::clearNet2");
    m_scheduler->unschedule(schedule_selector(SocketHelper::recvMessage),this);
    this->removeEvent();    
}

//關閉連線
void SocketHelper::close()
{
    mKimoSocket->close();
}

//是否連線
bool SocketHelper::isConnected()
{
    return socketState == SOCKSTAT_CONNECTED;
}

//獲得ip
std::string SocketHelper::getIP()
{
    return ip;
}

//獲得埠
int SocketHelper::getPort()
{
    return port;
}

//是否正在連線
bool SocketHelper::isConnecting()
{
    return socketState == SOCKSTAT_CONNECTING;
}

void SocketHelper::removeEvent()
{
    for (int i = 0; i < ListenerVect.size(); i++)
    {
        sEventHelper->removeCustomEvent(ListenerVect.at(i));
    }
}

void SocketHelper::initEvent()
{
    removeEvent();
    ListenerVect.clear();
    auto listener1 = sEventHelper->AddCustomEvent(NetWork_ConnectSucceed, CC_CALLBACK_1(SocketHelper::ConnectSucceed, this), 1);
    auto listener2 = sEventHelper->AddCustomEvent(NetWork_ConnectFailed, CC_CALLBACK_1(SocketHelper::ConnectFailed, this), 1);
    auto listener3 = sEventHelper->AddCustomEvent(NetWork_ConnectMessageData, CC_CALLBACK_1(SocketHelper::MessageData, this), 1);
    auto listener4 = sEventHelper->AddCustomEvent(NetWork_ConnectClose, CC_CALLBACK_1(SocketHelper::Close, this), 1);
    ListenerVect.push_back(listener1);
    ListenerVect.push_back(listener2);
    ListenerVect.push_back(listener3);
    ListenerVect.push_back(listener4);
}

//連線失敗
void SocketHelper::ConnectFailed(EventCustom* event)
{
    socketState = SOCKSTAT_FAILED;
}

void SocketHelper::Close(EventCustom* event)
{
    socketState = SOCKSTAT_CONNECTED;
}

//-----------測試介面
void SocketHelper::test_reconnect()
{
    mKimoSocket->reConnect();
}
 

//=========================================

 

 

相關文章