WinSock
■ソケット通信
ソケットとはTCP/IPの通信プロトコルにおける出入り口の役割を担っているAPIのことです。
ソケットを使用することでプログラマはTCPやUDPなどの通信手段や手順を知らなくてもよく、
通信相手に合わせてソース内容を変更する必要もありません。
このソケットを使用した通信をソケット通信と呼びます。
●ソケット間通信で必要な情報
ソケット間通信で必要な情報は主にIPアドレスとポート番号の二つです。
この二つの情報を元にしてネットワーク上で通信相手の特定を行い、
PC間の通信を可能にしています。
・IPアドレス
IPアドレスはネットワーク上でのコンピュータを識別するための識別子です。
IPアドレスにはLANのようなローカル環境で使用されるプライベートIPアドレスと
インターネットで使用するグローバルIPアドレスがあります。
・ポート番号
ポート番号はIPアドレスで特定した通信データをどのアプリケーションで
対処するのかを判断するために使用される識別子です。
HTTP(WEB閲覧)やPOP3(メール受信)、IMAP(メール送信)などの
頻繁に使われるアプリケーションには専用の番号が決めれています。
このサービス側で決められている専用のポート番号をウェルノウンポートと呼びます。
■サーバーソケット、クライアントソケット
ソケットにはデータを受信側と送信側の二つのソケットが必要です。
このソケットの受信側をサーバーソケット、送信側をクライアントソケットと呼んでいます。
●サーバーソケット
サーバーソケットはクライアントがいつ通信を行うか分からないので
常に起動してクライアントから通信要求が来るのを待っています。
この待機状態のことをリスン(listen)状態と呼びます。
●クライアントソケット
クライアントソケットはサーバーソケットに要求を出すソケットで、
サーバーソケットとは違い、サーバーに対して用がある時だけ使用します。
クライアントソケットはサーバーソケットへの通信方法として
ストリームソケットとデータグラムソケットの2種類があります。
・ストリームソケット
ストリームソケットはTCPプロトコルを使用して通信を行うソケットです。
メリット:
送受信データの信頼度が高い
デメリット:
データの信頼性を保つための手順が多いので時間がかかる
・データグラムソケット
データグラムソケットはUDPプロトコルを使用して通信を行うソケットです。
メリット:
データを高速に送受信する
デメリット:
速度を優先しているので、データの信頼性が低い
■WinSock
WinSock(Windows Sockets)は本来UNIXで使用されていたソケット通信を
Windows版に改良したAPIのことです。
「■ソケットとは」でも記述していますがプログラマはWinSockを使用することで
ネットワークの詳細を知らなくても通信データの送受信が可能となります。
●バージョン情報
WinSockのバージョンは最新が2.2となります。
バージョン1ではストリームソケットのみでしたが、
バージョン2からデータグラムソケットが使用可能となりました。
●WinSockのプログラミングに必要なファイル
・libファイル
#pragma comment (lib, "Ws2_32.lib")
・インクルードファイル
#include <WinSock2.h>
※#include <WinSock2.h>と#include <windows.h>の両方を
インクルードする場合は多重定義によるエラーが発生するので
インクルード前に「#define WIN32_LEAN_AND_MEAN」を宣言してください。
●手順
①.バージョンの作成
MAKEWORDマクロを使用し、WinSockをどのバージョンで作成するかを決めます。
②.WSADATAの宣言
③のWSAStartupの情報を格納するためにWSADATA構造体を宣言します。
③.WinSockの初期化
WSAStartup関数を使用し、WinSockを初期化します。
④.WinSockの終了
WSACleanup関数を使用し、WinSockを終了します。
⑤.WinSockのエラー番号取得
SOCKET_ERRORが返されるWinSock関数のエラーに対して
WSAGetLastError関数を使用してエラー番号を取得します。
●WinSock基本プログラム
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <Windows.h>
#include <WinSock2.h>
#pragma comment (lib, "Ws2_32.lib")
void main(void)
{
// ①WinSockのバージョン作成
WORD version_request = MAKEWORD(1, 1);
// ②WSADATA:Windows Sockets情報を格納する構造体
WSADATA sadata;
// ③WinSockを初期化する
int error = WSAStartup(version_request, &sadata);
if (error != 0)
{
printf("WinSock初期化失敗\n");
return;
}
printf("使用バージョン = %d.%d\n",
LOBYTE(sadata.wVersion), HIBYTE(sadata.wVersion));
printf("最新バージョン = %d.%d\n",
LOBYTE(sadata.wHighVersion), HIBYTE(sadata.wHighVersion));
printf("%s\n", sadata.szDescription);
printf("状態 = %s\n", sadata.szSystemStatus);
printf("ソケット最大数 = %d\n", sadata.iMaxSockets);
printf("UDP送受信最大バイト数 = %d\n", sadata.iMaxUdpDg);
// ④WinSockのリソースを解放する
error = WSACleanup();
if (error == SOCKET_ERROR)
{
// ⑤エラー番号を取得する
error = WSAGetLastError();
printf ("WinSockエラー番号 = %d\n", error);
}
while(true);
}
■WinSockによるHTTP通信
上のソースはWinSockを初期化してすぐに解放するだけの処理でしたので、
次はWinSockを使用したHTTP通信を行いたいと思います。
●HTTP通信の手順
①.WinSockを初期化する
WSAStartup関数を使用してWinSockを初期化します。
②.ポート番号とホスト名の入力
ポート番号とホスト名を入力します。
ホスト名:
WinSockが指すホスト名はFQDN(完全修飾ドメイン名)、ドメイン名、
IPアドレス、コンピュータ名のいずれか
ホスト(ホストコンピュータ):
ホストはネットワークを通じて様々なサービスを
提供するコンピュータのことです。
サーバーはホストのシステムを指しており
WinSockではIPアドレス等を使用して
ネットワーク上からホストコンピュータを探し出して接続します。
ポート番号:
HTTP通信なので80で固定
※ゲーム等ではエフェメラルポートのいずれかを使用する
③.ファイル階層の設定
ファイル階層はドメイン(yahoo.co.jp)やIP(182.22.59.229)の後に続く
「/」以降の文字列になります。
例えばyahooの海外サッカーのURLは「soccer.yahoo.co.jp/ws/」となっています。
これは「soccer.yahoo.co.jp」までがドメインでその後の「/ws/」が
そのドメインをファイル階層の頂点としたファイル階層です。
④.ソケットを開く
IPアドレスの種類、通信の種類を決定して
socket関数でソケットを開きます。
⑤.ホスト名からホスト情報を取得する
ホスト名からホスト情報を取得する方法は2種類あります。
・gethostbyname関数の使用
ドメイン名(FQDN含む)、コンピュータ名の場合は
gethostbyname関数を使用することで情報を取得できます。
・gethostbyaddr関数の使用
IPアドレスの場合はgethostbyaddrを使用しますが
この関数はアドレスが文字列のままでは使用できないので、
inet_addr関数を使用して文字表現を数値表現に変換して
情報の取得をします。
⑥.ホストへ接続する
ホストへの接続はconnetct関数を使用します。
接続ための情報をSOCKADDR_IN構造体を使用して設定します。
・SOCKADDRとSOCKADDR_IN構造体の関係
WinSockの関数ではソケットアドレスの情報を引数で渡す場面が多々あり、
関数の引数の内容はSOCKADDRのポインタとなっています。
ですが、SOCKADDR_INの構造体を設定したとしても問題なく動作します。
理由として、SOCKADDRは他のソケット構造体に切り替えても
動作するようにメモリ配置を考えて作られています。
⑦.閲覧したいページの情報を送信する
HTTP通信を行う際は確認したいURLのGETリクエストを
send関数を使用して送信する
GETリクエスト基本書式:
GET 閲覧したいURL HTTP/1.1
⑧.受信データを読み込む
sendで送信したHTTPデータをrecv関数で受信する
⑨.ソケットの送受信を停止する
通信を終了する前にshutdown関数を使用して
ソケットの受信と送信を停止する
⑩.ソケットを閉じる
使用が終わったソケットはclosesocket関数で閉じる
⑪.WinSockを終了する
WSACleanup関数を使用してWinSockを終了する
●ソース
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <WinSock2.h>
#pragma comment (lib, "Ws2_32.lib")
void main(void)
{
WSADATA wsa_data;
LPHOSTENT lp_host;
SOCKET sock;
int result;
SOCKADDR_IN sock_add;
char server[256];
char url[256];
char rcv[256];
char port[8];
char str[256];
unsigned short port_no;
unsigned int addr;
while (true)
{
// ①.WinSockを初期化
result = WSAStartup(MAKEWORD(1, 1), &wsa_data);
if (result != 0)
{
printf("WinSockの初期化エラー\n");
getchar();
return;
}
// ②.ホスト、ポート号の入力
// ホスト名入力(yahoo.co.jp)
printf("-------ホスト名-------\n");
gets_s(server);
// ポート番号入力
printf("-------ポート番号-------\n");
gets_s(port);
// ポート番号80以外はエラー
if (strcmp(port, "80") != 0)
{
printf("ポート番号は80のみ使用可能です。\n");
getchar();
return;
}
// ③.ファイル階層入力
// ファイル階層入力
port_no = (unsigned short)atoi(port);
printf("-------ファイル名-------\n");
gets_s(url);
// ファイル階層が入力されていなかったらエラー
if (strcmp(url, "") == 0)
{
url[0] = '/';
}
// ④.ソケットを開く
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
printf("ソケットオープンエラー\n");
WSACleanup();
getchar();
return;
}
// ⑤.サーバー名からホスト情報を取得
lp_host = gethostbyname(server);
if (lp_host == NULL)
{
// アドレスを表す文字列を数値表現に変換
addr = inet_addr(server);
// ネットワークアドレスからホスト情報を取得
lp_host = gethostbyaddr((char*)&addr, 4, AF_INET);
if (lp_host == NULL)
{
printf("%sが見つかりません\n", server);
WSACleanup();
getchar();
return;
}
}
// SOCKADDR_IN構造体に必要事項をセット
memset(&sock_add, 0, sizeof(sock_add));
sock_add.sin_family = AF_INET;
sock_add.sin_port = htons(port_no);
sock_add.sin_addr = *((LPIN_ADDR)*lp_host->h_addr_list);
// ⑥.ソケット接続
if (connect(sock, (PSOCKADDR)&sock_add, sizeof(sock_add)) != 0)
{
printf("ソケットに接続失敗\n");
closesocket(sock);
WSACleanup();
getchar();
return;
}
// ⑦.GETリクエスト送信
sprintf_s(str, "GET http://%s%s HTTP/1.0\r\n\r\n”", server, url, 256);
result = send(sock, str, (int)strlen(str), 0);
printf("RequestURL = %s\n", str);
while (true)
{
// 受信用バッファをクリア
memset(rcv, 0, sizeof(rcv));
// ⑧.データを受信
result = recv(sock, rcv, (int)sizeof(rcv) - 1, 0);
// 受信したデータを標準出力に書き出す
printf("%s\n", rcv);
if (result == 0)
{
// 何も受け取らなかったら終了
break;
} else if (result == SOCKET_ERROR) {
printf("recvエラー\n");
getchar();
break;
}
}
// ⑨.シャットダウン
if (shutdown(sock, SD_BOTH) != 0)
{
printf("シャットダウンに失敗しました\n");
getchar();
}
// ⑩.ソケットを閉じる
closesocket(sock);
// ⑪.WinSock解放
WSACleanup();
}
}
●サンプル
以下のリンクは上記コードが書かれているサンプルです。
サンプル