close

C++ Socket資料整理

Server 端執行流程:

Step1.  link Winsock Library(windows環境下)

Step2.  初始化 Windows Sockets DLL(windows環境下)

Step3.  使用 socket() 建立 socket descriptor  

Step4.  設定 Server 位址資訊

Step5.  設定 listen() -> 等待連線 -> 接受連線 accect()

Step6.  傳送接收資料

 

Client 端執行流程:

Step1.  link Winsock Library(windows環境下)

Step2.  初始化 Windows Sockets DLL(windows環境下)

Step3.  使用 socket() 建立 socket descriptor  

Step4.  設定 Server 位址資訊

Step5.  連線到 Server connect()

Step6.  傳送接收資料

 

 

[server] [client]

Step1. Windows 使用 Socket 需要 link Winsock Library

 

link方式:

專案的「屬性」 ->「組態屬性」->「連結器」->「輸入」->「其他相依性」加入 wsock32.lib   Ws2_32.lib

 

也可以在程式中,使用以下方式加入#pragma comment(lib, "wsock32.lib") #pragma comment(lib, "Ws2_32.lib")

 

wsock32.lib Ws2_32.lib 的區別:

wsock32.lib 是較舊的 1.1 版本,Ws2_32.lib 是較新的 2.0 版本。

wsock32.lib winsock.h 一起使用,Ws2_32.lib WinSock2.h 一起使用。

winsock.h WinSock2.h 不能同時使用,WinSock2.h 是用來取代 winsock.h,而不是擴展 winsock.h

 

 

 

 

[server] [client]

Step2. 初始化 Windows Sockets DLL

範例:

 

WSAData wsaData;

WORD version = MAKEWORD(2, 2); // 版本

int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // 成功回傳 0

if (iResult != 0) {

    // 初始化Winsock 失敗

}

 

 

[server] [client]

Step3. 建立socket描述符 (socket descriptor)

 

函式 : SOCKET socket (int af,int type, int protocol);

 

Int af : 使用何種通訊家族.

     

例如:

AF_INET:使用IPv4

AF_INET6:使用IPv6

 

Int type: the type specification for the new socket.

  1. 能使用的值跟第1個參數有關.
  2. 例如 :

SOCKET_STREAM : 使用 TCP 協議

SOCKET_DGRAM : 使用UDP協議

Int protocol : The protocol to be used.

  1. 能用的值跟前面兩個參數有關
  2. 例如:

IPPROTO_TCP : 使用TCP

IPPROTO_UDP : 使用UDP

 

成功回傳 socket descriptor,失敗回傳INVALID_SOCKET

範例:

SOCKET sListen = INVALID_SOCKET;

sListen= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (sListen== INVALID_SOCKET) {

    // 建立失敗

}

 

 

[server][client]

Step4. 設定位址資訊的資料 ( SOCKADDR_IN)

結構: 使用IP4格式結構struct sockaddr_in (in 表示 internet)設定internet位址資訊。

範例:

SOCKADDR_IN addr;

memset (&addr, 0, sizeof (addr)) ; // 清空,將資料設為 0

addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 設定 IP,後面解釋 inet_addr()

// addr.sin_addr.s_addr = INADDR_ANY; // 若設定 INADDR_ANY 表示任何 IP

addr.sin_family = AF_INET;

addr.sin_port = htons(1234); // 設定 port,後面解釋 htons()



 

 

 

struct sockaddr_in : IP4格式使用

struct sockaddr_in6: IP6格式使用

struct sockaddr : 通用格式

struct sockaddr_un : UNIX domain 格式

 

htons()

使用 htons() 是因為網路位元順序 (Network Byte Order, 縮寫NBO) 可能跟電腦主機位元順序(Host Byte Order, 縮寫HBO)不同,所以需要要函式來轉換,以增加移植性。

htons()"h(host)" "to" "n(network)" "s(short)"Host to Network Short,將 short (2byte) 資料順序從 host 轉換至 network

htonl()Host to Network Long,將 long(4byte) 資料順序從 host 轉換至 network

ntohs()Network to Host Short,將 short (2byte) 資料順序從 network 轉換至 host

ntohl()Network to Host Long,將 long(4byte) 資料順序從 network 轉換至 host

 

Inet_addr()

