#1.기본적으로 C언어를 어느정도 안다는 가정하에 내용이 진행이 됩니다.
#2.IDE개발 환경은 아무거나 해도 상관 없지만. 비쥬얼 스튜디오 2005~ 2010까지를 추천드립니다.
네트워크 프로그래밍이라는것은 소켓 프로그래밍이라고 해도
무방합니다. 윈도우 네트워크라는게 상당 부분 리눅스의 BSD소켓 시스템을
차용해서 만들어졌기 때문입니다. 이번에 보실껏은 버클리 소켓이라고 말을 하기도 하는 기본적인 소켓입니다. 비동기 입출력 소켓도 아니고 블럭킹모드로 움직이는 소켓프로그램이지요. 물론 MMORPG에서같이 다중 접속을 허용할려면 윈도우스 기본적으로 4가지 모델을 사용합니다. 1.멀티 쓰레드 모델 2. IO 멀티 플렉싱 모델 3. WSA이벤트 모델 4. IOCP 모델 IO멀티 플렉싱 기법은 리눅스의 기반 과 소스코드가 호환됩니다만. 기본적으로 동기 알림 모델이라 반응성이 떨어집니다. 접속자의 수는 50명도 넘기기가 힘이 듭니다. 그리고 멀티 쓰레드의 모델같은경우는 전역객체의 임계영역의 지점도 한계가 있고 접속이 하나가 생성될때 마다 하나의 쓰레드를 생성해야 하기 때문에 CPU의 컨텍트 스위칭의 부담감이 있습니다. WSAEVENT의 경우는 비동기 알림이구 비동기 전송 센드로 하면 어느정도 납득할만합니다. 물론.. 윈도우에서 최고의 네트워크 모델은 IOCP이겠지만요.. 하지만 이것은 나름대로 복잡한 로직이 필요하니 간단하게 기본적으로 버클리 소켓을 이용한 간단한 동기 입출력 모델을 보도록 하겠습니다.
서버 프로그램 내에서는 서버 소켓을 하나 만들어 둡니다.
뭐 만든다고 해도 소켓 자체는 window 운영체제가 만들어 줍니다.
그리고 운영체제가 만들면서 소켓의 핸들을 리턴받습니다.
마치 윈도우 운영차제에서 윈도우 창을 만들때 와 비슷하죠?
마찬가지로 소켓을 만들면 윈도우에서 핸들을 주는데
우리는 이 핸들도 여러가지를 합니다.
일단 여기서는 1:1 접속의 구조라서 서버에서는 서버소켓 1개와 클라이언트 소켓 1개
이렇게 밖에 없습니다. 동시접속구조를 꾸미고 싶다면 select구조로 작성하거나 IOCP로
작성해야 합니다만. 상당히 난이도가 높은 코드를 요합니다.
그럼 서버의 구조는 어떻게 될까요 ?
일단 윈도우 소켓 프로그래밍을 할려면 라이브러리를 초기화 해줘야 하는데
다음함수가 초기화를 해줍니다.
WSAStartup( MAKEWORD(2, 2), &wsaData)
모든 윈도우 소켓 프로그래밍은 무조건 WSAStartup함수가 호출 되어야 합니다.
윈도우 소켓 2.2버젼을 사용한다는 뜻이고 WSADATA는 하나 선언해서
주소값을 넘기면 됩니다.
그리고 나서 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) 함수로 서버 소켓을
일단 만들어둡니다. 대충 보면 TCP 프로토콜의 소켓을 만든 다는 뜻인데요.
서버 소켓이라는 것은 일종의 문지기를 담당합니다. 클라이언트에서 접속을 요청할
경우 접대를 해줄 소켓이 필요한데 이역활을 서버 소켓이 담당합니다..
bind(hServerSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) 함수로
서버컴퓨터의 ip값이랑 포트값을 할당합니다. 전화기를 애를들어보져
전화를 걸려면(클라이언트) 상대방(서버) 번호가 있어야겠지요?
bind는 전화국에 가서 번호를 따오는 것과 똑같은 일을 합니다..
listen함수로 연결 요청 대기 상태로 들어갑니다. 이때부터 이때부터
클라에서 연결을 요청할수 있습니다. 하지만 연결요청에 대한 응답을 해야 하는데!
다음의 함수를 연결요청을 수락 할수 있습니다.
hClientSocket = accept(hServerSocket, (SOCKADDR*)&clientAddr, &iClientAddrSize)
연결이 요청되고 나서 이제부터 데이터의 송수신이 가능한데
send함수랑 recv함수로 송수신이 가능합니다.
그리고 송수신이 끝나고 나면 소켓을 닫아주어야 합니다. 다음의 함수로 닫아 줄수가
있습니다.
closesocket(hServerSocket);
다음으로 윈속 라이브러리를 올렸으니 반대로 깨긋하게 닫아줘야겠지요?
다음의 함수로 닫아줍니다.
WSACleanup();
클라이언트의 코드를 볼까요 바인드 함수 연결 함수 이런거 전혀 필요없습니다.
그냥 바로 소켓생성하고 커넥트 함수 호출해주시면 준비가 끝납니다.
connect(hSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr))
프로젝트 생성하실때는 win32 콘솔 어플리케이션으로 생성해서 설정하고
초기설정에서는 빈프로젝트에 체크를 해주세요
그럼 소스를 볼까요 프로젝트 속성 -> 구성 속성 -> 문자열 집합
을 멀티 바이트 문자 집합으로 바꿔주시고 다음에 소스코드를 확인 하여 보세요
*서버 소스 코드
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib") CONST INT MAX_LISTENING_QUEUE = 5;
static CHAR g_szPort[] ="3587"; INT main(VOID)
{
WSADATA wsaData;
SOCKET hServerSocket;
SOCKET hClientSocket;
SOCKADDR_IN serverAddr;
SOCKADDR_IN clientAddr; INT iClientAddrSize ; CHAR message[] = "검은 고양이 네로 네로 네로 이랬다 저래 나 장난꾸러기~!!"; if ( WSAStartup( MAKEWORD(2, 2), &wsaData) != 0 )
{
puts("WSAStartup() 함수 에러");
exit(1);
} hServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( hServerSocket == INVALID_SOCKET )
{
puts("서버 소켓 핸들 에러") ;
exit(1);
} ZeroMemory(&serverAddr, sizeof(serverAddr)) ;
serverAddr.sin_family = AF_INET ;
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(atoi(g_szPort));
if ( bind(hServerSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR )
{
puts("바인드 함수가 정상적으로 작동되지 못하였습니다.");
exit(1);
} if ( listen(hServerSocket, MAX_LISTENING_QUEUE) == SOCKET_ERROR )
{
puts("리슨 함수가 제대로 작동되지 못하였습니다.");
exit(1);
} iClientAddrSize = sizeof( clientAddr );
hClientSocket = accept(hServerSocket, (SOCKADDR*)&clientAddr, &iClientAddrSize); if (hClientSocket == INVALID_SOCKET)
{
puts("클라이언트 소켓이 잘못되었습니다.!!");
exit(1);
} send(hClientSocket, message, sizeof(message), NULL); closesocket(hServerSocket);
closesocket(hClientSocket);
WSACleanup();
return 0;
}
*클라이언트 소스 코드
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib") static CHAR g_szIP[] = "127.0.0.1";
static CHAR g_szPort[] ="3587"; INT main(VOID)
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN serverAddr; CHAR message[100];
INT iMessageLen; if ( WSAStartup( MAKEWORD(2, 2), &wsaData) != 0 )
{
puts("WSAStartup() 함수 에러");
exit(1);
} hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( hSocket == INVALID_SOCKET )
{
puts("소켓 핸들 에러") ;
exit(1);
} ZeroMemory(&serverAddr, sizeof(serverAddr)) ;
serverAddr.sin_family = AF_INET ;
serverAddr.sin_addr.S_un.S_addr = inet_addr(g_szIP);
serverAddr.sin_port = htons(atoi(g_szPort)); if ( connect(hSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR )
{
puts("접속이 끊겼음....");
exit(1);
}
iMessageLen = recv(hSocket, message, sizeof(message), 0);
if (iMessageLen == -1)
{
puts("EOF입니다.");
exit(1);
} MessageBox(NULL, message, "ServerMessage", MB_OK); closesocket(hSocket);
WSACleanup();
return 0;
}
무방합니다. 윈도우 네트워크라는게 상당 부분 리눅스의 BSD소켓 시스템을
차용해서 만들어졌기 때문입니다. 이번에 보실껏은 버클리 소켓이라고 말을 하기도 하는 기본적인 소켓입니다. 비동기 입출력 소켓도 아니고 블럭킹모드로 움직이는 소켓프로그램이지요. 물론 MMORPG에서같이 다중 접속을 허용할려면 윈도우스 기본적으로 4가지 모델을 사용합니다. 1.멀티 쓰레드 모델 2. IO 멀티 플렉싱 모델 3. WSA이벤트 모델 4. IOCP 모델 IO멀티 플렉싱 기법은 리눅스의 기반 과 소스코드가 호환됩니다만. 기본적으로 동기 알림 모델이라 반응성이 떨어집니다. 접속자의 수는 50명도 넘기기가 힘이 듭니다. 그리고 멀티 쓰레드의 모델같은경우는 전역객체의 임계영역의 지점도 한계가 있고 접속이 하나가 생성될때 마다 하나의 쓰레드를 생성해야 하기 때문에 CPU의 컨텍트 스위칭의 부담감이 있습니다. WSAEVENT의 경우는 비동기 알림이구 비동기 전송 센드로 하면 어느정도 납득할만합니다. 물론.. 윈도우에서 최고의 네트워크 모델은 IOCP이겠지만요.. 하지만 이것은 나름대로 복잡한 로직이 필요하니 간단하게 기본적으로 버클리 소켓을 이용한 간단한 동기 입출력 모델을 보도록 하겠습니다.
서버 프로그램 내에서는 서버 소켓을 하나 만들어 둡니다.
뭐 만든다고 해도 소켓 자체는 window 운영체제가 만들어 줍니다.
그리고 운영체제가 만들면서 소켓의 핸들을 리턴받습니다.
마치 윈도우 운영차제에서 윈도우 창을 만들때 와 비슷하죠?
마찬가지로 소켓을 만들면 윈도우에서 핸들을 주는데
우리는 이 핸들도 여러가지를 합니다.
일단 여기서는 1:1 접속의 구조라서 서버에서는 서버소켓 1개와 클라이언트 소켓 1개
이렇게 밖에 없습니다. 동시접속구조를 꾸미고 싶다면 select구조로 작성하거나 IOCP로
작성해야 합니다만. 상당히 난이도가 높은 코드를 요합니다.
그럼 서버의 구조는 어떻게 될까요 ?
일단 윈도우 소켓 프로그래밍을 할려면 라이브러리를 초기화 해줘야 하는데
다음함수가 초기화를 해줍니다.
WSAStartup( MAKEWORD(2, 2), &wsaData)
모든 윈도우 소켓 프로그래밍은 무조건 WSAStartup함수가 호출 되어야 합니다.
윈도우 소켓 2.2버젼을 사용한다는 뜻이고 WSADATA는 하나 선언해서
주소값을 넘기면 됩니다.
그리고 나서 socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) 함수로 서버 소켓을
일단 만들어둡니다. 대충 보면 TCP 프로토콜의 소켓을 만든 다는 뜻인데요.
서버 소켓이라는 것은 일종의 문지기를 담당합니다. 클라이언트에서 접속을 요청할
경우 접대를 해줄 소켓이 필요한데 이역활을 서버 소켓이 담당합니다..
bind(hServerSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) 함수로
서버컴퓨터의 ip값이랑 포트값을 할당합니다. 전화기를 애를들어보져
전화를 걸려면(클라이언트) 상대방(서버) 번호가 있어야겠지요?
bind는 전화국에 가서 번호를 따오는 것과 똑같은 일을 합니다..
listen함수로 연결 요청 대기 상태로 들어갑니다. 이때부터 이때부터
클라에서 연결을 요청할수 있습니다. 하지만 연결요청에 대한 응답을 해야 하는데!
다음의 함수를 연결요청을 수락 할수 있습니다.
hClientSocket = accept(hServerSocket, (SOCKADDR*)&clientAddr, &iClientAddrSize)
연결이 요청되고 나서 이제부터 데이터의 송수신이 가능한데
send함수랑 recv함수로 송수신이 가능합니다.
그리고 송수신이 끝나고 나면 소켓을 닫아주어야 합니다. 다음의 함수로 닫아 줄수가
있습니다.
closesocket(hServerSocket);
다음으로 윈속 라이브러리를 올렸으니 반대로 깨긋하게 닫아줘야겠지요?
다음의 함수로 닫아줍니다.
WSACleanup();
클라이언트의 코드를 볼까요 바인드 함수 연결 함수 이런거 전혀 필요없습니다.
그냥 바로 소켓생성하고 커넥트 함수 호출해주시면 준비가 끝납니다.
connect(hSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr))
프로젝트 생성하실때는 win32 콘솔 어플리케이션으로 생성해서 설정하고
초기설정에서는 빈프로젝트에 체크를 해주세요
그럼 소스를 볼까요 프로젝트 속성 -> 구성 속성 -> 문자열 집합
을 멀티 바이트 문자 집합으로 바꿔주시고 다음에 소스코드를 확인 하여 보세요
*서버 소스 코드
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib") CONST INT MAX_LISTENING_QUEUE = 5;
static CHAR g_szPort[] ="3587"; INT main(VOID)
{
WSADATA wsaData;
SOCKET hServerSocket;
SOCKET hClientSocket;
SOCKADDR_IN serverAddr;
SOCKADDR_IN clientAddr; INT iClientAddrSize ; CHAR message[] = "검은 고양이 네로 네로 네로 이랬다 저래 나 장난꾸러기~!!"; if ( WSAStartup( MAKEWORD(2, 2), &wsaData) != 0 )
{
puts("WSAStartup() 함수 에러");
exit(1);
} hServerSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( hServerSocket == INVALID_SOCKET )
{
puts("서버 소켓 핸들 에러") ;
exit(1);
} ZeroMemory(&serverAddr, sizeof(serverAddr)) ;
serverAddr.sin_family = AF_INET ;
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(atoi(g_szPort));
if ( bind(hServerSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR )
{
puts("바인드 함수가 정상적으로 작동되지 못하였습니다.");
exit(1);
} if ( listen(hServerSocket, MAX_LISTENING_QUEUE) == SOCKET_ERROR )
{
puts("리슨 함수가 제대로 작동되지 못하였습니다.");
exit(1);
} iClientAddrSize = sizeof( clientAddr );
hClientSocket = accept(hServerSocket, (SOCKADDR*)&clientAddr, &iClientAddrSize); if (hClientSocket == INVALID_SOCKET)
{
puts("클라이언트 소켓이 잘못되었습니다.!!");
exit(1);
} send(hClientSocket, message, sizeof(message), NULL); closesocket(hServerSocket);
closesocket(hClientSocket);
WSACleanup();
return 0;
}
*클라이언트 소스 코드
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib") static CHAR g_szIP[] = "127.0.0.1";
static CHAR g_szPort[] ="3587"; INT main(VOID)
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN serverAddr; CHAR message[100];
INT iMessageLen; if ( WSAStartup( MAKEWORD(2, 2), &wsaData) != 0 )
{
puts("WSAStartup() 함수 에러");
exit(1);
} hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( hSocket == INVALID_SOCKET )
{
puts("소켓 핸들 에러") ;
exit(1);
} ZeroMemory(&serverAddr, sizeof(serverAddr)) ;
serverAddr.sin_family = AF_INET ;
serverAddr.sin_addr.S_un.S_addr = inet_addr(g_szIP);
serverAddr.sin_port = htons(atoi(g_szPort)); if ( connect(hSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR )
{
puts("접속이 끊겼음....");
exit(1);
}
iMessageLen = recv(hSocket, message, sizeof(message), 0);
if (iMessageLen == -1)
{
puts("EOF입니다.");
exit(1);
} MessageBox(NULL, message, "ServerMessage", MB_OK); closesocket(hSocket);
WSACleanup();
return 0;
}