쪽지보내기

#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를 해주지 않는다.
}

Posted by 아몰라