IP 轉換為 unsigned long 格式,轉換後,已是網路位元順序 (Network Byte Order,所以不用再使用 htons()

 

 

 

 

[Server]

Step5. 綁定socket的位址資訊(bind)

函式:int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

int sockfdsocket() 函式回傳的 socket descriptor

struct sockaddr *my_addr:用來通訊的位址資料(IPPORT)

int addrlen:位址長度 sizeof(my_addr)

綁定成功回傳 0,失敗回傳 SOCKET_ERROR

範例:

// addr 為 sockaddr_in 結構,強制轉型為 sockaddr 結構

int r = bind(sListen, (SOCKADDR*)&addr, sizeof(addr));

assert (r != SOCKET_ERROR);

 

 

 

 

[server]

Step6. 監聽連線 (listen)

函式:int listen(SOCKET s,  int backlog)

SOCKET ssocket() 函式回傳的 socket descriptor

int backlog:最大可監聽多少連線(佇列、排隊)。設定 SOMAXCONN 表示系統最大值。

成功回傳 0,失敗回傳 SOCKET_ERROR

connection-oriented ( SOCK_STREAM ) server 程式端程式才使用。

 

範例:

// addr 為 sockaddr_in 結構,強制轉型為 sockaddr 結構

int r = listen(sListen, SOMAXCONN);

assert (r != SOCKET_ERROR);

 

 

 

 

[server]

Step7.處理連線 (accept)

函式:SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen)

SOCKET ssocket() 函式回傳的 socket descriptor

struct sockaddr *addr:結果參數,預先配置 SOCKADDR 結構的指標,用來存放客戶端位址,不存放可設置為 NULL

int *addrlen:結果參數,第二個參數的大小,不存放也可以設為 NULL

成功回傳 socket descriptor,失敗回傳 INVALID_SOCKET,可用 WSAGetLastError() 取得 error code

所以成功時,會產生一個新的 socket descriptor,而原本的 socket descriptor 持續監聽原來的端口。

connection-oriented ( SOCK_STREAM ) server 程式端程式才使用。

範例:

SOCKET sConnect;

struct sockaddr_in clientAddr; // client 端位址資訊

int clientAddrLen = sizeof(clientAddr);

sConnect = accept(sListen, (SOCKADDR*)&clientAddr, &clientAddrLen);

// sConnect = accept(sListen, NULL, NULL);

if (sConnect != INVALID_SOCKET)

{

    // 有 client 端成功連線過來

    printf("server: got connection from %s", inet_ntoa(clientAddr.sin_addr));

}

 

 

 

[client]

Step8. 連線到 socket Server

函式:int connect(SOCKET s, const struct sockaddr *name, int namelen)

SOCKET ssocket() 函式回傳的 socket descriptor

const struct sockaddr *nameServer 端的位址資料。

int namelen:第二個參數的大小。

成功回傳 0,失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code

 

int r = connect(sConnect, (SOCKADDR*)&addr, sizeof(addr));
if(r != SOCKET_ERROR){
    // 連線成功
}

 

 

 

[server][client]

Step9. 傳送訊息
 

函式 1int send(SOCKET s, const char *buf, int len, int flags)

函式 2int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen)

send() sendto() 的差異在於,send() 只能用在 connection-oriented ( SOCK_STREAM ) 的連線,所以 sendto() send() 多最後兩個參數,用來指定目的地的位址資訊。

SOCKET ssocket() 函式回傳的 socket descriptor

const char *buf:訊息的指標。

int len:訊息的長度。

int flagsThe flags parameter can be used to influence the behavior of the function beyond the options specified for the associated socket(一般設 0)

MSG_DONTROUTE:不將訊息送給 gateway,而直接送給 host(Specifies that the data should not be subject to routing. A Windows Sockets service provider can choose to ignore this flag.)

MSG_OOBSends OOB data (stream-style socket such as SOCK_STREAM only)

const struct sockaddr *to:目的地位址資訊。

int tolen:目的地位址資訊的大小。

成功回傳傳送的資料長度,失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code

 

char *sendbuf = "sending data test";

send(sConnect, sendbuf, (int)strlen(sendbuf), 0);

 

 

[server][client]

Step10. 接收訊息

 

函式 1int recv(SOCKET s, char *buf, int len, int flags)

函式 2int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)

recv() recvfrom() 的差異在於,recv() 只能用在 connection-oriented ( SOCK_STREAM ) 的連線,所以 recvfrom() recv() 多最後兩個參數,用來指定接收來源的位址資訊。

SOCKET ssocket() 函式回傳的 socket descriptor

const char *buf:訊息的指標。

int len:訊息的長度。

int flagsThe flags parameter can be used to influence the behavior of the function invocation beyond the options specified for the associated socket.(一般設 0)

MSG_PEEKPeeks at the incoming data。只看訊息內容,但不將訊息從 queue 移除。

MSG_OOBProcesses Out Of Band (OOB) data

const struct sockaddr *to:目的地位址資訊。

int tolen:目的地位址資訊的大小。

成功回傳接收的資料長度,連線被關閉回傳 0。失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code

char message[200];

