WindowsでC/C++でネットワークのプログラミングをやってみようと思います
ネットワークプログラムは基本的にソケットと呼ばれるコネクターでコンピュータ同士を繋ぎます
なのでソケットを使うネットワークプログラミングはソケットプログラミングとも呼ばれます
Linuxなどではそこそこ書籍がありますが
Windowsでやろうとすると思ったより書籍がありません
ちなみにWindowsでのソケットプログラミングをWinsockと呼びます
サーバープログラムとクライアントプログラムの二つを用意してやらないといけないので
テストおよびデバックがかなり大変です

先に参考になるサイトと書籍を挙げて置きます
サイト:

Geekなページ
http://www.geekpage.jp/programming/winsock/

書籍:

猫でもわかるネットワークプログラミング
WinSock2プログラミング 改訂第2版(絶版なのでアマゾンとかで買うしかないかも・・・でも高い)

先に第一回と第二回で使うサーバーとクライアントのプログラムを挙げて置きます
fileServerClient2008.zip

正しく動作させるにはサーバ側プログラムを起動させてから
クライアント側プログラムを起動します

サーバプログラムを起動させ、
クライアントプログラムを同時に複数起動させてみてください
サーバ1つに対し複数のクライアントがアクセスできるはずです
wキー、aキー、sキー、dキーで移動ができます
余力があれば、robotフォルダーのプログラムも起動してみてください
こちらはクライアントのプログラムの改造版で一定時間ごとに自動的に移動します

ServerClient.JPG


サーバープログラムの手順は次のようになります

WSAStartup	WinSockライブラリの準備
socket	ソケットの作成
bind		サーバのアドレスとソケットをバインド
listen	待ち受け準備
accept	ソケット接続
recv,send	データ送受信(繰り返し)
closesocket	ソケットを閉じる
WSACleanup	WinSockライブラリの後処理

具体的な実装はServer.cppを見てください
順番に説明します


まず,任(コンストラクタ)

CServer::CServer():m_ServerSocket(INVALID_SOCKET)
{
	// Windows の場合
	WSADATA data;
	WSAStartup(MAKEWORD(2,0), &data);
}

WSAStartupでWinsockのライブラリのバージョン指定をします
MAKEWORD(2,0)はWinsock2.0を使うことを示します
Winsockライブラリを読み込むことで以降のソケット関係の関数を使用することができます
そのまま使ってください


つぎに◆銑い魄豕い砲笋蠅泙(Listenメソッド)

void CServer::Listen(u_short PORT)
{
	// sockaddr_in 構造体のセット
	memset(&m_ServerAddr, 0, sizeof(m_ServerAddr));
	m_ServerAddr.sin_port = htons(PORT);
	m_ServerAddr.sin_family = AF_INET;
	m_ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	// ソケットの生成(ストリーム型)
	m_ServerSocket = socket(AF_INET, SOCK_STREAM, 0);
 	// ソケットのバインド
	bind(m_ServerSocket, (struct sockaddr *) &m_ServerAddr, sizeof(m_ServerAddr));
 	// 接続の許可
	listen(m_ServerSocket, 5);

	// ここで、ノンブロッキングに設定しています。
	// val = 0でブロッキングモードに設定できます。
	// ソケットの初期設定はブロッキングモードです。
	u_long val=1;
	ioctlsocket(m_ServerSocket, FIONBIO, &val);
}

まず、sockaddr_in構造体にサーバ側の待ち受けポート番号とIPアドレスを指定します
ポート番号とは、コンピュータがデータ通信を行う際に通信先のプログラムを特定するための番号のことです。
したがって、クライアント側からサーバーに繋ぐときはサーバ側の指定ポート番号と同じ番号を使って接続します。
サーバ側のポート番号指定にはsin_portメンバにポート番号を入れます。
ここでPORTがポート番号(整数)に当たりますが、
0〜1024までの番号はウェルノウンポートと呼ばれ、使ってはいけません。
たとえば、ウェブページを見るときにブラウザなどでサーバーにアクセスするプロトコルである
HTTPなどは80番ポートが使われており、サーバプログラムで80番ポートを使用してしまうと
ウェブページを閲覧することができなくなっていまいます
よって1025以上の整数をPORTに指定してください
IPアドレスの指定に関してはこのままの通りで構いません

socket関数でソケットを作成します
このとき第2引数にSOCK_STREAMを指定するとTCP/IP用のソケットが作成されます
ここでは使いませんがSOCK_DGRAMを指定するとUDP用のソケットが作成されます

bind関数で先ほど設定したサーバ側のポートとIPアドレス情報をソケットと関連付けます
listen関数で待ち受けの準備をします。第二引数はバックログの指定ですが普通は5を指定します

その次の処理は非常に重要です

// ここで、ノンブロッキングに設定しています。
// val = 0でブロッキングモードに設定できます。
// ソケットの初期設定はブロッキングモードです。
u_long val=1;
ioctlsocket(m_ServerSocket, FIONBIO, &val);

この処理をする理由なのですが、
クライアントからの接続を行うaccept関数、データを受信するrecv関数
クライアント側からサーバに接続を行うconnect関数(第二回で説明します)などは
入力があるまで処理が止まってしまうscanfのような関数です
このような処理を止めてしまう関数をブロッキング関数と呼びます。
ここで何が言いたいかというと途中で処理が止まってしまうと困る場合があります
つまり、accept関数でクライアントからの接続をずっと待っている間は、
処理がここで止まってしまい、他の処理ができなくなってしまいます
これを打開するには次の方法があり

