Client 모델
: 하나 이상의 소켓 처리시 overlapped I/O 모델이나 WSAEventSelect 모델이 성능상 유리.
윈도우 기반일 경우 WSAAsyncSelct 모델이 개발 편의성 제공.
Server 모델
: 동시에 몇개의 소켓 처리시 overlapped I/O 모델이 성능상 유리.
대량의 I/O 처리시 IOCP 모델이 성능상 유리.
|
SPI(Service Provider Interface) : http://msdn.microsoft.com/en-us/library/ms741418.aspx (스펙)
Server.cpp
#include <iostream>
#include <winsock2.h>
#include <windows.h>
using namespace std;
#pragma comment(lib, "ws2_32.LIB")
#pragma comment(lib, "Kernel32.lib")
void printError(TCHAR *errMsg)
{
TCHAR* lpOSMsg;
int errNum = WSAGetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errNum,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpOSMsg,
0,
NULL);
wcout << TEXT("[ERROR] ") << errMsg << TEXT(", [") << errNum << TEXT("] ") << lpOSMsg << endl;
LocalFree(lpOSMsg);
}
int main()
{
setlocale(LC_ALL, "");
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR)
{
printError(TEXT("윈도우 소켓 초기화 에러"));
return -1;
}
wcout << TEXT("server WinSock 초기화 : ver ") << (int)LOBYTE(wsaData.wVersion) << "." << (int)HIBYTE(wsaData.wVersion) << endl;
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //socket
try
{
if (sock == INVALID_SOCKET)
throw TEXT("대기 소켓 객체 생성 실패");
struct sockaddr_in addr;
addr.sin_family = AF_INET; //TCP, UDP..
addr.sin_port = htons(1024);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(INADDR_ANY); //inet_addr("127.0.0.1");
bool isReuse = true;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&isReuse, sizeof(isReuse)) == SOCKET_ERROR) throw TEXT("setsockopt error");
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) throw TEXT("bind error");
if (listen(sock, SOMAXCONN) == SOCKET_ERROR) throw TEXT("listen error");
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
int cliSockCount = 0;
SOCKET cliSockArr[FD_SETSIZE-1];
while (true)
{
fd_set readfds = fds;
wcout << TEXT("fds 소켓 개수 : ") << fds.fd_count << endl;
int ret = select(0, &readfds, NULL, NULL, NULL);
if (ret == SOCKET_ERROR) throw TEXT("select error");
int eventSockCount = ret;
//서버 socket이 읽기 사건 발생시 accept 수행 및 client socket 등록
if (FD_ISSET(sock, &readfds)) //서버 소켓에서 읽기 event가 발생했을 경우
{
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
SOCKET clisock = accept(sock, (struct sockaddr*) &cliaddr, &addrlen);
if (cliSockCount < FD_SETSIZE -1)
{
//wcout << TEXT("[") << inet_ntoa(cliaddr.sin_addr) << TEXT("] 로 부터 접속") << endl;
cliSockArr[cliSockCount++] = clisock;
FD_SET(clisock, &fds);
}
else //select() 의 대기 최대 소켓 개수 초과시
{
struct linger ling; //TIME_WAIT 상태 회피
ling.l_onoff = 1; //nonzero : socket 유지, zero : 즉시 종료
ling.l_linger = 0; //socket 유지 시간(sec)
setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling));
closesocket(clisock);
}
eventSockCount--;
}
//event가 발생한 socket 에 대해 recv/send 수행.
for (int i=0; i<cliSockCount && 0<eventSockCount; i++)
{
if (!FD_ISSET(cliSockArr[i], &readfds)) continue;
eventSockCount--;
TCHAR msg[100];
int nReadBytes = recv(cliSockArr[i], (char*)msg, sizeof(msg), 0);
if (nReadBytes == 0 || nReadBytes == SOCKET_ERROR) //연결이 닫혔거나(0), 에러(SOCK_ERROR)면..
{
closesocket(cliSockArr[i]);
FD_CLR(cliSockArr[i], &fds);
cliSockArr[i] = cliSockArr[cliSockCount - 1];
cliSockCount--;
continue;
}
else
{
//wcout << TEXT("받은 데이터 : ") << msg << TEXT(", cliSock : ") << cliSockArr[i] << endl;
send(cliSockArr[i], (const char*)msg, nReadBytes, 0);
}
}
}
}
catch (TCHAR* errMsg)
printError(errMsg);
if (sock != INVALID_SOCKET)
{
struct linger ling;
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling));
closesocket(sock);
}
WSACleanup();
return 0;
}
|
Client.cpp
#include <winsock2.h>
#include <iostream>
#include <windows.h>
using namespace std;
#pragma comment(lib, "ws2_32.LIB")
void printError(TCHAR *errMsg)
{
TCHAR* lpOSMsg;
int errNum = WSAGetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errNum,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpOSMsg,
0,
NULL);
wcout << TEXT("[ERROR] ") << errMsg << TEXT(", [") << errNum << TEXT("] ") << lpOSMsg << endl;
LocalFree(lpOSMsg);
}
int main()
{
setlocale(LC_ALL, "");
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR)
{
printError(TEXT("윈도우 소켓 초기화 에러"));
return -1;
}
wcout << TEXT("client WinSock 초기화 : ver ") << (int)LOBYTE(wsaData.wVersion) << "." << (int)HIBYTE(wsaData.wVersion) << endl;
while(true)
{
int n;
wcout << TEXT("client 수 입력 : "); wcin >> n;
SOCKET* arrSock = new SOCKET[n];
for (int i=0; i<n; i++)
{
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
arrSock[i] = sock;
try
{
if (sock == INVALID_SOCKET) throw TEXT("대기 소켓 객체 생성 실패");
struct sockaddr_in addr;
addr.sin_family = AF_INET; //TCP, UDP..
addr.sin_port = htons(1024);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(INADDR_ANY); //inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == SOCKET_ERROR) throw TEXT("connect error");
while (true)
{
TCHAR msg[100] = {0};
wsprintf(msg, TEXT("hi~"));
int nSendBytes = send(sock, (const char*)msg, sizeof(msg), 0);
if (nSendBytes == SOCKET_ERROR) throw TEXT("send error");
ZeroMemory(msg, sizeof(msg));
int nRecvBytes = recv(sock, (char*)msg, sizeof(msg), 0);
if (nRecvBytes == SOCKET_ERROR) throw TEXT("recv error");
//wcout << TEXT("recv data : ") << msg << endl;
break;
}
}
catch (TCHAR* errMsg)
printError(errMsg);
}
wcout << TEXT("현재 client 수 : ") << n << endl;
wcout << TEXT("enter : 계속 진행") << endl;
wcin.ignore();
wcin.get();
for (int i=0; i<n; i++)
{
if (arrSock[i] != INVALID_SOCKET)
{
//테스트 용으로 client의 TIME_WAIT를 없애기 위한 linger
struct linger ling;
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(arrSock[i], SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling));
closesocket(arrSock[i]);
}
}
delete arrSock;
}
WSACleanup();
return 0;
}
|
Server.cpp
#include <winsock2.h>
#include <windows.h>
#include <list>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
#define ID_LIST (WM_USER + 0x01)
#define ID_BUTTON (WM_USER + 0x02)
#define WM_SOCKET_NOTIFY (WM_USER + 0x03)
HWND hList;
HWND hBtn;
SOCKET gSock = INVALID_SOCKET;
list<SOCKET> gClisock;
void MoveCenter(HWND hWnd);
void printError(TCHAR *errMsg);
bool CreateSock(HWND hWnd);
void fdAccept(HWND hWnd);
void fdRead(SOCKET clisock);
void fdClose(HWND hWnd, SOCKET clisock);
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpClassName=TEXT("main");
LPCTSTR lpTitleName=TEXT("Tomato Soap");
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=(WNDPROC)WndProc;
WndClass.lpszClassName=lpClassName;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpClassName,lpTitleName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,500,500,
NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while (GetMessage(&Message,0,0,0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
UnregisterClass(lpClassName, hInstance);
return (int)Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
switch(iMessage) {
case WM_CREATE:
hWndMain=hWnd;
MoveCenter(hWnd);
hList = CreateWindow(TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, 10, 10, 400, 400, hWnd, (HMENU)ID_LIST, g_hInst, NULL);
hBtn = CreateWindow(TEXT("BUTTON"), TEXT("close"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_NOTIFY, 10, 410, 100, 30, hWnd, (HMENU)ID_BUTTON, g_hInst, NULL);
if (!CreateSock(hWnd)) SendMessage(hWnd, WM_DESTROY, NULL, NULL);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_BUTTON:
switch (HIWORD(wParam))
{
case BN_CLICKED:
SendMessage(hWnd, WM_DESTROY, NULL, NULL);
break;
}
break;
case ID_LIST:
break;
}
return 0;
case WM_DESTROY:
if (gSock != INVALID_SOCKET)
closesocket(gSock);
WSACleanup();
PostQuitMessage(0);
return 0;
case WM_SOCKET_NOTIFY:
switch (WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
fdAccept(hWnd);
break;
case FD_READ:
fdRead((SOCKET)wParam);
break;
case FD_CLOSE:
fdClose(hWnd, (SOCKET)wParam);
break;
}
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
void MoveCenter(HWND hWnd)
{
RECT rectDesktop, rectWindow;
GetWindowRect(GetDesktopWindow(),&rectDesktop) ;
GetWindowRect(hWnd,&rectWindow) ;
LONG width, height;
width = rectWindow.right - rectWindow.left ;
height = rectWindow.bottom - rectWindow.top ;
LONG x, y;
x = LONG((rectDesktop.right - width) / 2) ;
y = LONG((rectDesktop.bottom - height) / 2) ;
MoveWindow(hWnd, x, y, width, height, TRUE) ;
}
void printError(TCHAR *errMsg)
{
TCHAR* lpOSMsg;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpOSMsg,
0,
NULL);
TCHAR msg[100];
wsprintf(msg, TEXT("%s :: %s"), errMsg, lpOSMsg);
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
MessageBox(hWndMain, msg, TEXT("error"), MB_OK);
LocalFree(lpOSMsg);
SendMessage(hWndMain, WM_DESTROY, NULL, NULL);
}
bool CreateSock(HWND hWnd)
{
TCHAR msg[100];
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR)
{
wsprintf(msg, TEXT("server WinSock 초기화 error"));
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
SendMessage(hWnd, WM_DESTROY, NULL, NULL);
}
wsprintf(msg, TEXT("server WinSock 초기화 : ver %d.%d"), (int)LOBYTE(wsaData.wVersion), (int)HIBYTE(wsaData.wVersion));
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //socket
try
{
if (sock == INVALID_SOCKET) throw TEXT("대기 소켓 객체 생성 실패");
struct sockaddr_in addr;
addr.sin_family = AF_INET; //TCP, UDP..
addr.sin_port = htons(1024);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(INADDR_ANY); //inet_addr("127.0.0.1");
//TIME_WAIT인 경우 socket 재사용
bool isReuse = true;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&isReuse, sizeof(isReuse)) == SOCKET_ERROR)
throw TEXT("setsockopt error");
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
throw TEXT("bind error");
if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
throw TEXT("listen error");
if (WSAAsyncSelect(sock, hWnd, WM_SOCKET_NOTIFY, FD_ACCEPT) == SOCKET_ERROR)
throw TEXT("WSAAsyncSelect error");
gSock = sock;
}
catch (TCHAR* errMsg)
{
printError(errMsg);
if (sock != INVALID_SOCKET)
{
struct linger ling; //server에서의 TIME_WAIT 방지를 위한 조치
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(sock, SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling));
closesocket(sock);
}
return false;
}
return true;
}
void fdAccept(HWND hWnd)
{
SOCKET sock = gSock;
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
SOCKET clisock= accept(sock, (struct sockaddr*)&cliaddr, &addrlen);
WSAAsyncSelect(clisock, hWnd, WM_SOCKET_NOTIFY, FD_READ | FD_CLOSE);
gClisock.push_back(clisock);
TCHAR cliip[20];
MultiByteToWideChar( CP_ACP, MB_COMPOSITE, inet_ntoa(cliaddr.sin_addr), -1, cliip, lstrlen(cliip));
TCHAR msg[100];
wsprintf(msg, TEXT("접속수:%d, 접속ip: %s"), gClisock.size(), cliip);
int n = (int)SendMessage(hList, LB_ADDSTRING, NULL, (LPARAM)msg); //리스트에 문자 추가 후 해당 index를 리턴.
SendMessage(hList, LB_SETTOPINDEX, n, 0); //리스트에 해당 index가 항상 보여지도록 scrolling함
}
void fdRead(SOCKET clisock)
{
TCHAR msg[1024] = {0};
int nReadbytes = recv(clisock, (char*)msg, sizeof(msg), 0);
if (nReadbytes == SOCKET_ERROR) return;
for (list<SOCKET>::iterator it=gClisock.begin(); it!=gClisock.end(); it++)
send(*it, (const char*)msg, nReadbytes, 0);
}
void fdClose(HWND hWnd, SOCKET clisock)
{
closesocket(clisock);
gClisock.remove(clisock);
TCHAR msg[100];
wsprintf(msg, TEXT("접속수:%d"), gClisock.size());
int n = (int)SendMessage(hList, LB_ADDSTRING, NULL, (LPARAM)msg); //리스트에 문자 추가 후 해당 index를 리턴.
SendMessage(hList, LB_SETTOPINDEX, n, 0); //리스트에 해당 index가 항상 보여지도록 scrolling함
}
|
Client.cpp
#include <winsock2.h>
#include <windows.h>
#include "resource.h"
#pragma comment(lib, "ws2_32.lib")
#define ID_LIST (WM_USER + 0x01)
#define ID_CLOSEBUTTON (WM_USER + 0x02)
#define WM_SOCKET_NOTIFY (WM_USER + 0x03)
#define ID_MSGEDIT (WM_USER + 0x04)
#define ID_SENDBUTTON (WM_USER + 0x05)
HWND hList;
HWND hCloseBtn;
HWND hMsgEdit;
HWND hSendBtn;
SOCKET gSock = INVALID_SOCKET;
TCHAR gUserID[128]; //사용자 이름
void printError(TCHAR *errMsg);
void MoveCenter(HWND hWnd);
bool CreateSock(HWND hWnd);
void fdRead(HWND hWnd);
void fdSend(HWND hWnd);
BOOL CALLBACK LoginDlgProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK EditProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpClassName=TEXT("main");
LPCTSTR lpTitleName=TEXT("Tomato Soap");
//Login 다이알로그 메세지 proc
BOOL CALLBACK LoginDlgProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
HWND hEdit = NULL;
switch (iMessage)
{
case WM_INITDIALOG:
MoveCenter(hWnd);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
GetDlgItemText(hWnd, IDC_EDIT1, gUserID, sizeof(gUserID));
EndDialog(hWnd, IDOK);
break;
case IDCANCEL:
EndDialog(hWnd, IDCANCEL);
break;
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow)
{
//다이알 로그호출하여 user id를 받아 gUserID에 저장.
INT_PTR nLoginID = DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), HWND_DESKTOP, LoginDlgProc);
if (nLoginID == IDCANCEL) return 0;
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=(WNDPROC)WndProc;
WndClass.lpszClassName=lpClassName;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpClassName,lpTitleName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,500,500,
NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while (GetMessage(&Message,0,0,0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
UnregisterClass(lpClassName, hInstance);
return (int)Message.wParam;
}
WNDPROC EditProcOrg = NULL;
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
switch(iMessage) {
case WM_CREATE:
hWndMain=hWnd;
MoveCenter(hWnd);
hList = CreateWindow(TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, 10, 10, 400, 400, hWnd, (HMENU)ID_LIST, g_hInst, NULL);
hMsgEdit = CreateWindow(TEXT("EDIT"), NULL, WS_CHILD | WS_VISIBLE| WS_BORDER, 10, 410, 300, 20, hWnd, (HMENU)ID_MSGEDIT, g_hInst, NULL);
hSendBtn = CreateWindow(TEXT("BUTTON"), TEXT("send"), WS_CHILD | WS_VISIBLE, 310, 410, 50, 20, hWnd, (HMENU)ID_SENDBUTTON, g_hInst, NULL);
hCloseBtn = CreateWindow(TEXT("BUTTON"), TEXT("close"), WS_CHILD | WS_VISIBLE, 360, 410, 50, 20, hWnd, (HMENU)ID_CLOSEBUTTON, g_hInst, NULL);
SetFocus(hMsgEdit);
//채팅 메세지 입력시 엔터 감지 위한 에디터 컨트롤 서브 클래싱
EditProcOrg = (WNDPROC)SetWindowLong(hMsgEdit, GWL_WNDPROC, (LONG)EditProc);
if (!CreateSock(hWnd)) SendMessage(hWnd, WM_DESTROY, NULL, NULL);
return 0;
case WM_DESTROY:
if (gSock != INVALID_SOCKET)
closesocket(gSock);
WSACleanup();
PostQuitMessage(0);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_CLOSEBUTTON:
switch (HIWORD(wParam))
{
case BN_CLICKED:
SendMessage(hWnd, WM_DESTROY, NULL, NULL);
break;
}
break;
case ID_SENDBUTTON:
fdSend(hWnd);
break;
}
return 0;
case WM_SOCKET_NOTIFY:
switch (WSAGETSELECTEVENT(lParam))
{
case FD_READ:
fdRead(hWnd);
break;
}
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
LRESULT CALLBACK EditProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
switch (iMessage)
{
case WM_CHAR:
if (wParam == VK_RETURN) return 0; //엔터시 삑~ 소리 없애기 위한 방법
break;
case WM_KEYDOWN:
if (wParam == VK_RETURN)
SendMessage(hWndMain, WM_COMMAND, ID_SENDBUTTON, NULL);
break;
}
return(EditProcOrg(hWnd,iMessage,wParam,lParam));
}
void printError(TCHAR *errMsg)
{
TCHAR* lpOSMsg;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpOSMsg,
0,
NULL);
TCHAR msg[100];
wsprintf(msg, TEXT("%s :: %s"), errMsg, lpOSMsg);
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
MessageBox(hWndMain, msg, TEXT("error"), MB_OK);
LocalFree(lpOSMsg);
SendMessage(hWndMain, WM_DESTROY, NULL, NULL);
}
void MoveCenter(HWND hWnd)
{
RECT rectDesktop, rectWindow;
GetWindowRect(GetDesktopWindow(),&rectDesktop) ;
GetWindowRect(hWnd,&rectWindow) ;
LONG width, height;
width = rectWindow.right - rectWindow.left ;
height = rectWindow.bottom - rectWindow.top ;
LONG x, y;
x = LONG((rectDesktop.right - width) / 2) ;
y = LONG((rectDesktop.bottom - height) / 2) ;
MoveWindow(hWnd, x, y, width, height, TRUE) ;
}
bool CreateSock(HWND hWnd)
{
TCHAR msg[100];
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR)
{
wsprintf(msg, TEXT("client WinSock 초기화 error"));
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
SendMessage(hWnd, WM_DESTROY, NULL, NULL);
}
wsprintf(msg, TEXT("client WinSock 초기화 : ver %d.%d"), (int)LOBYTE(wsaData.wVersion), (int)HIBYTE(wsaData.wVersion));
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //socket
try
{
if (sock == INVALID_SOCKET) throw TEXT("대기 소켓 객체 생성 실패");
struct sockaddr_in addr;
addr.sin_family = AF_INET; //TCP, UDP..
addr.sin_port = htons(1024);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(INADDR_ANY); //inet_addr("127.0.0.1");
if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == SOCKET_ERROR)
throw TEXT("connect error");
if (WSAAsyncSelect(sock, hWnd, WM_SOCKET_NOTIFY, FD_READ) == SOCKET_ERROR)
throw TEXT("WSAAsyncSelect error");
gSock = sock;
}
catch (TCHAR* errMsg)
{
printError(errMsg);
if (sock != INVALID_SOCKET) closesocket(sock);
return false;
}
return true;
}
void fdRead(HWND hWnd)
{
TCHAR msg[128+256];
int nReadbytes = recv(gSock, (char*)msg, sizeof(msg), 0);
if (nReadbytes == SOCKET_ERROR) return;
int n = (int)SendMessage(hList, LB_ADDSTRING, NULL, (LPARAM)msg); //리스트에 문자 추가 후 해당 index를 리턴.
SendMessage(hList, LB_SETTOPINDEX, n, 0); //리스트에 해당 index가 항상 보여지도록 scrolling함
}
void fdSend(HWND hWnd)
{
TCHAR msg[256];
GetWindowText(hMsgEdit, msg, sizeof(msg)/sizeof(TCHAR));
SetWindowText(hMsgEdit, TEXT(""));
TCHAR buf[128+256];
wsprintf(buf, TEXT("%s : %s"), gUserID, msg);
if (send(gSock, (const char*) buf, sizeof(buf), 0) == SOCKET_ERROR)
printError(TEXT("send error"));
}
|
실행결과 및 소스
..
CServerSock gSock;
BOOL CServerDlg::OnInitDialog()
{
..
if (!AfxSocketInit())
{
printError(TEXT("AfxSocketInit error"));
EndDialog(-1);
}
if (gSock.Create(1024) && gSock.Listen())
{
gSock.AsyncSelect(FD_ACCEPT);
}
else
{
printError(TEXT("create listen error"));
gSock.Close();
EndDialog(-1);
}
..
//CSocket을 상속받아 OnAccept()를 오버라이딩.
void CServerSock::OnAccept(int nErrorCode)
{
CClientSock* cliSock = new CClientSock;
..
Accept(*cliSock);
..
CString clientIp;
UINT clientPort;
cliSock->GetPeerName(clientIp, clientPort); //client ip/port 조회
..
cliSock->AsyncSelect(FD_READ | FD_CLOSE); //client의 메세지 수신 및 종료 evnet 감시
CSocket::OnAccept(nErrorCode);
}
//CSocket을 상속받아 OnAccept()를 오버라이딩.
void CClientSock::OnReceive(int nErrorCode)
{
TCHAR msg[1024] = {0};
int nReadbytes = Receive(msg, sizeof(msg)-1); //데이터 수신
if (nReadbytes == SOCKET_ERROR) return;
..
for (list<CClientSock*>::iterator it=cliSockList->begin(); it!=cliSockList->end(); it++)
((CClientSock*)*it)->Send(msg, nReadbytes); //데이터 송신
CSocket::OnReceive(nErrorCode);
}
//CSocket을 상속받아 OnClose() 오버라이딩
void CClientSock::OnClose(int nErrorCode)
{
//this->Close();
cliSockList->remove(this);
CSocket::OnClose(nErrorCode);
}
.. |
CSocket_Client 주요부분
..
CClientSock gSock;
int CChatDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
..
struct sockaddr_in addr;
addr.sin_family = AF_INET; //TCP, UDP..
addr.sin_port = htons(1024);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(INADDR_ANY); //inet_addr("127.0.0.1");
if (!AfxSocketInit())
{
printError(TEXT("AfxSocketInit error"));
EndDialog(-1);
}
if (gSock.Create() && gSock.Connect((const SOCKADDR*)&addr, sizeof(addr)))
gSock.AsyncSelect(FD_READ);
else
{
printError(TEXT("create or connect error"));
gSock.Close();
EndDialog(-1);
}
void CChatDlg::OnBnClickedSendButton()
{
CString msg;
GetDlgItemText(IDC_MSG_EDIT, msg);
CString str = userid + TEXT(" : ") + msg;
gSock.Send((LPCTSTR)str, str.GetLength() * sizeof(TCHAR)); //데이터 송신
..
//CSocket 을 상속한 후 OnReceive() 오버라이딩
void CClientSock::OnReceive(int nErrorCode)
{
TCHAR buf[1024] = {0};
Receive(buf, sizeof(buf)-1); //데이터 수신
..
int n = msgList->AddString(buf);
msgList->SetTopIndex(n);
CSocket::OnReceive(nErrorCode);
}
|
#include <winsock2.h>
#include <windows.h>
#include <process.h>
#pragma comment(lib, "ws2_32.lib")
#define ID_LIST (WM_USER + 0x01)
#define ID_BUTTON (WM_USER + 0x02)
#define WM_SOCKET_NOTIFY (WM_USER + 0x03)
HWND hList;
HWND hBtn;
#define MAX_CLIENT (WSA_MAXIMUM_WAIT_EVENTS -1 -1)
UINT WINAPI ThreadProc(LPVOID lpParameter);
void MoveCenter(HWND hWnd);
void printError(TCHAR *errMsg);
void InnerThreadProc(void);
SOCKET gSock = INVALID_SOCKET;
SOCKET gClientSock[MAX_CLIENT];
WSAEVENT g_hEvent[MAX_CLIENT+2]; //처음 2개는 스레드 탈출, 대기 소켓을 위한 이벤트
WSAEVENT* g_phEvtSock = g_hEvent+2;
long g_lSockCnt = 0;
long g_lThreadCnt = 0;
void OnAccept(void);
void OnRead(SOCKET sock);
void OnClose(int i);
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpClassName=TEXT("main");
LPCTSTR lpTitleName=TEXT("Tomato Soap");
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow)
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=(WNDPROC)WndProc;
WndClass.lpszClassName=lpClassName;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
hWnd = CreateWindow(lpClassName,lpTitleName,WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,500,500,
NULL,(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while (GetMessage(&Message,0,0,0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
UnregisterClass(lpClassName, hInstance);
return (int)Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
TCHAR msg[100];
HANDLE hThread;
switch(iMessage) {
case WM_CREATE:
hWndMain=hWnd;
MoveCenter(hWnd);
hList = CreateWindow(TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, 10, 10, 400, 400, hWnd, (HMENU)ID_LIST, g_hInst, NULL);
hBtn = CreateWindow(TEXT("BUTTON"), TEXT("close"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_NOTIFY, 10, 410, 100, 30, hWnd, (HMENU)ID_BUTTON, g_hInst, NULL);
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) == SOCKET_ERROR)
{
wsprintf(msg, TEXT("server WinSock 초기화 error"));
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
SendMessage(hWnd, WM_DESTROY, NULL, NULL);
}
wsprintf(msg, TEXT("server WinSock 초기화 : ver %d.%d"), (int)LOBYTE(wsaData.wVersion), (int)HIBYTE(wsaData.wVersion));
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
g_hEvent[0] = WSACreateEvent();
if (g_hEvent[0] == NULL)
{
printError(TEXT("WSACreateEvent error"));
SendMessage(hWnd, WM_DESTROY, 0, 0);
}
hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
return 0;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case ID_BUTTON:
switch (HIWORD(wParam))
{
case BN_CLICKED:
SendMessage(hWnd, WM_DESTROY, NULL, NULL);
break;
}
break;
case ID_LIST:
break;
}
return 0;
case WM_DESTROY:
SetEvent(g_hEvent[0]);
while (0<g_lThreadCnt);
Sleep(100);
WSACloseEvent(g_hEvent[0]);
WSACleanup();
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
void MoveCenter(HWND hWnd)
{
RECT rectDesktop, rectWindow;
GetWindowRect(GetDesktopWindow(),&rectDesktop) ;
GetWindowRect(hWnd,&rectWindow) ;
LONG width, height;
width = rectWindow.right - rectWindow.left ;
height = rectWindow.bottom - rectWindow.top ;
LONG x, y;
x = LONG((rectDesktop.right - width) / 2) ;
y = LONG((rectDesktop.bottom - height) / 2) ;
MoveWindow(hWnd, x, y, width, height, TRUE) ;
}
void printError(TCHAR *errMsg)
{
TCHAR* lpOSMsg;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpOSMsg,
0,
NULL);
TCHAR msg[100];
wsprintf(msg, TEXT("%s :: %s"), errMsg, lpOSMsg);
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
MessageBox(hWndMain, msg, TEXT("error"), MB_OK);
LocalFree(lpOSMsg);
SendMessage(hWndMain, WM_DESTROY, NULL, NULL);
}
UINT WINAPI ThreadProc(LPVOID lpParameter)
{
InterlockedIncrement(&g_lThreadCnt);
__try
{
__try
{
InnerThreadProc();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
}
__finally
{
closesocket(gSock);
WSACloseEvent(g_hEvent[1]);
for (int i=0; i<g_lSockCnt; i++)
{
closesocket(gClientSock[i]);
WSACloseEvent(g_phEvtSock[i+2]);
}
InterlockedDecrement(&g_lThreadCnt);
}
return 0;
}
void InnerThreadProc(void)
{
try
{
gSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (gSock == INVALID_SOCKET) throw TEXT("대기 소켓 객체 생성 실패");
g_hEvent[1] = WSACreateEvent();
if (g_hEvent[1] == WSA_INVALID_EVENT) throw TEXT("WSACreateEvent error");
if (WSAEventSelect(gSock, g_hEvent[1], FD_ACCEPT) == SOCKET_ERROR)
throw TEXT("WSAEventSelect error");
struct sockaddr_in addr;
addr.sin_family = AF_INET; //TCP, UDP..
addr.sin_port = htons(1024);
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //htonl(INADDR_ANY); //inet_addr("127.0.0.1");
//TIME_WAIT인 경우 socket 재사용
bool isReuse = true;
if (setsockopt(gSock, SOL_SOCKET, SO_REUSEADDR, (const char*)&isReuse, sizeof(isReuse)) == SOCKET_ERROR)
throw TEXT("setsockopt error");
if (bind(gSock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
throw TEXT("bind error");
if (listen(gSock, SOMAXCONN) == SOCKET_ERROR)
throw TEXT("listen error");
while (true)
{
DWORD dwRet = WSAWaitForMultipleEvents(2+g_lSockCnt, g_hEvent, FALSE, INFINITE, FALSE);
int n = dwRet - WSA_WAIT_EVENT_0;
if (n == 0) break;
SOCKET sock = (2 <=n ? gClientSock[n-2] : gSock);
WSANETWORKEVENTS netEvt;
WSAEnumNetworkEvents(sock, g_hEvent[n], &netEvt);
if (netEvt.lNetworkEvents & FD_ACCEPT)
OnAccept();
if (netEvt.lNetworkEvents & FD_READ)
OnRead(sock);
if (netEvt.lNetworkEvents & FD_CLOSE)
OnClose(n-2);
}
}
catch (TCHAR * errMsg)
{
printError(errMsg);
}
}
void OnAccept(void)
{
sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
SOCKET commsock = accept(gSock, (sockaddr*)&cliaddr, &addrlen);
if (g_lSockCnt < MAX_CLIENT)
{
WSAEVENT hEvtSock = WSACreateEvent();
if (hEvtSock != WSA_INVALID_EVENT)
{
WSAEventSelect(commsock, hEvtSock, FD_READ | FD_CLOSE);
g_phEvtSock[g_lSockCnt] = hEvtSock;
gClientSock[g_lSockCnt] = commsock;
g_lSockCnt++;
TCHAR cliip[20];
MultiByteToWideChar( CP_ACP, MB_COMPOSITE, inet_ntoa(cliaddr.sin_addr), -1, cliip, lstrlen(cliip));
TCHAR msg[100];
wsprintf(msg, TEXT("접속수:%d, 접속ip: %s"), g_lSockCnt, cliip);
int n = (int)SendMessage(hList, LB_ADDSTRING, NULL, (LPARAM)msg); //리스트에 문자 추가 후 해당 index를 리턴.
SendMessage(hList, LB_SETTOPINDEX, n, 0); //리스트에 해당 index가 항상 보여지도록 scrolling함
return;
}
}
struct linger ling; //server에서의 TIME_WAIT 방지를 위한 조치
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(commsock, SOL_SOCKET, SO_LINGER, (const char*)&ling, sizeof(ling));
closesocket(commsock);
}
void OnRead(SOCKET sock)
{
TCHAR msg[1024] = {0};
int nReadbytes = recv(sock, (char*)msg, sizeof(msg), 0);
if (nReadbytes == SOCKET_ERROR) return;
for (int i=0; i<g_lSockCnt; i++)
send(gClientSock[i], (const char*)msg, nReadbytes, 0);
}
void OnClose(int i)
{
closesocket(gClientSock[i]);
WSACloseEvent(g_phEvtSock[i]);
g_phEvtSock[i] = g_phEvtSock[g_lSockCnt-1];
gClientSock[i] = gClientSock[g_lSockCnt-1];
g_lSockCnt--;
TCHAR msg[100];
wsprintf(msg, TEXT("접속수:%d"), g_lSockCnt);
int n = (int)SendMessage(hList, LB_ADDSTRING, NULL, (LPARAM)msg); //리스트에 문자 추가 후 해당 index를 리턴.
SendMessage(hList, LB_SETTOPINDEX, n, 0); //리스트에 해당 index가 항상 보여지도록 scrolling함
}
|
'C++' 카테고리의 다른 글
콘솔로 한글 출력이 안되는 경우 setlocale(LC_ALL, ""); (0) | 2010.10.15 |
---|---|
Dialog 창에서 엔터나 ESC 일때 창 닫히는 현상 막기 (0) | 2010.10.13 |
윈도우 창을 모니터 화면 가운데로 이동하기 (0) | 2010.10.13 |
Random 난수 생성하기 (0) | 2010.10.13 |
Windows Thread 와 Synchronization(동기화) (0) | 2010.10.06 |
자료구조 (0) | 2010.09.30 |
VC++ 수행시간 체크 (1) | 2010.09.15 |
IME(Input Method Editor) (0) | 2010.09.11 |