ZeroMemory(message, 200);

recv(sConnect, message, sizeof(message), 0);

 

 

 

[server] [client]

Step11. 設定 socket option 選項。

函式 int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)

用來設定建立的  socket 一些特性,例如是否強制關閉等。

使用範例可參考: https://blog.csdn.net/qinmi/article/details/1523081

 

 

 

[server] [client]

Step12. 關閉 socket

函式 int closesocket(SOCKET s)

 

 

 

 

 

 

 

 

 

[server Sample Code]

 

#pragma comment(lib, "Ws2_32.lib")
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#include <string>

using namespace std;


#define        PORT_NUM        (9527)

int main()
{

    /*
        struct WSAData 
        {  
            WORD            wVersion;  
            WORD            wHighVersion;  
            char            szDescription[WSADESCRIPTION_LEN+1];  
            char            szSystemStatus[WSASYSSTATUS_LEN+1];  
            unsigned short    iMaxSockets;  
            unsigned short    iMaxUdpDg;  
            char FAR*        lpVendorInfo;  
        };  

        參數
        wVersion
        Windows 通訊端 DLL 要求呼叫端使用的 Windows 通訊端規格版本。

        wHighVersion
        這個 DLL 可支援的最高 Windows 通訊端規格版本 (編碼如上所示)。 通常,這會與相同wVersion。

        szDescription
        Windows 通訊端 DLL 在以 null 終止之 ASCII 字串複製 Windows Sockets 實作的描述,包括廠商身分識別。 文字 (長度最多 256 個字元) 可以包含任何字元,不過,廠商會被告誡包含控制項和格式化字元:應用程式最可能將此值顯示 (可能以截斷方式) 在狀態訊。

        szSystemStatus
        Windows 通訊端 DLL 在以 null 終止之 ASCII 字串複製相關狀態或組態資訊。 Windows Sockets DLL 的資訊可能會有所幫助使用者或支援人員; 時,才使用這個欄位不應視為的延伸szDescription欄位。

        iMaxSockets
        單一處理序可能會開啟的最大通訊端數目。 Windows 通訊端實作可提供通訊端全域集區以配置到任何處理序,或者也依照通訊端的每個處理序資源進行配置。 數字完全反映 Windows 通訊端 DLL 或網路軟體組態的設定方式。 應用程式撰寫者可以使用這個數字當成 Windows Sockets 實作是否可由應用程式使用的指示。 例如,X Windows 伺服器可能會檢查iMaxSockets第一次啟動︰ 如果少於 8,應用程式會顯示錯誤訊息指示使用者重新設定網路軟體。 (這是情況szSystemStatus文字可能會使用。)很顯然並不保證特定應用程式可以實際配置iMaxSockets通訊端,因為可能有其他使用中的 Windows Sockets 應用程式。

        iMaxUdpDg
        可由 Windows 通訊端應用程式傳送或接收的最大使用者資料包通訊協定 (UDP) 資料包的位元組大小。 如果實作沒有施加限制, iMaxUdpDg為零。 在 Berkeley 通訊端的許多實作中,UDP 資料包上有 8192 位元組的隱含限制 (必要時會分散)。 Windows 通訊端實作可以根據片段重組緩衝區的配置施加限制。 最小值iMaxUdpDg相容 Windows 通訊端實作是 512。 請注意,不論值iMaxUdpDg,不得嘗試傳送大於比最大傳輸單位 (MTU) 的網路廣播的資料包。 (Windows 通訊端 API 不提供機制來尋找 MTU,不過不得少於 512 位元組)。

        lpVendorInfo
        廠商特定資料結構的遠端指標。 這個結構的定義 (如果有提供) 超出 Windows 通訊端規格的範圍。
    */
    WSADATA wsaData;
    WORD    DLLVersion;
    DLLVersion = MAKEWORD(2,1);//winsocket-dll version


    // 用 WSAStartup 開始 Winsocket-DLL
    int err = WSAStartup(DLLVersion,&wsaData);
    if (err!=0)
    {
        // Tell the user that we could not find a usable Winsock DLL.
        printf("WSAStartup failed with error: %d\n",err);
        return 1;
    }
    if (LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=1)
    {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */
        printf("Could not find a usable version of Winsock.dll\n");
        WSACleanup();
        return 1;
    }
    else
    {
        printf("The Winsock 2.1 dll was found okay\n");
    }


    // 宣告 socket位址資訊 (不同的通訊,有不同的位址資訊,所以會有不同的資料結構存放這些位址資訊)
    /*
    /*
     *
     * Socket address, internet style. 
        struct sockaddr_in 
        {
                short   sin_family;
                u_short sin_port;
                struct  in_addr sin_addr;
                char    sin_zero[8];
        };
        #ifndef s_addr
    
     * Internet address (old style... should be updated)

        struct in_addr 
        {
            union 
            {
                    struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                    struct { u_short s_w1,s_w2; } S_un_w;
                    u_long S_addr;
            } S_un;
        }
    */
    SOCKADDR_IN    addr;
    int addrlen = sizeof(addr);

    // Create socket
    SOCKET sListen        ;        //listening for an incoming connection
    SOCKET sConnection    ;        //oerating if a connection was found

    // AF_INET        :  表示建立的Socket屬於internet family
    // SOCK_STREAM    :  表示建立的socket是connection-oriented socket
    sConnection = socket(AF_INET,SOCK_STREAM,NULL);

    // 設定位址資訊的資料
    addr.sin_addr.s_addr    = inet_addr("127.0.0.1");
    addr.sin_family            = AF_INET;
    addr.sin_port            = htons(PORT_NUM);


    // 設定Listen
    sListen = socket(AF_INET,SOCK_STREAM,NULL);
    if (sListen == INVALID_SOCKET)
    {
        printf("socket function failed with error : %u \n",WSAGetLastError());
        WSACleanup();
        return 1;
    }
    // Bind the socket
    int iResult = bind (sListen,(SOCKADDR*)&addr,sizeof(addr));
    if (iResult == SOCKET_ERROR)
    {
        printf("Bind failed with error : %u \n", WSAGetLastError());
        closesocket(sListen);
        WSACleanup();
        return 1;
    }

    //SOMAXCONN: listening without any limit
    if(listen(sListen, SOMAXCONN) == SOCKET_ERROR)
    {
        printf("listen function failed with error: %d \n", WSAGetLastError());
        closesocket(sListen);
        WSACleanup();
        return 1;

    }
    else
        printf("Listening on socket...\n");

 
    // 等待連線
    SOCKADDR_IN clientAddr;
    while (1)
    {
        cout << "Waitting for connect... "<<endl;
        if(sConnection = accept(sListen,(SOCKADDR*)&clientAddr,&addrlen))
        {
            cout << "a connection was found."<<endl;
            printf("Server : got a connection from : %s\n",inet_ntoa(addr.sin_addr));

            //Send message to client 
            char *sendbuf = "sending data test";
            printf("Send buf to client (0x%x) \n", &sendbuf);

            //----------------------
            // Send an initial buffer
            iResult = send(sConnection,sendbuf,(int)strlen(sendbuf),0);
            if (iResult == SOCKET_ERROR)
            {

                printf("send failed with error :%d \n", WSAGetLastError());
                closesocket(sConnection);
                WSACleanup();
                return 1;
            }            
        }
    }
 
    getchar();
    getchar();
}


 

 

 

 

 [clientSample Code]

 

