hozugawa.net · A neutral informational page

WSAGetLastErrorとWSAStartupでWinsockを初期化する

WindowsでTCP/IP通信やUDP通信を実装するには、ソケット関数を呼び出す前にWinsockの利用準備を整える必要があります。中心となるのが、Winsock DLLを初期化するWSAStartup関数と、直前の関数呼び出しで発生した通信エラーを取得するWSAGetLastError関数です。

この2つは役割が異なります。WSAStartupは通信機能を使える状態にする初期化処理、WSAGetLastErrorは失敗原因を調べるためのエラーコード取得処理です。初期化の戻り値を確認せずにソケットを作成すると、原因が分かりにくいWSANOTINITIALISEDが発生することがあります。

Windows APIの呼び出し規約やエラー処理を整理するときは、関連するAPIの説明をまとめたWindows API資料も参照できます。Winsock固有の規則を理解し、初期化、通信、終了処理を一組として設計することが重要です。

WSAStartupが担う初期化処理

WSAStartupは、アプリケーションが使用するWinsockのバージョンを指定して、Winsock DLLを初期化します。一般的なWinsock 2.2を利用する場合は、次のように呼び出します。

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

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

WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);

if (result != 0) {
    // resultにはWSAStartup固有のエラーコードが入る
    return 1;
}

第1引数のMAKEWORD(2, 2)は、メジャーバージョン2、マイナーバージョン2を要求する指定です。第2引数のWSADATA構造体には、実際に利用可能なバージョンや実装情報が格納されます。通常は戻り値が0であれば初期化成功と判断します。

重要なのは、WSAStartupの失敗時にWSAGetLastErrorを呼び出してはいけない点です。WSAStartupは失敗理由を戻り値として直接返すため、まずresultの値を確認します。WSAGetLastErrorは、初期化後にsocket、bind、connect、recvなどのWinsock関数が失敗した場合に利用します。

WSAGetLastErrorで失敗原因を取得する

ソケット関数の戻り値がエラーを示したときは、できるだけ直後にWSAGetLastErrorを呼び出します。例えばsocket関数は失敗時にINVALID_SOCKETを返します。

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (sock == INVALID_SOCKET) {
    int errorCode = WSAGetLastError();
    // errorCodeをログへ記録する
}

connectやbindなど、関数ごとに定められた失敗値を先に確認することも欠かせません。戻り値を確認せずにWSAGetLastErrorだけを呼ぶと、以前の処理で設定された古いエラーコードを扱う可能性があります。また、成功した直後のWSAGetLastErrorの値には意味がないため、成功時の値を診断情報として利用してはいけません。

WSAGetLastErrorが返す値は、通常のWin32 APIで使われるGetLastErrorのコードとは別体系です。例えば、初期化前にWinsock関数を呼んだ場合はWSANOTINITIALISED、接続先が応答しない場合はWSAETIMEDOUTが候補になります。エラーコードを表示する際は、数値だけでなく発生したAPI名や対象のアドレスも記録すると調査しやすくなります。

初期化と終了処理を対にする

WSAStartupを呼び出したアプリケーションは、通信処理が終わった段階でWSACleanupを呼び出します。Winsockは内部的に初期化回数を管理しているため、WSAStartupを複数回呼んだ場合は、対応する回数だけWSACleanupを実行する必要があります。

bool winsockReady = false;

WSADATA data;
if (WSAStartup(MAKEWORD(2, 2), &data) == 0) {
    winsockReady = true;

    // socket、connect、送受信などの処理

    WSACleanup();
    winsockReady = false;
}

実際のプログラムでは、早期returnや例外によってWSACleanupが抜けない設計にします。C++ならRAIIを利用した管理クラスを作成し、デストラクタで終了処理を行う方法が安全です。C言語では終了ラベルを一つに集約し、生成済みのソケット、接続、Winsock環境を順番に解放します。

Winsockの初期化はプロセス全体の通信基盤を用意する処理であり、ソケット単位の作成とは異なります。CreateMutexなどのハンドル管理でも、生成した資源を適切な順序で解放する設計が必要です。Windows APIの資源管理に関するMutexの実装例も、終了処理を考える際の参考になります。

よくある失敗と診断方法

WSANOTINITIALISEDが発生する代表例は、WSAStartupより前にsocket、getaddrinfo、inet_ptonなどのWinsock関連APIを呼び出しているケースです。初期化関数をmainの途中に置くより、通信機能を管理する開始処理の先頭で一度だけ呼び出すと、呼び出し順序が明確になります。

WSAStartupそのものが失敗した場合は、戻り値をWinsockのエラーコードとして確認します。要求したバージョンをサポートしていない場合、引数が不正な場合、DLLやシステム環境に問題がある場合などが考えられます。WSADATAのwVersionとwHighVersionを調べれば、要求したバージョンと実際に利用可能なバージョンの差も確認できます。

診断時には、エラーを取得する呼び出しを別のAPIで挟まないことが大切です。例えばsocketが失敗した直後にログ出力用のWinsock関数や別の処理を実行すると、確認対象が変わる可能性があります。エラーコードを直ちにローカル変数へ保存し、その後に文字列化やログ出力を行うと安全です。

実装全体での使い分け

WSAStartupの戻り値、ソケット関数の戻り値、WSAGetLastErrorの取得タイミングを分けて考えると、初期化処理と通信処理の境界がはっきりします。次のように、各APIが返す情報の意味を整理できます。

処理 成功時の確認 失敗時の確認 主な注意点
WSAStartup 戻り値が0 戻り値を直接確認 WSAGetLastErrorを使わない
socket INVALID_SOCKETではない 直後にWSAGetLastError 作成失敗後に無効なソケットを使わない
bind 0 直後にWSAGetLastError ポート競合やアドレス設定を確認
connect 0 直後にWSAGetLastError タイムアウトや接続拒否を区別する
send・recv 送受信バイト数 直後にWSAGetLastError 0は切断を示す場合がある
WSACleanup 戻り値を必要に応じて確認 終了順序を点検 WSAStartupと回数を対応させる

例えばconnectの失敗を調べるコードでは、エラーコードを保存してからメッセージを出力します。

int result = connect(sock, reinterpret_cast<sockaddr*>(&server), sizeof(server));

if (result == SOCKET_ERROR) {
    const int errorCode = WSAGetLastError();

    if (errorCode == WSAECONNREFUSED) {
        // 接続先で待ち受けていない
    } else if (errorCode == WSAETIMEDOUT) {
        // 応答が一定時間内に届かない
    }
}

実用上の要点は、プログラム開始時にWSAStartupを一度実行し、各Winsock関数の失敗直後にWSAGetLastErrorでコードを保存し、すべてのソケットを閉じた後にWSACleanupを呼ぶことです。この順序を守れば、Winsockの初期化不足、通信エラー、終了処理の漏れを切り分けやすくなります。