・マルチスレッド化する
・ソケット処理をウィンドウメッセージ化する
・ソケットをノンブロッキングモード(非同期ソケット)にする

マルチスレッド化については処理が非常に複雑で色々と制御がめんどうくさい問題が出てくるので扱いません
ウィンドウメッセージ化する処理もウィンドウズプログラミング限定の処理になってしまうので今回は扱いません
ここでは、ソケットをioctlsocket関数を用いてノンブロッキングモード(非同期ソケット)にします
非同期ソケットにしておけば、accept関数やrecv関数で処理が止まってしまうことはありません
このいずれかの処理は複数のクライアントからの接続に対応するためには必須の処理です


非同期ソケット化後イ鮃圓い泙后Acceptメソッド)

bool CServer::Accept(SOCKET* pDstSocket,struct sockaddr_in* pDstAddr)
{
	// クライアントアドレス
	int dstAddrSize = sizeof(struct sockaddr_in);
	*pDstSocket = accept(m_ServerSocket, (struct sockaddr *) pDstAddr, &dstAddrSize);
	if(*pDstSocket != INVALID_SOCKET){
		// 接続ソケットを非同期モードにする
		u_long val=1;
		ioctlsocket(*pDstSocket, FIONBIO, &val);
		return true;
	}

	return false;
}

accept関数でクライアントからの接続を待ちます
この関数は第二回で説明する、クライアント側のconnect関数と対応しています
サーバ側がaccept状態でクライアント側でconnect関数が呼ばれると接続が完了します

*pDstSocket = accept(m_ServerSocket, (struct sockaddr *) pDstAddr, &dstAddrSize);
	

accept関数の第一引数にはい離愁吋奪箸鮖慊蠅靴泙
接続が成功すると戻り値に接続先のソケットが返ります
以降、接続先のクライアントとのデータ送受信(recv関数、send関数)を行うにはこのソケットを使います
また、accept関数の第二引数で接続先のクライアントのアドレス情報が取得できます

// 接続ソケットを非同期モードにする
u_long val=1;
ioctlsocket(*pDstSocket, FIONBIO, &val);

recv関数がブロッキング関数なので、データ送受信用ソケットを非同期ソケット化します


Δ任后Recvメソッド、Sendメソッド)
データの受信にはrecv関数、データの送信にはsend関数を使います

// 受信
RECVSTATUS CServer::Recv(SOCKET DstSocket,char* pData,int DataSize,int *pRecvSize)
{	
	int n = recv(DstSocket, pData, DataSize, 0);
	if (n < 1) {
		// データが来ていない
		if (WSAGetLastError() == WSAEWOULDBLOCK) {
			return RECV_STILL;
		// 切断orエラー
		} else {
			return RECV_FAILED;
		}
	} 
	*pRecvSize = n;	// 受信データ長取得
	return RECV_SUCCESSED;
}

データ受信処理です

int n = recv(DstSocket, pData, DataSize, 0);

recv関数の第一引数には、接続済みソケットを指定します
第二引数には受け取るデータ用配列と第三引数にはデータ配列サイズを指定します
気を付けなければいけないのが戻り値です
関数が成功すれば、実際に取得したデータのデータ長(sendで送られたデータ長)が返ります
切断時は0、エラー時はSOCKET_ERROR(-1)が返ります
また、非同期ソケット時はデータが送られてきてない場合
WSAGetLastError()がWSAEWOULDBLOCKとなるのでチェックする必要があります
RECVSTATUSはServer.hで私が定義した列挙体です

// Server.h
// 受信状態
enum RECVSTATUS
{
	RECV_STILL,	// データが来ていない
	RECV_SUCCESSED,	// 成功
	RECV_FAILED	// 切断orエラー
};

受信状態に合わせて戻り値を変えます
続いて送信処理です

// 送信
bool CServer::Send(SOCKET DstSocket,char* pData,int DataSize)
{
	// パケットの送信
	if(send(DstSocket, pData, DataSize, 0) == SOCKET_ERROR)
		return false;

	return true;
}

データ送信処理です

send(DstSocket, pData, DataSize, 0);

使い型は第二引数にデータ、第三引数にデータサイズを指定します
送信失敗時は戻り値がSOCKET_ERRORを返します。

接続が確立した後は
Δ魴り返して色々処理することになります

送信するデータに関してはあらかじめサーバークライアント間で
取り決めをしておかなければなりません
今回はPacket.h共通ヘッダーを
サーバプログラム、クライアントプログラムともに
インクルードしています


Г任(Closeメソッド)
クライアントからの接続が切れた場合(あるいは接続を切る場合)
closesocket関数でデータ送受信用ソケットを指定します

// 切断
void CServer::Close(SOCKET DstSocket)
{
	closesocket(DstSocket);
}

サンプルではrecv関数でクライアントからの接続が切れた時、呼び出してソケットの後処理をしています


┐任后淵妊好肇薀タ)
WinSockライブラリの後処理をします

// デストラクタ
CServer::~CServer()
{
	// Windows での終了設定
	WSACleanup();
}

サーバ側は以上です
起動にはクライアントのプログラムも必要になります
C/C++ソケットプログラミング第二回

選択肢 投票
(^ω^)できたー 60  
。(`ω´#)。できねー 59  

添付ファイル: fileServerClient.JPG 1698件 [詳細] fileServerClient2008.zip 3257件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-09-29 (金) 08:07:12