#pragma comment(lib, "Ws2_32.lib")

#include <WinSock2.h>
#include <iostream>
#include <string>

using namespace std;


#define        PORT_NUM        (9527)


void main()
{
    string confirm;
    char message[200];

    //開始 Winsock-DLL
    int r;
    WSAData wsaData;
    WORD DLLVersion;
    DLLVersion = MAKEWORD(2,1);
    r = WSAStartup(DLLVersion, &wsaData);

    //宣告給 socket 使用的 sockadder_in 結構
    SOCKADDR_IN addr;

    int addlen = sizeof(addr);

    //設定 socket
    SOCKET sConnect;

    //AF_INET: internet-family
    //SOCKET_STREAM: connection-oriented socket
    sConnect = socket(AF_INET, SOCK_STREAM, NULL);

    //設定 addr 資料
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT_NUM);

    cout << "connect to server?[Y] or [N]" << endl;
    cin >> confirm;

    if(confirm == "N")
    {
        exit(1);
    }else{
        if(confirm == "Y")
        {
            connect(sConnect, (SOCKADDR*)&addr, sizeof(addr));

            //接收 server 端的訊息
            ZeroMemory(message, 200);
            r = recv(sConnect, message, sizeof(message), 0);
            cout << message << endl;

            //設定 closesocket 時,不經過 TIME-WAIT 過程,直接關閉socket
            //BOOL bDontLinger = FALSE;
            //setsockopt(sConnect,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

            //若之後不再使用,可用 closesocket 關閉連線
            closesocket(sConnect);

            getchar();
            getchar();
        }
    }

}

 

 

 

 

 

 

 

 

 

arrow
arrow
    文章標籤
    C++ C socket c++ socket
    全站熱搜
    創作者介紹
    創作者 Eric 的頭像
    Eric

    一個小小工程師的心情抒發天地

    Eric 發表在 痞客邦 留言(0) 人氣()