Windows Socket 2の基礎

# アーキテクチャ
`WS2_32.DLL`を通じて`Windows Sockets 2 API`を活用し、プロトコルスタックと通信する。
> [Windows ソケット 2のアーキテクチャ](https://learn.microsoft.com/ja-jp/windows/win32/winsock/windows-sockets-2-architecture-2) 

# winsockの使い方
基本的には`winsock2.h`と`ws2tcpip.h`をインクルードして利用する。また、`#pragma comment(lib, "Ws2_32.lib")`も宣言してスタティックリンクを行う。

`ws2tcpip.h`は接続先を指定する際に利用する`InetPton`関数を利用するために必要なヘッダーファイルである。
> Ws2tcpip.h ヘッダー ファイルには、TCP/IP 用の WinSock 2 Protocol-Specific Annex ドキュメントで導入された定義が含まれています。これには、IP アドレスの取得に使用される新しい関数と構造体が含まれています。

また、`Windows.h`はロードする必要はありません。
> Winsock2.h ヘッダー ファイルには内部的に Windows.h ヘッダー ファイルのコア要素が含まれているため、Winsock アプリケーションでは通常、Windows.h ヘッダー ファイルの#include行はありません。 Windows.h ヘッダー ファイルに#include行が必要な場合は、その前に #define WIN32_LEAN_AND_MEAN マクロを付ける必要があります。

```c
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "Ws2_32.lib")
```
> * [Winsockの概要](https://learn.microsoft.com/ja-jp/windows/win32/winsock/getting-started-with-winsock)
> * [基本的な Winsock アプリケーションの作成](https://learn.microsoft.com/ja-jp/windows/win32/winsock/creating-a-basic-winsock-application)

# サーバー モデル
一般的なモデルは以下の通り
1. Winsock を初期化します。
1. ソケットを作成します。
1. ソケットをバインドします。
1. クライアントのソケットでリッスンします。
1. クライアントからの接続を受け入れます。
1. データの受信と送信。
1. 切断します。

## Winsockの初期化
Windowsソケットの実装に関する情報が含まれる`WSADATA`構造体を準備したのち、`WSAStartup`関数を用いてプロセスがWinsock DLLを使用できるようにする。

`WSAStartup`関数の戻り値は`int型`であり、成功した場合は`0`を、失敗した場合はエラーコードを返す。

```c
WSADATA wsaData;

int iResult;

// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
}
```
> * [Winsockの初期化](https://learn.microsoft.com/ja-jp/windows/win32/winsock/initializing-winsock)
> * [WSADATA 構造体](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock/ns-winsock-wsadata)
> * [WSAStartup関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock/nf-winsock-wsastartup)

## ソケットの作成
1. 構造体を準備し名前解決を行う  
    * `ZeroMemory`関数を実行してaddrinfo構造体のインスタンス化により確保された領域をすべて0で埋める
        * **これを実行しないと`getaddrinfo`関数はエラーを起こしてしまうため絶対に実施すること**
    * `getaddrinfo`関数の第1引数には名前解決したいホスト名を、第2引数にはサービス名もしくはポート番号を格納する
    ```c
    struct addrinfo *result = NULL, hints;

    ZeroMemory(&hints, sizeof (hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the local address and port to be used by the server
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }
    ```
    > * [addrinfo構造体](https://learn.microsoft.com/ja-jp/windows/win32/api/ws2def/ns-ws2def-addrinfoa)
    > * [getaddrinfo関数](https://learn.microsoft.com/ja-jp/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo)

2. リッスン用ソケットオブジェクトの作成  
    ソケット作成と統合してもよい
    ```c
    SOCKET ListenSocket = INVALID_SOCKET;
    ```
3. ソケットの作成
    ```c
    // Create a SOCKET for the server to listen for client connections
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    ```
    > * [socket関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-socket)

> [サーバーのソケットの作成](https://learn.microsoft.com/ja-jp/windows/win32/winsock/creating-a-socket-for-the-server)

## ソケットのバインド
サーバーの場合は作成したソケットをネットワークアドレス（IPアドレスとポート）にバインドする必要がある。また、バインド後は`getaddrinfo`関数によって返された情報は不要になるため`freeaddrinfo`関数でメモリ領域を解放する。

```c
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    printf("bind failed with error: %d\n", WSAGetLastError());
    freeaddrinfo(result);
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
```
> * [ソケットのバインド](https://learn.microsoft.com/ja-jp/windows/win32/winsock/binding-a-socket)
> * [freeaddrinfo関数](https://learn.microsoft.com/ja-jp/windows/win32/api/ws2tcpip/nf-ws2tcpip-freeaddrinfo)

## ソケットでのリッスン
* Listen関数を用いてソケットをリッスン状態にする。
* Listen関数の第2引数は保留中の接続のキューの最大長を示している。`SOMAXCONN`を指定すると最大の妥当な値に設定してくれる。

```c
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) {
    printf( "Listen failed with error: %ld\n", WSAGetLastError() );
    closesocket(ListenSocket);
    WSACleanup();
    return 1;
}
```
> [listen関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-listen)

## 接続の受け入れ
通常はパフォーマンス向上のためマルチスレッドで行う。また、複数の接続を受け付けるためにはループを用いて接続要求をチェックする。

1. クライアントからの接続受け入れ用ソケットの作成（手順2に統合してもよい）
    ```c
    SOCKET ClientSocket;
    ```
2. 接続を受信したら許可する（シングルスレッド）
    ```c
    ClientSocket = INVALID_SOCKET;

    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    ```
> * [接続の受け入れ](https://learn.microsoft.com/ja-jp/windows/win32/winsock/accepting-a-connection)

## サーバーでのデータの受信と送信
* `recv`関数でデータを受信しデータを第2引数で指定したchar型のバッファーへ格納する。
    * エラーが発生していなければ**戻り値は受信したバイト数を返す**
    * **接続が正常に閉じられている場合は戻り値は0**となる
* `send`関数で第2引数で指定したchar型バッファーに格納されているデータを送信する。

下記コードは受信したデータをそのまま送り返すものである
```c
// Since the maximum value of MTU is 1500 bytes
#define DEFAULT_BUFLEN 1500

char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;

// Receive until the peer shuts down the connection
do {

    iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0) {
        printf("Bytes received: %d\n", iResult);

        // Echo the buffer back to the sender
        iSendResult = send(ClientSocket, recvbuf, iResult, 0);
        if (iSendResult == SOCKET_ERROR) {
            printf("send failed: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
        printf("Bytes sent: %d\n", iSendResult);
    } else if (iResult == 0)
        printf("Connection closing...\n");
    else {
        printf("recv failed: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

} while (iResult > 0);
```

また、バッファをリセットしないと受信データが前回より少ない場合は前回のデータが残ってしまうため毎回リセットするのが推奨される。
```c
memset(recvbuf, '\0', sizeof(recvbuf));
```

> * [サーバーでのデータの受信と送信](https://learn.microsoft.com/ja-jp/windows/win32/winsock/receiving-and-sending-data-on-the-server)
> * [recv関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock/nf-winsock-recv)
> * [send関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-send)
> * [memset関数](https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/memset-wmemset?view=msvc-170)
> * [NULLと'\0'の違い](http://www.kis-lab.com/serikashiki/C/C01.html)

## サーバーの切断
* `shutdown`関数を用いてクライアントと接続されている**ソケットの送信側**を終了させる
* 送信側を終了すると引き続きデータを受信できるようになるため、ソケットの受信側も終了させる

```c
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ClientSocket);
    WSACleanup();
    return 1;
}

// cleanup
closesocket(ClientSocket);
WSACleanup();

return 0;
```

> * [サーバーの切断](https://learn.microsoft.com/ja-jp/windows/win32/winsock/disconnecting-the-server)
> * [shutdown関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock/nf-winsock-shutdown)

## 関連記事
* [Windows SocketsでTCP通信とUDP通信を実装したプログラムをリバーシングしてみる](https://kashiwaba-yuki.com/windows-windbg-010-socket)

# クライアントモデル
1. Winsock を初期化します。
1. ソケットを作成します。
1. サーバーに接続します。
1. データの送受信。
1. 切断します。

## Winsockの初期化
サーバー モデルの「Winsockの初期化」と同じであるため記載省略。詳細はサーバー モデルを参照すること。

## ソケットの作成
1. 構造体を準備し名前解決を行う  
    * `ZeroMemory`関数を実行してaddrinfo構造体のインスタンス化により確保された領域をすべて0で埋める
        * **これを実行しないと`getaddrinfo`関数はエラーを起こしてしまうため絶対に実施すること**
    * `getaddrinfo`関数の第1引数には名前解決したいホスト名を、第2引数にはサービス名もしくはポート番号を格納する
    ```c
    struct addrinfo *result = NULL, *ptr = NULL, hints;

    ZeroMemory( &hints, sizeof(hints) );
    hints.ai_family   = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }
    ```
    > * [addrinfo構造体](https://learn.microsoft.com/ja-jp/windows/win32/api/ws2def/ns-ws2def-addrinfoa)
    > * [getaddrinfo関数](https://learn.microsoft.com/ja-jp/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo)

2. リッスン用ソケットオブジェクトの作成  
    ソケット作成と統合してもよい
    ```c
    SOCKET ConnectSocket = INVALID_SOCKET;
    ```
3. ソケットの作成
    ```c
    // Attempt to connect to the first address returned by
    // the call to getaddrinfo
    ptr=result;

    // Create a SOCKET for connecting to server
    ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
    ```
    > * [socket関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-socket)

> * [クライアントのソケットの作成](https://learn.microsoft.com/ja-jp/windows/win32/winsock/creating-a-socket-for-the-client)

## ソケットへの接続
* `connect`関数を用いて、作成されたソケットと`getaddrinfo`関数にてパラメーターが設定された`sockaddr`構造体を渡してサーバーと接続する。
* getaddrinfo関数によって返された情報は不要になるため`freeaddrinfo`関数でメモリ領域を解放する。

```c
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
    closesocket(ConnectSocket);
    ConnectSocket = INVALID_SOCKET;
}

// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message

freeaddrinfo(result);

if (ConnectSocket == INVALID_SOCKET) {
    printf("Unable to connect to server!\n");
    WSACleanup();
    return 1;
}
```

> * [ソケットへの接続](https://learn.microsoft.com/ja-jp/windows/win32/winsock/connecting-to-a-socket)
> * [connect関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-connect)

なお、別のアプリケーションがソケットを経由してやり取りを行う場合は`WSAconnect`を用いること。  
`socket`関数のみで行おうとする場合、非常に実装が難しく改行の扱いなどもこちら側で実装する必要があるためである。

> WSAConnect 関数は、別のソケット アプリケーションへの接続を確立し、接続データを交換し、指定された FLOWSPEC 構造体に基づいて必要なサービス品質を指定します。
> * [WSAConnect関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-wsaconnect)

## クライアントでのデータの送受信
* `recv`関数でデータを受信しデータを第2引数で指定したchar型のバッファーへ格納する。
    * エラーが発生していなければ**戻り値は受信したバイト数を返す**
    * **接続が正常に閉じられている場合は戻り値は0**となる
* `send`関数で第2引数で指定したchar型バッファーに格納されているデータを送信する。

```c
#define DEFAULT_BUFLEN 512

int recvbuflen = DEFAULT_BUFLEN;

const char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];

int iResult;

// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
    printf("send failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

printf("Bytes Sent: %ld\n", iResult);

// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

// Receive data until the server closes the connection
do {
    iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
    if (iResult > 0)
        printf("Bytes received: %d\n", iResult);
    else if (iResult == 0)
        printf("Connection closed\n");
    else
        printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);
```

また、バッファをリセットしないと受信データが前回より少ない場合は前回のデータが残ってしまうため毎回リセットするのが推奨される。
```c
memset(recvbuf, '\0', sizeof(recvbuf));
```

> * [クライアントでのデータの送受信](https://learn.microsoft.com/ja-jp/windows/win32/winsock/sending-and-receiving-data-on-the-client)
> * [recv関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock/nf-winsock-recv)
> * [send関数](https://learn.microsoft.com/ja-jp/windows/win32/api/winsock2/nf-winsock2-send)
> * [memset関数](https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/memset-wmemset?view=msvc-170)
> * [NULLと'\0'の違い](http://www.kis-lab.com/serikashiki/C/C01.html)

## クライアントの切断
* `shutdown`関数を用いてサーバーと接続されている**ソケットの送信側**を終了させる
* 送信側を終了後、ソケットの受信側も終了させる
```c
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ConnectSocket);
    WSACleanup();
    return 1;
}

// cleanup
closesocket(ConnectSocket);
WSACleanup();

return 0;
```
> * [クライアントの切断](https://learn.microsoft.com/ja-jp/windows/win32/winsock/disconnecting-the-client)