쪽지보내기
#pragma comment(lib,"Dll")
#include "Packet.h"
#include "resource.h"
#define WM_RECV (WM_APP+1)
#define WM_SEND (WM_APP+2)
#define MYIP "192.168.34.103"
#define MYPORT 1000
#define YOURIP "192.168.34.102"
#define YOURPORT 1000
char str[256];
BOOL CALLBACK DlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam);
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2,0),&wsadata);
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, DlgProc);
WSACleanup();
return 0;
}
BOOL OnInit(HWND hDlg);
BOOL OnCommand(HWND hDlg, WORD cid, WORD cmsg, HWND cWnd);
BOOL OnSend(HWND hDlg, SOCKET sock, WORD msg, WORD eid);
BOOL OnRecv(HWND hDlg, SOCKET sock, WORD msg, WORD eid);
void SendStart(HWND hDlg);
BOOL CALLBACK DlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
switch(iMessage)
{
case WM_INITDIALOG: return OnInit(hDlg);
case WM_COMMAND: return OnCommand(hDlg, LOWORD(wParam), HIWORD(wParam), (HWND)lParam);
case WM_SEND: return OnSend(hDlg, (SOCKET)wParam, LOWORD(lParam), HIWORD(lParam));
case WM_RECV: return OnRecv(hDlg, (SOCKET)wParam, LOWORD(lParam), HIWORD(lParam));
}
return FALSE;
}
BOOL OnInit(HWND hDlg)
{
SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
SOCKADDR_IN servaddr={0,};
servaddr.sin_addr.s_addr = inet_addr(MYIP);
servaddr.sin_port = htons(MYPORT);
servaddr.sin_family = PF_INET;
bind(sock,(SOCKADDR *)&servaddr,sizeof(servaddr));
listen(sock,5);
WSAAsyncSelect(sock,hDlg,WM_RECV,FD_ACCEPT|FD_CLOSE);
//sock에 accept준비가 되거나 closesocket을 해야 할 상태가 오면
//hDlg윈도우에 WM_RECV 메시지를 보내 주세요.
return TRUE;
}
BOOL OnCommand(HWND hDlg, WORD cid, WORD cmsg, HWND cWnd)
{
switch(cid)
{
case IDOK:
GetDlgItemText(hDlg, IDC_EDIT1, str , 256);
SendStart(hDlg);
break;
case IDCANCEL: EndDialog(hDlg, IDCANCEL);
break;
}
return TRUE;
}
void SendStart(HWND hDlg)
{
SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
SOCKADDR_IN servaddr={0,};
servaddr.sin_addr.s_addr = inet_addr(YOURIP);
servaddr.sin_port = htons(YOURPORT);
servaddr.sin_family = PF_INET;
WSAAsyncSelect(sock,hDlg,WM_SEND,FD_CONNECT);
connect(sock,(SOCKADDR *)&servaddr,sizeof(servaddr));
//WSAAsyncSelect 함수가 호출되고 나서, 접속요청 작업의 완료가 있었던 경우.
//connect 함수가 호출되고 난 후에 접속 작업이 완료되었을 때.
}
void OnConnect(HWND hDlg, SOCKET sock, WORD eid);
void OnWrite(HWND hDlg, SOCKET sock, WORD eid);
void OnClose2(HWND hDlg,SOCKET sock,WORD eid);
BOOL OnSend(HWND hDlg, SOCKET sock, WORD msg, WORD eid)
{
switch(msg)
{
case FD_CONNECT: OnConnect(hDlg, sock, eid); break;
case FD_WRITE: OnWrite(hDlg, sock, eid); break;
case FD_CLOSE: OnClose2(hDlg, sock, eid); break;
}
return TRUE;
}
void OnConnect(HWND hDlg, SOCKET sock, WORD eid)
{
WSAAsyncSelect(sock,hDlg,WM_SEND,FD_WRITE);
// connect 나 accept 함수가 호출되고 나서, 접속이 완료 되었을 때
}
void OnWrite(HWND hDlg, SOCKET sock, WORD eid)
{
int slen = strlen(str);
MsgHead msg(0);
int bodylen = sizeof(slen) + slen;
msg.SetBlen(bodylen);
Packet *pack = new Packet();
pack->Pack(&msg, sizeof(MsgHead));
pack->Pack(&slen, sizeof(int));
pack->Pack(str, slen);
pack->Send(sock);
WSAAsyncSelect(sock,hDlg,WM_SEND,FD_CLOSE);
}
void OnClose2(HWND hDlg,SOCKET sock,WORD eid)
{
DWORD nrecv;
if(ioctlsocket(sock,FIONREAD,&nrecv)==0)
{
if(nrecv)
{
PostMessage(hDlg,WM_SEND,sock,(eid>>16)|FD_CLOSE);
}
else
{
closesocket(sock);
}
}
}
/중요/
//send는 수신측이 recv를 하지 않는다 하더라도 수신버퍼가 꽉차지 않으면 수행이 완료 됩니다.
//FD_READ는 수신 버퍼에 수신 데이터가 있게 되면 발생하는 것인데 동시 1개 이상 발행하지 않게 됩니다.
//또한 FD_READ발생시 recv를 send한번에 보낸 만큼만 받게 되는데 이후 아직 수신한 데이터가 있으면 다시 FD_READ는 발생합니다.
//결론적으로 송신측에서 수신측이 모든 recv를 수행하지 않은 시점(데이터는 다 보냈지만)에 closesocket을 하게 되며
//이런 경우에 FD_CLOSE가 발생을 하면 수신 버퍼에 아직 recv하지 않은 것이 있는지 확인해야 한다.
//ioctlsocket메소드에서 두번째 인자를 FIONREAD를 주고 세번째 인자에 ULONG타입이 변수의 주소를 주면
//세번째 인자로 넘긴 주소에 아직 처리되지 않은 수신 버퍼에 있는 데이터의 사이즈를 알 수 있습니다.
//이들에 대한 처리보다 closesocket이 먼저 이루어지면 안 되기 때문에 메시지 큐에 FD_CLOSE를 다시 발생 시켜줌으로써
//(현재 상황은 수신할 데이터가 있기 때문에 FD_READ에 관련 메시지가 메시지 큐에 있는데 FD_CLOSE가 먼저 수행된 것)
//먼저 FD_READ를 수행하게 되고 다시 남은 것이 있으면 당연히 FD_READ가 내부적으로 메시지 큐에 있게 될 것입니다.
//물론 현재 상황은 다시 FD_CLOSE 가 먼저겠지요.
//이를 반복하다보면 결국은 수신할 데이터가 없게 되고
//closesocket을 정상적으로 수행할 수 있는 시기가 오게 되는 것입니다.
void OnAccept(HWND hDlg, SOCKET sock, WORD eid);
void OnRead(HWND hDlg, SOCKET sock, WORD eid);
BOOL OnRecv(HWND hDlg, SOCKET sock, WORD msg, WORD eid)
{
switch(msg)
{
case FD_ACCEPT: OnAccept(hDlg, sock, eid); break;
case FD_READ: OnRead(hDlg, sock, eid); break;
case FD_CLOSE: OnClose2(hDlg, sock, eid); break;
}
return TRUE;
}
void OnAccept(HWND hDlg, SOCKET sock, WORD eid)
{
SOCKADDR_IN cliaddr = {0, };
int len = sizeof(cliaddr);
SOCKET dosock;
dosock = accept(sock, (SOCKADDR*)&cliaddr, &len);
WSAAsyncSelect(dosock, hDlg, WM_RECV, FD_READ|FD_CLOSE);
//Read
//WSAAsyncSelect 함수가 호출 되고나서, 수신할수 있는 데이터가 있을 때
//데이터가 로컬 호스트로 도착하고, FD_READ 가 아직 포스팅되지 않았을 때
//Close
Note : closesocket 함수를 호출하고 난 후에 FD_CLOSE 메시지는 포스팅 되지 않습니다.
}
void OnRead(HWND hDlg, SOCKET sock, WORD eid)
{
int len;
char r_str[256];
memset(r_str,0, 256);
MsgHead msg;
if(recv(sock,(char *)&msg,sizeof(MsgHead),0)<=0) //recv시 false 값에대한 예외처리
//안할경우 수신버퍼에 데이터가 남아있어서 OnRead가 또 실행될수있다.
{
return;
}
Packet *pack = new Packet(sock, msg.Getblen());
pack->UnPack(&len, sizeof(len));
pack->UnPack(r_str, len);
MessageBox(hDlg, r_str , "", MB_OK);
//WSAAsyncSelect(sock, hDlg, WM_RECV, FD_CLOSE); 받을 때는 Close를 해주지 않는다.
}