[C++轉C#]Socket部分- C++總覽

目的:如題,Socket通訊部分要從C++轉換到C#
期限:12月底

方法:確認C++和C#在socket通訊上的差異
以下蒐集各方大大們(鞠躬)的資料:

一、關於C++的socket通訊

[轉自XYZ的筆記本]

Server 端執行流程:
link Winsock Library(windows環境下) -> 初始化 Windows Sockets DLL(windows環境下) -> 使用 socket() 建立 socket descriptor -> 設定 Server 位址資訊 -> 設定 listen() -> 等待連線 -> 接受連線 accect() -> 傳送接收資料

Client 端執行流程:
link Winsock Library(windows環境下) -> 初始化 Windows Sockets DLL(windows環境下) -> 使用 socket() 建立 socket descriptor -> 設定 Server 位址資訊 -> 連線到 Server 端 connect() -> 傳送接收資料
測試環境 Visual Studio Express 2012
在 Windows 使用 Socket 需要 link Winsock Library。[server端] [client端]
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。

初始化 Windows Sockets DLL。[server端] [client端]
範例:

WSAData wsaData;
WORD version = MAKEWORD(2, 2); // 版本
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData); // 成功回傳 0
if (iResult != 0) {
    // 初始化Winsock 失敗
}

*建立 socket 描述符 (socket descriptor)。[server端] [client端] 函式: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 個參數有關
例如 SOCK_STREAM:使用 TCP 協議 SOCK_DGRAM:使用 UDP 協議 int protocol:The protocol to be used.
能用的值跟前面兩個參數有關
例如 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) {
    // 建立失敗
}

設定位址資訊的資料 (SOCKADDR_IN)。[server端] [client端] *結構:
使用 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()

綁定 socket 的位址資料 (bind)。[server端]
函式:int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
int sockfd:socket() 函式回傳的 socket descriptor
struct sockaddr *my_addr:用來通訊的位址資料(IP、PORT)
int addrlen:位址長度 ,sizeof(my_addr)
綁定成功回傳 0,失敗回傳 SOCKET_ERROR
範例:

// addr 為 sockaddr_in 結構,強制轉型為 sockaddr 結構
int r = bind(sListen, (SOCKADDR*)&addr, sizeof(addr));
assert (r != SOCKET_ERROR);

監聽連線 (listen)。[server端]
函式:int listen(SOCKET s, int backlog) SOCKET s:socket() 函式回傳的 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);

處理連線 (accept)。[server端]

函式:SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen) SOCKET s:socket() 函式回傳的 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));
}

連線到 socket Server。[client端]
函式:int connect(SOCKET s, const struct sockaddr *name, int namelen) SOCKET s:socket() 函式回傳的 socket descriptor。 const struct sockaddr *name:Server 端的位址資料。 int namelen:第二個參數的大小。 成功回傳 0,失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code。


範例:

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

傳送訊息。[server端] [client端]
函式 1:
int send(SOCKET s, const char *buf, int len, int flags)
函式 2:
int 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 s:socket() 函式回傳的 socket descriptor。 const char *buf:訊息的指標。 int len:訊息的長度。 int flags:The 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_OOB:Sends OOB data (stream-style socket such as SOCK_STREAM only)。 const struct sockaddr *to:目的地位址資訊。 int tolen:目的地位址資訊的大小。 成功回傳傳送的資料長度,失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code。

範例:

1
2
char *sendbuf = "sending data test";
send(sConnect, sendbuf, (int)strlen(sendbuf), 0);

接收訊息。[server端] [client端]
函式 1:int recv(SOCKET s, char *buf, int len, int flags)
函式 2:int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
recv() 和 recvfrom() 的差異在於,recv() 只能用在 connection-oriented ( SOCK_STREAM ) 的連線,所以 recvfrom() 比 recv() 多最後兩個參數,用來指定接收來源的位址資訊。
SOCKET s:socket() 函式回傳的 socket descriptor。
const char *buf:訊息的指標。
int len:訊息的長度。
int flags:The flags parameter can be used to influence the behavior of the function invocation beyond the options specified for the associated socket.。(一般設 0)
MSG_PEEK:Peeks at the incoming data。只看訊息內容,但不將訊息從 queue 移除。
MSG_OOB:Processes Out Of Band (OOB) data。
const struct sockaddr *to:目的地位址資訊。
int tolen:目的地位址資訊的大小。
成功回傳接收的資料長度,連線被關閉回傳 0。失敗回傳 SOCKET_ERROR,可用 WSAGetLastError() 取得 error code。

範例:

1
2
3
char message[200];
ZeroMemory(message, 200);
recv(sConnect, message, sizeof(message), 0);


  1. 設定 socket option 選項。[server端] [client端]
    函式 :int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)
    用來設定建立的  socket 一些特性,例如是否強制關閉等。
    使用範例可參考:setsockopt 设置socket - 驱动开发 - 博客频道 - CSDN.NET
關閉 socket。[server端] [client端]
函式 :int closesocket(SOCKET s)


Scoket Server 範例:
執行後會等待 client 端連線,有連線進來時,則傳送 "sending data test" 訊息給 client 端。

#pragma comment(lib, "Ws2_32.lib")
 
#include <WinSock2.h>
#include <iostream>
 
using namespace std;
 
void main()
{
    int r;
    WSAData wsaData;
    WORD DLLVSERION;
    DLLVSERION = MAKEWORD(2,1);//Winsocket-DLL 版本
 
    //用 WSAStartup 開始 Winsocket-DLL
    r = WSAStartup(DLLVSERION, &wsaData);
 
    //宣告 socket 位址資訊(不同的通訊,有不同的位址資訊,所以會有不同的資料結構存放這些位址資訊)
    SOCKADDR_IN addr;
    int addrlen = sizeof(addr);
 
    //建立 socket
    SOCKET sListen; //listening for an incoming connection
    SOCKET sConnect; //operating if a connection was found
 
    //AF_INET:表示建立的 socket 屬於 internet family
    //SOCK_STREAM:表示建立的 socket 是 connection-oriented socket
    sConnect = 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(1234);
 
    //設定 Listen
    sListen = socket(AF_INET, SOCK_STREAM, NULL);
    bind(sListen, (SOCKADDR*)&addr, sizeof(addr));
    listen(sListen, SOMAXCONN);//SOMAXCONN: listening without any limit
 
    //等待連線
    SOCKADDR_IN clinetAddr;
    while(true)
    {
        cout << "waiting..." << endl;
 
        if(sConnect = accept(sListen, (SOCKADDR*)&clinetAddr, &addrlen))
        {
            cout << "a connection was found" << endl;
            printf("server: got connection from %s\n", inet_ntoa(addr.sin_addr));
 
            //傳送訊息給 client 端
            char *sendbuf = "sending data test";
            send(sConnect, sendbuf, (int)strlen(sendbuf), 0);
             
        }
    }
 
    //getchar();
}


Socket Client 範例:
執行後,按 "Y" 連線到 Server 端,並接收 Server 端傳過來的訊息。



#pragma comment(lib, "Ws2_32.lib")
 
#include <WinSock2.h>
#include <iostream>
#include <string>
 
using namespace std;
 
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(1234);
 
    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();
        }
    }
 
}



(感謝大大!!)







留言

熱門文章