1回きりのデータ送受信では、TCP/IPによるソケット通信とは呼べない? という事で、ウィンドウ上で繰り返しデータの送受信を行ってみる事に。
試しに、ボタン1でWinSockの起動、ボタン3でエディットボックスに入力した文字列を相手に送信するだけのサンプルを作ってみました。
<注意点>
・エラー処理は手抜きで済ませています。
・接続要求待ちの accept で新たに生成されたソケットを、クライアントとのデータ送受信に使用します。
・accept に使用したソケットは、何度でも接続要求待ちに利用することが出来ます。(複数のクライアントに対応可能)
・send もブロッキング動作するようですが、送信完了する(TCPの送信バッファにデータを書き終わる)までなので、気付かなかった事に。
【WM_APPについて】
ウィンドウのメッセージループへユーザ独自のメッセージを送る際に、良く見かけるのが WM_USER + xyz の定義ですが WM_USER はユーザの為では無く、定義済みのウィンドウ(コントロール)クラスの為に用意されたものです。このプライベートウィンドウクラスの範囲で、ユーザアプリケーションは重要な独自メッセージを定義してはいけません。
WM_APP は、ユーザアプリケーションが独自メッセージを利用出来るよう用意されたもので、システム定義のメッセージと競合する事は有りません。
【Windowメッセージの範囲】
種類 | 範囲 | 説明 |
---|---|---|
WM_XYZXYZ | 0~0x3FF | システムが使用するメッセージ |
WM_USER | 0x400~0x7FFF | プライベートウィンドウクラスが使用するメッセージ *1 |
WM_APP | 0x8000~0xBFFF | アプリケーションが使用できるメッセージ *2 |
-- | 0xC000~0xFFFF | RegisterWindowMessage 関数が定義するメッセージ *3 |
RESERVED | 0x10000~ | 将来のために予約 |
*1:定義済みウィンドウ(コントロール)クラス用、*2:ユーザ定義用
*3:アプリケーション実行中に関数を呼び出し、文字列のメッセージコード取得時に定義される
お試し環境
WindowsXP 32bit Edition
Visual C++ 2008
/*---- サーバの作成 --------------------- コマンドライン -------------------*/
D:\vc2008\dllchk>cl server.c user32.lib ws2_32.lib
/*----------------------------------------------------------------------------*/
/*---- クライアントの作成 ------------------ コマンドライン ----------------*/
D:\vc2008\dllchk>cl client.c user32.lib ws2_32.lib
/*----------------------------------------------------------------------------*/
/*---- server.c ----------------------- お試しソース -----------------------*/
#include <stdio.h>
#include <winsock2.h>
#include <process.h> // _beginthreadex 関数
#define WINCLASS_NAME "Form.003"
#define ID_BUTTON1 1001
#define ID_BUTTON3 1003
#define ID_EDITBOX 1011
#define WM_WS_RECV (WM_APP + 1)
static SOCKET ss, cs; // ss - server socket, cs - client socket
unsigned WINAPI subThread2(void *hCtr)
{
int i, len;
char str[64], buf[64], *datpt;
if(send(cs, "Wellcome to WinSock Communication", 33, 0) < 1) {
printf("send error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
while(1) {
memset(buf, 0, sizeof(buf));
if((len = recv(cs, buf, sizeof(buf), 0)) <= 0) {
printf("recv error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
sprintf(str, "len = %d, data = %s", len, buf);
MessageBox(NULL, str, "クライアント送信データ", MB_OK | MB_ICONINFORMATION);
datpt = calloc(len + 1, sizeof(char));
for(i = 0; i < len; i++) {
datpt[i] = buf[i];
}
PostMessage((HWND)hCtr, WM_WS_RECV, (WPARAM)len, (LPARAM)datpt);
}
return(0);
}
unsigned WINAPI subThread1(void *hCtr)
{
WSADATA wsaData;
SOCKADDR_IN sa, ca; // sa - server address, ca - client address
int len;
char str[64];
// WinSock の初期化
if(WSAStartup(WINSOCK_VERSION, &wsaData) != 0) {
printf("WSAStartup error\n");
return(1);
}
// ソケットの作成
sa.sin_family = AF_INET;
if((ss = socket(sa.sin_family, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("socket error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
// ソケットに名前を付ける
sa.sin_port = htons(54321);
// sa.sin_addr.s_addr = INADDR_ANY;
sa.sin_addr.s_addr = inet_addr("127.0.0.1");
if(bind(ss, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR) {
printf("bind error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
// 接続要求待ち
if(listen(ss, 1) == SOCKET_ERROR) {
printf("listen error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
// 接続要求受付
len = sizeof(ca);
if((cs = accept(ss, (SOCKADDR *)&ca, &len)) == INVALID_SOCKET) {
printf("accept error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
} else {
sprintf(str, "クライアントアドレス = %s、ポート = %d", inet_ntoa(ca.sin_addr), ntohs(ca.sin_port));
MessageBox(NULL, str, "接続要求受付", MB_OK | MB_ICONINFORMATION);
}
_beginthreadex(NULL, 0, subThread2, hCtr, 0, NULL);
return(0);
}
LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hBtn1, hBtn3, hEdit;
HINSTANCE hInstance;
int len, wmid, wmevent;
char str[64], buf[64];
switch(uMsg) {
case WM_CREATE :
hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
if((hBtn1 = CreateWindowEx(WS_EX_LEFT, "BUTTON", "ボタン1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 75, 220, 70, 20, hWnd, (HMENU)ID_BUTTON1, hInstance, NULL)) == NULL) {
printf("ボタン1コントロールを作成出来ませんでした。\n");
return(FALSE);
}
if((hBtn3 = CreateWindowEx(WS_EX_LEFT, "BUTTON", "ボタン3", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 245, 220, 70, 20, hWnd, (HMENU)ID_BUTTON3, hInstance, NULL)) == NULL) {
printf("ボタン3コントロールを作成出来ませんでした。\n");
return(FALSE);
}
if((hEdit = CreateWindowEx(WS_EX_LEFT, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_AUTOVSCROLL | ES_LEFT | ES_MULTILINE, 100, 100, 210, 30, hWnd, (HMENU)ID_EDITBOX, hInstance, NULL)) == NULL) {
printf("エディットボックスコントロールを作成出来ませんでした。\n");
return(FALSE);
}
break;
case WM_COMMAND :
wmid = LOWORD(wParam);
wmevent = HIWORD(wParam);
switch(wmevent) {
case BN_CLICKED :
switch(wmid) {
case ID_BUTTON1 :
EnableWindow(hBtn1, FALSE);
SetWindowText(hBtn1, "押下1");
_beginthreadex(NULL, 0, subThread1, hWnd, 0, NULL);
break;
case ID_BUTTON3 :
EnableWindow(hBtn3, FALSE);
SetWindowText(hBtn3, "押下3");
memset(buf, 0, sizeof(buf));
len = SendMessage((HWND)hEdit, WM_GETTEXT, (WPARAM)64, (LPARAM)buf);
if(send(cs, buf, len, 0) < 1) {
printf("send error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
SetWindowText(hBtn3, "ボタン3");
EnableWindow(hBtn3, TRUE);
break;
default :
break;
}
break;
default :
break;
}
break;
case WM_WS_RECV :
sprintf(str, "len = %d, data = %s", (int)wParam, (char *)lParam);
MessageBox(NULL, str, "クライアントからデータが送られてきました。", MB_OK | MB_ICONINFORMATION);
if((int)wParam == 4 && strncmp((char *)lParam, "exit", 4) == 0) {
// WinSock 切断
shutdown(ss, SD_BOTH);
// ソケットの破棄
closesocket(ss);
closesocket(cs);
// WinSock のリソース解放
WSACleanup();
}
free((char *)lParam);
break;
case WM_CLOSE :
if(MessageBox(hWnd, "ウィンドウを閉じます。\nよろしいですか?", "確認", MB_OKCANCEL | MB_ICONWARNING) == IDOK) {
DestroyWindow(hWnd);
} else {
SetWindowText(hBtn1, "ボタン1");
EnableWindow(hBtn1, TRUE);
SetWindowText(hBtn3, "ボタン3");
EnableWindow(hBtn3, TRUE);
}
break;
case WM_DESTROY :
PostQuitMessage(0);
break;
default :
return DefWindowProc(hWnd , uMsg , wParam , lParam);
}
return(0);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wcx;
MSG msg;
int ret;
/* ウィンドウクラスの登録 */
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = WinProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hInstance;
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcx.lpszMenuName = NULL;
wcx.lpszClassName = WINCLASS_NAME;
wcx.hIconSm = NULL;
if(!RegisterClassEx(&wcx)) {
printf("ウィンドウクラスの登録が出来ませんでした。\n");
return(0);
}
/* 登録したクラスのウィンドウを生成 */
hWnd = CreateWindowEx(WS_EX_LEFT, WINCLASS_NAME, "ウインドウタイトル3", WS_OVERLAPPEDWINDOW, 50, 50, 400, 300, NULL, NULL, hInstance ,NULL);
if(hWnd == NULL) {
printf("ウィンドウが作成出来ませんでした。\n");
return(FALSE);
}
/* ウィンドウの表示 */
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
/* メッセージループ */
while((ret = GetMessage(&msg, NULL, 0, 0)) > 0 ) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(ret == -1) {
printf("メッセージの取得に失敗しました。\n");
return(FALSE);
}
return((int)msg.wParam);
}
/*----------------------------------------------------------------------------*/
/*---- client.c ----------------------- お試しソース -----------------------*/
#include <stdio.h>
#include <winsock2.h>
#include <process.h> // _beginthreadex 関数
#define WINCLASS_NAME "Form.003"
#define ID_BUTTON1 1001
#define ID_BUTTON3 1003
#define ID_EDITBOX 1011
#define WM_WS_RECV (WM_APP + 1)
static SOCKET cs; // cs - client socket
unsigned WINAPI subThread2(void *hCtr)
{
int i, len;
char str[64], buf[64], *datpt;
if(send(cs, "Let's try Communication", 23, 0) < 1) {
printf("send error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
while(1) {
memset(buf, 0, sizeof(buf));
if((len = recv(cs, buf, sizeof(buf), 0)) <= 0) {
printf("recv error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
sprintf(str, "len = %d, data = %s", len, buf);
MessageBox(NULL, str, "サーバ送信データ", MB_OK | MB_ICONINFORMATION);
datpt = calloc(len + 1, sizeof(char));
for(i = 0; i < len; i++) {
datpt[i] = buf[i];
}
PostMessage((HWND)hCtr, WM_WS_RECV, (WPARAM)len, (LPARAM)datpt);
}
return(0);
}
unsigned WINAPI subThread1(void *hCtr)
{
WSADATA wsaData;
LPHOSTENT host;
SOCKADDR_IN sa; // sa - server address
int i;
char str[64];
// char *argv = "localhost";
char *argv = "127.0.0.1";
// WinSock の初期化
if(WSAStartup(WINSOCK_VERSION, &wsaData) != 0) {
printf("WSAStartup Error\n");
return(1);
}
// ソケットの作成
if((cs = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
printf("socket error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
sa.sin_family = AF_INET;
sa.sin_port = htons(54321);
sa.sin_addr.s_addr = inet_addr(argv);
if(sa.sin_addr.s_addr == INADDR_NONE) { // 入力がホスト名なら
if((host = gethostbyname(argv)) == NULL) {
printf("ホスト名の取得に失敗しました : %s\n", argv);
WSACleanup();
return(1);
}
// サーバへの接続要求
for(i = 0; (IN_ADDR *)host->h_addr_list[i] != NULL; i++) {
// sa.sin_addr.s_addr = ((IN_ADDR *)host->h_addr_list[i])->s_addr;
sa.sin_addr.s_addr = (*(IN_ADDR *)host->h_addr_list[i]).s_addr;
if(connect(cs, (SOCKADDR *)&sa, sizeof(sa)) == 0) {
break;
}
}
if((IN_ADDR *)host->h_addr_list[i] == NULL) {
sprintf(str, "ホスト名 = %s", argv);
MessageBox(NULL, str, "接続要求エラー", MB_OK | MB_ICONWARNING);
WSACleanup();
return(1);
}
MessageBox(NULL, "ホスト名", "サーバ接続完了", MB_OK | MB_ICONINFORMATION);
} else { // 入力がドット表記のアドレスなら
// サーバへの接続要求
if(connect(cs, (SOCKADDR *)&sa, sizeof(sa)) != 0) {
sprintf(str, "アドレス = %s", argv);
MessageBox(NULL, str, "接続要求エラー", MB_OK | MB_ICONWARNING);
WSACleanup();
return(1);
}
MessageBox(NULL, "アドレス", "サーバ接続完了", MB_OK | MB_ICONINFORMATION);
}
_beginthreadex(NULL, 0, subThread2, hCtr, 0, NULL);
return(0);
}
LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HWND hBtn1, hBtn3, hEdit;
HINSTANCE hInstance;
int len, wmid, wmevent;
char str[64], buf[64];
switch(uMsg) {
case WM_CREATE :
hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
if((hBtn1 = CreateWindowEx(WS_EX_LEFT, "BUTTON", "ボタン1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 75, 220, 70, 20, hWnd, (HMENU)ID_BUTTON1, hInstance, NULL)) == NULL) {
printf("ボタン1コントロールを作成出来ませんでした。\n");
return(FALSE);
}
if((hBtn3 = CreateWindowEx(WS_EX_LEFT, "BUTTON", "ボタン3", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 245, 220, 70, 20, hWnd, (HMENU)ID_BUTTON3, hInstance, NULL)) == NULL) {
printf("ボタン3コントロールを作成出来ませんでした。\n");
return(FALSE);
}
if((hEdit = CreateWindowEx(WS_EX_LEFT, "EDIT", "", WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | ES_AUTOVSCROLL | ES_LEFT | ES_MULTILINE, 100, 100, 210, 30, hWnd, (HMENU)ID_EDITBOX, hInstance, NULL)) == NULL) {
printf("エディットボックスコントロールを作成出来ませんでした。\n");
return(FALSE);
}
break;
case WM_COMMAND :
wmid = LOWORD(wParam);
wmevent = HIWORD(wParam);
switch(wmevent) {
case BN_CLICKED :
switch(wmid) {
case ID_BUTTON1 :
EnableWindow(hBtn1, FALSE);
SetWindowText(hBtn1, "押下1");
_beginthreadex(NULL, 0, subThread1, hWnd, 0, NULL);
break;
case ID_BUTTON3 :
EnableWindow(hBtn3, FALSE);
SetWindowText(hBtn3, "押下3");
memset(buf, 0, sizeof(buf));
len = SendMessage((HWND)hEdit, WM_GETTEXT, (WPARAM)64, (LPARAM)buf);
if(send(cs, buf, len, 0) < 1) {
printf("send error : %d\n", WSAGetLastError());
WSACleanup();
return(1);
}
SetWindowText(hBtn3, "ボタン3");
EnableWindow(hBtn3, TRUE);
break;
default :
break;
}
break;
default :
break;
}
break;
case WM_WS_RECV :
sprintf(str, "len = %d, data = %s", (int)wParam, (char *)lParam);
MessageBox(NULL, str, "サーバからの送信データです。", MB_OK | MB_ICONINFORMATION);
if((int)wParam == 3 && strncmp((char *)lParam, "bye", 3) == 0) {
// ソケットの破棄
closesocket(cs);
// WinSock のリソース解放
WSACleanup();
}
free((char *)lParam);
break;
case WM_CLOSE :
if(MessageBox(hWnd, "ウィンドウを閉じます。\nよろしいですか?", "確認", MB_OKCANCEL | MB_ICONWARNING) == IDOK) {
DestroyWindow(hWnd);
} else {
SetWindowText(hBtn1, "ボタン1");
EnableWindow(hBtn1, TRUE);
SetWindowText(hBtn3, "ボタン3");
EnableWindow(hBtn3, TRUE);
}
break;
case WM_DESTROY :
PostQuitMessage(0);
break;
default :
return DefWindowProc(hWnd , uMsg , wParam , lParam);
}
return(0);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wcx;
MSG msg;
int ret;
/* ウィンドウクラスの登録 */
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = WinProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.hInstance = hInstance;
wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcx.lpszMenuName = NULL;
wcx.lpszClassName = WINCLASS_NAME;
wcx.hIconSm = NULL;
if(!RegisterClassEx(&wcx)) {
printf("ウィンドウクラスの登録が出来ませんでした。\n");
return(0);
}
/* 登録したクラスのウィンドウを生成 */
hWnd = CreateWindowEx(WS_EX_LEFT, WINCLASS_NAME, "ウインドウタイトル3", WS_OVERLAPPEDWINDOW, 50, 50, 400, 300, NULL, NULL, hInstance ,NULL);
if(hWnd == NULL) {
printf("ウィンドウが作成出来ませんでした。\n");
return(FALSE);
}
/* ウィンドウの表示 */
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
/* メッセージループ */
while((ret = GetMessage(&msg, NULL, 0, 0)) > 0 ) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(ret == -1) {
printf("メッセージの取得に失敗しました。\n");
return(FALSE);
}
return((int)msg.wParam);
}
/*----------------------------------------------------------------------------*/
/*============================================================================*/