티스토리 뷰
버클리 유닉스에서 개발한 네트워크 프로그래밍 인터페이스
이것을 윈도우 환경에서 사용할 수 있도록 한것을 윈도우 소켓 (Windows Sockets), 줄여서 윈속(Winsock) 이라 한다.
윈도우95 부터 API에 정식으로 포함하고 있다.
MFC는 윈속 API를 좀더 편리하게 사용할 수 있도록 다음과 같은 클래스를 제공하고 있다.
CAsyncSocket 과 CSocket 은 소켓 자체를 추상화한 것으로 필수 클래스며, CSocketFile은 소켓을 이요한 통신을 편리하게 하기 위한 부가적인 클래스다.
- 윈도우 소켓
소켓은 다양한 종류의 프로토콜을 일괄된 인터페이스로 다룰 수 있도록 만든 함수 집합으로 유닉스에서는 시스템 콜(System Cll)로 윈도우에서는 API로 제공하고 있다.
소켓은 프로토콜 자체는 아니고 단지 인터페이스에 불과하므로 두 프로그램이 통신을 위해 양쪽 모두 소켓을 사용해야 하는것은 아니다. 단지 양쪽 프로그램이 동일한 프로토콜을 사용하고 정해진 형태와 절차에 따라 데이터를 주고 받으면 되는 것이다.
윈도우 소켓 1.X 버전 까지는 주로 TCP/IP를 지원했지만 2.X 버전 부터는 다양한 종류의 프로토콜을 지원하도록 구조를 변경하고 함수를 추가하였다. 현재 윈도우 소켓 2.X에서 지원하는 프로토콜은 TCP/IP, IPv6(윈도우XP이상), IrDA(윈도우98이상), Bluetooth(윈도우XP SP1이상), IPX/SPX, ATM, DECNet, TP4(윈도우2000부터 지원하지 않음), DLC(윈도우XP부터 지원하지 않음), NetBEUI(윈도우Xp부터 지원하지 않음)
MFC가 제공하는 소켓 클래스는 윈도우 소켓 1.1 버전만 사용한다. 따라서 위와 같은 다양한 프로토콜과 더불어 2.X 버전의 향상된 함수를 사용하려면 API로 직접 프로그래밍해야 한다. (VC++6.0기준 임)
- CAsyncSocket : 비동기식 - 직렬화(CArchive)를 사용 못함.
- CSocket : 동기식 (CAsyncSocket 상속 받음)
<동기/비동기식 차이로 인한 동작 차이점 인듯>
함수 |
설명 |
Accept() |
원래 Accept()는 CAsyncSocket 클래스에서 제공하는 가상함수로, CSoket 클래스에서 내부적으로 이 함수를 재정의한다. CSocket::Accept()는 접속한 클라이언트가 없으면 계속 대기(Blocking)한다. 반면 CAsyncSocket::Accept()는 접속한 클라이언트가 없더라도 곧바로 리턴하기 때문에, 이를 처리하는 부가적인 코드를 작성해야 한다. |
Receive() |
원래 Receive()는 CAsyncSocket 클래스에서 제공하는 가상함수로, CSoket 클래스에서 내부적으로 이 함수를 재정의한다. CSocket::Receive()는 클라이언트가 보낸 데이터가 없으면 계속 대기(Blocking)한다. 반면 CAsyncSocket::Receive()는 클라이언트가 보낸 데이터가 없더라도 곧바로 리턴하므로 이를 처리하는 부가적인 코드를 작성해야 한다. |
Send() |
원래 Send()는 CAsyncSocket 클래스에서 제공하는 가상함수로, CSoket 클래스에서 내부적으로 이 함수를 재정의한다. CSocket::Send()의 독특한 특징은 내부에서 루프를 돌면서 nBufLen만큼 데이터를 모두 보내도록 시도한다는 점이다. 반면CAsyncSocket::Send()는 실제로 보낸 바이트 수가 nBufLen로 지장한 값보다 작더라도 곧바로 리턴하므로 리턴값을 반드시 확인하고 이를 처리하는 부가적인 코드를 작성해야 한다. |
포트번호가 같아도 프로토콜이 다르면 상관없다.
<서버 측 코드>
1. 소켓 생성
BOOL CSocket::Create(UINT nSocketPort=0, int nSocketType=SOCK_STREAM, LPCTSTR lpszSocketAddress=NULL);
- 소켓 포트번호는 반드시 지정해야 한다.
- UDP를 사용하려면 SOCK_DGRAM 으로 지정
- IP주소가 2개 이상인 컴퓨터에서만 의미 있고 그냥 기본값 사용
2. 외부에서 TCP 를 이용한 접속이 가능하도록 한다.
BOOL AsyncSocket::Listen(int nConnectionBlocking=5);
- nConnectionBlocking : 당장 서버가 처리하지 않아도 접속할 수 있는 클라이언트의 최대숫자이며 기본값 사용
3. 접속한 클라이언트와 통신할 수 있도록 새로운 소켓을 생성한다.
BOOL CSocket::Accept(CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr=NULL, int* lpSockAddrLen=NULL);
- rConnectedSocket : 접속한 클라이언트와 연결된 소켓 저장
- lpSockAddr : 접속한 클라이언트의 IP 주소
- lpSockAddrLen : 접속한 클라이언트의 포트 번호
rConnectedSocket 에 전달하고, lpSockAddr과 lpSockAddrlen 값으로 클라이언트의 IP와 포트 번호를 알 수 있다.
4. 접속한 클라이언트 정보를 얻는다.
BOOL CAsyncSocket::GetPeerName(CString& rPeerAddress, UINT& rPeerPort);
- rPeerAddress : 클라이언트의 IP주소 저장
- rPeerPort : 클라이언트의 포트 번호 저장
5. 클라이언트가 보낸 데이터를 받는다.
int CSocket::Receive(void* lpBuf, int nBufLen, int nFlags=0);
- lpBuf : 데이터 수신 버퍼의 주소
- nBufLen : 수신 버퍼의 크기
- nFlags : 기본값 사용
6. 소켓을 닫는다.
void CSocket::Close()
지역 변수로 선언된 경우 CSocket 클래스의 소멸자가 호출되어 CSocket::Close()를 자동으로 호출하게 되므로 코드 상에서 Close() 호출을 생략할 수 있다.
<클라이언트 코드>
1. 서버 접속
BOOL CAsyncSocket::Connect(LPCTSTR lpszHostAddress, UINT nHostPort);
- lpszHostAddress : 서버의 주소
- nHostPost : 서버의 포트 번호
2. 데이터 전송
int CAsyncSocket::Send(const void* lpBuf, int nBufLen, int nFlags=0);
- lpBuf : 송신 버퍼의 주소
- nBufLen : 송신 버퍼 크기
- nFlags : 기본값 사용
3. 소켓 닫기
void CSocket::Close();
<서버>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 |
#include <afxsock.h>
void ErrQuit(int err)
{
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL); MessageBox(NULL, (LPCTSTR)lpMsgBuf, "오류 발생", MB_OK | MB_ICONINFORMATION);
LocalFree(lpMsgBuf);
ExitProcess(1);
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// MFC를 초기화합니다. 초기화하지 못한 경우 오류를 인쇄합니다.
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 오류 코드를 필요에 따라 수정합니다.
printf("심각한 오류: MFC를 초기화하지 못했습니다.\n");
nRetCode = 1;
}
else
{
// TODO: 응용 프로그램의 동작은 여기에서 코딩합니다.
if (!AfxSocketInit())
AfxMessageBox("소켓 초기화에 실패했습니다.", MB_OK | MB_ICONSTOP);
CSocket sock;
if (!sock.Create(8000))
ErrQuit(sock.GetLastError());
if(!sock.Listen())
ErrQuit(sock.GetLastError());
cout << (LPCTSTR)("서버 입니다") << endl;
TCHAR buf[256];
while (1)
{
CSocket newsork;
if(!sock.Accept(newsork))
ErrQuit(sock.GetLastError());
CString PeerAddress;
UINT PeerPort;
newsork.GetPeerName(PeerAddress, PeerPort);
cout << "Connection from " << PeerAddress << " : " << PeerPort << endl;
while (1)
{
int retval;
retval = newsork.Receive(buf, 256);
if (retval == SOCKET_ERROR)
{
ErrQuit(sock.GetLastError());
break;
}
else if (retval == 0)
break;
else
cout << buf;
}
newsork.Close();
cout << endl;
}
}
return nRetCode;
}
else
{
// TODO: 오류 코드를 필요에 따라 수정합니다.
printf("심각한 오류: GetModuleHandle 실패\n");
nRetCode = 1;
}
return nRetCode;
} |
cs |
<클라이언트>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 |
#include <afxsock.h>
void ErrQuit(int err)
{
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); MessageBox(NULL, (LPCTSTR)lpMsgBuf, "오류 발생", MB_OK | MB_ICONINFORMATION);
LocalFree(lpMsgBuf);
ExitProcess(1);
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// MFC를 초기화합니다. 초기화하지 못한 경우 오류를 인쇄합니다.
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 오류 코드를 필요에 따라 수정합니다.
printf("심각한 오류: MFC를 초기화하지 못했습니다.\n");
nRetCode = 1;
}
else
{
// TODO: 응용 프로그램의 동작은 여기에서 코딩합니다.
if (!AfxSocketInit())
AfxMessageBox("소켓 초기화에 실패했습니다");
CSocket sock;
if (!sock.Create())
ErrQuit(sock.GetLastError());
if (!sock.Connect("127.0.0.1", 8000))
ErrQuit(sock.GetLastError());
cout << "클라이언트 입니다." << endl;
char buf[256];
for (int i = 0; i < 10; i++)
{
sprintf_s(buf, "%d번 테스트 메시지 입니다.\n", i);
if (sock.Send(buf, 256) == SOCKET_ERROR)
ErrQuit(sock.GetLastError());
cout << i << "번 메시지를 보냈습니다." << endl;
Sleep(1000);
}
sock.Close();
}
return nRetCode;
}
else
{
// TODO: 오류 코드를 필요에 따라 수정합니다.
printf("심각한 오류: GetModuleHandle 실패\n");
nRetCode = 1;
}
return nRetCode;
} |
cs |