소켓 프로그래밍
1. Socket
일반적으로 네트워크 프로그래밍이라고 하면 TCP/IP 소켓 프로그래밍을 의미합니다.
소켓은 파일의 개념과 유사합니다.
NIC를 추상화 한 개념이 소켓입니다.
소켓 핸들을 열어서 입출력을 하게 됩니다.
소켓을 사용한 경우에는 반드시 닫아 주어야 합니다.
1) Socket관련 API함수
Socket(): 소켓 생성
Htonl(): 바이트 순서 변환(Host -> Network)
ntohl(): 바이트 순서 변환(Network->Host)
bind(): 소켓과 Port번호 연결
listen(): 접속 요청을 대기
accept(): 접속 요청을 받아들이고 새로운 소켓을 생성하여 접속 요청에 연결
send(): 데이터 전손
recv(): 데이터를 수신
connect(): 지정된 접속 정보를 이용하여 접속을 요청
inet_addr(): IP 주소를 인터넷 주소로 변경
inet_ntoa(): 인터넷 주소를 IP주소로 변경
shutdown(): 접속 종료
closesocket(): 소켓 식별자를 해제
2) 소켓 클래스의 종류
- CAsyncSocket: 비동기 적으로 동작하는 소켓
접속 시점을 알 수 없을 때 사용하는 소켓입니다.
서버는 거의 대부분 CAsyncSocket 왜냐하면 서버는 클라이언트와의 접속 여부에 상관없이 자신의 처리를 수행해야 하기 때문입니다.
채팅이나 메신저 같은 프로그램은 대부분 이 소켓을 이용합니다.
생성자
CAsyncSocket( ): 디폴트 생성자
BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM,
long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,
LPCTSTR lpszSocketAddress = NULL)
nSocketPort: 포트 번호
nSocketType: 소켓 종류
SOCK_STREAM: 데이터가 보낸 순서대로 도착
SOCK_DGRAM: 데이터가 보낸 순서대로 도착하지 안을 수도 있음
lEvent: 사용할 기능에 대한 옵션
lpszSocketAddress: IP주소
멤버 변수
m_hSocket: 소켓 핸들
멤버 함수
Accept(): 접속 요청을 받아들임
BOOL Attach(SOCKET hSocket, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE): 소켓을 연결할 때 사용
Bind(): 소켓과 통신 주소 연결
Close(): 소켓 종료
Connect(): 서버에게 접속 요청
Detach(): 소켓을 해제하고자 할 때 사용
FromHandle(): 소켓 핸들 리턴
Listen(): 클라이언트의 접속 요청을 대기
Receive(): 데이터를 수신
ReceiveFrom(): 데이터를 수신
Send(): 데이터를 송신
SendTo(): 데이터를 송신
ShutDown(): 데이터 송수신을 Disable
OnAccept(): 클라이언트의 접속을 받을 수 있는 조건이 될 때 호출
OnClose(): 소켓이 종료되었을 때 호출
OnConnect(): 접속요청이 완료되었을 때 호출
OnReceive(): 데이터가 수신될 때 호출
OnSend(): 데이터가 송신될 때 호출
- CSocket
동기적으로 동작하는 소켓으로 CAsyncSocket으로부터 상속됩니다.
접속 시점을 알 때 사용하는 소켓으로 동기화와 같은 문제를 전혀 고려하지 않아도 되는 소켓으로 파일전송 등에 많이 이용
프로젝트 생성 시 고급 기능에서 Windows 소켓 항목을 체크하면 연결 헤더 파일 인클루드를 AfxSocketInit() 함수로 처리 가능
간단한 채팅 프로그램 만들기
1. CFormView 기반으로 프로젝트를 생성(고급 기능에서 Windows 소켓 사용) - ChatTest
2. 대화상자 디자인
현재 상태 – IDC_STATE
EDIT1: IDC_EDIT_THATIP
EDIT2: IDC_EDIT_SEND
ListBox: IDC_LIST_MES`
3. 소켓 클래스로부터 상속받는 3개의 소켓 클래스 추가
CWaitSocket
CServerSocket
CClientSocket
4. CWaitSocket의 헤더파일에 Doc 클래스와 View 클래스의 헤더파일 추가하고 변수 선언
#include "ChatTestDoc.h"
#include "ChatTestView.h"
CChatTestView *m_pView;
5. CWaitSocket 클래스의 OnAccept 함수 재정의
m_pView->OnAccept();
6. CClientSocket의 헤더파일에 Doc 클래스와 View 클래스의 헤더파일 추가하고 변수 선언
#include "ChatTestDoc.h"
#include "ChatTestView.h"
CChatTestView *m_pView;
7. CClientSocket 클래스의 OnReceive 함수 재정의
m_pView->OnReceive();
8. CServerSocket에도 6번과 7번 작업 수행
9. View 클래스에 3개의 헤더파일을 인클루드하고 전역 변수 선언
#include "ClientSocket.h"
#include "ServerSocket.h"
#include "WaitSocket.h"
CClientSocket *g_pClientSoc;
CWaitSocket *g_pWaitSoc;
CServerSocket *g_pServerSoc;
int g_State;
10. View 클래스의 OnInitialUpdate 함수에서 초기화
SetDlgItemText(IDC_STATE, _T("현재오프라인상태입니다"));
11. View 클래스의 리스트 박스에 변수 연결
Control Type으로 m_ScrList
12. 접속대기 버튼의 BN_CLICKED 메시지 작성
g_pWaitSoc=new CWaitSocket();
g_pWaitSoc->m_pView=this;
g_pWaitSoc->Create(2000);
g_pWaitSoc->Listen();
g_State=1;
SetDlgItemText(IDC_STATE, _T("다른컴퓨터의접속을대기중입니다"));
13.지금 접속 버튼의 BN_CLICKED 메시지 작성
BOOL result;
TCHAR str[128];
g_pClientSoc=new CClientSocket();
g_pClientSoc->m_pView=this;
g_pClientSoc->Create();
GetDlgItemText(IDC_EDIT_THATIP,str,128);
if (lstrlen(str) == 0) {
MessageBox(_T("접속대상컴퓨터의IP를입력?"),_T("에러"),MB_OK);
return;
}
result=g_pClientSoc->Connect(str,2000);
if (result==TRUE) {
SetDlgItemText(IDC_STATE, _T("클라이언트로접속"));
g_State=3;
}
else {
MessageBox(_T("접속실패"),_T(""),MB_OK);
SetDlgItemText(IDC_STATE, _T("다른컴퓨터의접속을대기중입니다"));
g_State=0;
}
14.View 클래스의 OnAccept()함수 선언
void CChatTestView::OnAccept(void)
{
SetDlgItemText(IDC_STATE, _T("서버로연결되었습니다."));
g_pServerSoc=new CServerSocket;
g_pServerSoc->m_pView=this;
g_pWaitSoc->Accept(*g_pServerSoc);
g_State=2;
}
15. 보내기 버튼의 BN_CLICKED 메시지 작성
TCHAR str[256];
TCHAR str2[256];
GetDlgItemText(IDC_EDIT_SEND,str,256);
if (g_State==2) {
g_pServerSoc->Send(str,256);
}
else {
g_pClientSoc->Send(str,256);
}
wsprintf(str2, _T("보냄: %s"), str);
m_ScrList.AddString(str2);
16.View 클래스에 OnReceive 함수 선언
void CChatTestView::OnReceive(void)
{
TCHAR str[256];
TCHAR str2[256];
if (g_State==2) {
g_pServerSoc->Receive(str,256);
}
else if (g_State==3) {
g_pClientSoc->Receive(str,256);
}
wsprintf(str2, _T("받음: %s"), str);
m_ScrList.AddString(str2);
}
17. View 클래스의 WM_DESTROY 메시지 작성
if (g_pWaitSoc)
delete g_pWaitSoc;
if (g_pClientSoc)
delete g_pClientSoc;
if (g_pServerSoc)
delete g_pServerSoc;
파일 전송 프로그램 만들기
1. 서버 만들기
1) Dialog 기반으로 프로젝트 생성(FileServer) - 고급 기능에서 Windows sockets에 체크
2) 다이얼로그 디자인
EDIT – IDC_MESSAGE – Cstring 형 m_Message
서버시작 버튼: IDC_BTN_STARTSERVER
서버중단 버튼:IDC_BTN_STOPSERVER
3) 다이얼로그 클래스의 OnInitDialog에 작성(버튼 초기화)
GetDlgItem(IDC_BTN_STARTSERVER)->EnableWindow(TRUE);
GetDlgItem(IDC_BTN_STOPSERVER)->EnableWindow(FALSE);
4) 다이얼로그 클래스의 구현 파일 상단에 전역 변수 선언
struct sockaddr_in svr_myaddr_in;
SOCKET svr_ls;
5) 서버를 시작하기 위한 멤버 함수 선언
BOOL CFileServer1Dlg::ServerStart(void)
{
CString msg;
memset((char *)&svr_myaddr_in, 0, sizeof(struct sockaddr_in));
svr_myaddr_in.sin_family = AF_INET;
svr_myaddr_in.sin_addr.s_addr = INADDR_ANY;
svr_myaddr_in.sin_port = htons(9014);
svr_ls = socket(PF_INET, SOCK_STREAM, 0);
if(svr_ls == -1)
{
msg.Format(_T("소켓생성실패."));
AfxMessageBox(msg);
return FALSE;
}
if(bind(svr_ls, (struct sockaddr *)&svr_myaddr_in, sizeof(svr_myaddr_in)) == -1)
{
msg.Format(_T("bind() 함수실행실패."));
AfxMessageBox(msg);
return FALSE;
}
m_Message.Format(_T("서버대기중..."));
UpdateData(FALSE);
m_SvrService = (CSvrService*)AfxBeginThread(RUNTIME_CLASS(CSvrService), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
m_SvrService->SetServerInfo(svr_ls);
m_SvrService->ResumeThread();
return TRUE;
}
6)CWinThread로부터 상속받는 클래스 생성(CSvrService)
7) CSvrService클래스에 변수 선언
SOCKET svr_ls;
SOCKET svr_s;
sockaddr_in svr_peeraddr_in;
8) CSvrService 클래스에 서버 정보를 설정할 함수 선언
void CSvrService::SetServerInfo(SOCKET svr_ls)
{
this->svr_ls = svr_ls;
}
9) CSvrService 클래스의 Run 함수 재정의
int addrlen;
CString msg;
for(;;)
{
if(listen(svr_ls, 5) == -1)
{
msg.Format("listen() 함수실행실패.");
AfxMessageBox(msg);
return CWinThread::Run();
}
addrlen = sizeof(struct sockaddr_in);
svr_s = accept(svr_ls, (struct sockaddr *)&svr_peeraddr_in, &addrlen);
if(svr_s == -1)
{
msg.Format("accept() 함수실행실패.");
AfxMessageBox(msg);
}
else
{
AfxMessageBox("수신성공");
Service();
closesocket(svr_s);
}
}
return CWinThread::Run();
10) CSvrService 클래스에 실제 데이터 통신을 수행 할 멤버 함수 구현
void CSvrService::Service(void)
{
CFile* pFile = NULL;
char recv_msg[1001];
char tmp[101];
char filename[257];
int filenamelen;
int filelen;
int recv_cnt;
char path[256];
char buf[11];
recv(svr_s, recv_msg, 256, 0);
strncpy(filename, recv_msg, 256);
recv(svr_s, recv_msg, 10, 0);
strncpy(tmp, recv_msg, 10);
tmp[10] = '\0';
filenamelen = atoi(tmp);
filename[filenamelen] = '\0';
recv(svr_s, recv_msg, 10, 0);
strncpy(tmp, recv_msg, 10);
tmp[10] = '\0';
filelen = atoi(tmp);
sprintf(path, "C:\\Temp\\%s", filename);
pFile = new CFile((TCHAR *)path, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone);
recv_cnt = 10;
do {
recv_cnt = recv(svr_s, buf, 10, 0);
pFile->Write(buf, recv_cnt);
} while (recv_cnt==10);
pFile->Close();
AfxMessageBox(_T("수신완료"));
}
11) 다이얼로그 클래스에 작성한 클래스의 헤더파일을 인클루드 하고 변수 선언
#include "svrservice.h"
CSvrService* m_SvrService;
12) 서버 시작 버튼의 BN_CLICKED 작성
if(ServerStart() == FALSE)
{
return;
}
GetDlgItem(IDC_BTN_STARTSERVER)->EnableWindow(FALSE);
GetDlgItem(IDC_BTN_STOPSERVER)->EnableWindow(TRUE);
13) 서버 중단 버튼의 BN_CLICKED 작성
m_SvrService->SuspendThread();
closesocket(svr_ls);
m_Message.Format(_T("서버종료."));
UpdateData(FALSE);
GetDlgItem(IDC_BTN_STARTSERVER)->EnableWindow(TRUE);
GetDlgItem(IDC_BTN_STOPSERVER)->EnableWindow(FALSE);
2. 클라이언트 만들기
1) 대화상자 기반으로 프로젝트 생성(FileClient)- 고급 기능에서 Windows sockets에 체크
2) 대화상자 디자인
EDIT: IDC_FILENAME : CString m_FullFileName
파일 선택 버튼: IDC_BTN_SELECTFILE
파일 전송 버튼: IDC_BTN_SENDFILE
3) 다이얼로그 클래스에 변수 선언
CString m_FileName;
CString m_FileLength;
4) 파일 선택 버튼의 BN_CLICKED에 작성
CFileDialog dlg(TRUE);
if(dlg.DoModal() == IDOK)
{
m_FullFileName = dlg.GetPathName();
m_FileName = dlg.GetFileName();
UpdateData(FALSE);
}
5) 파일 전송 버튼의 BN_CLICKED 메시지 작성
struct sockaddr_in peeraddr_in;
SOCKET s;
CString msg;
char send_msg[1001];
memset((char *)&peeraddr_in, 0, sizeof(struct sockaddr_in));
peeraddr_in.sin_family = AF_INET;
peeraddr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
peeraddr_in.sin_port = htons(9014);
s = socket(AF_INET, SOCK_STREAM, 0);
if(s == -1)
{
msg.Format(_T("소켓생성실패."));
AfxMessageBox(msg);
return;
}
if(connect(s, (struct sockaddr *)&peeraddr_in, sizeof(struct sockaddr_in)) == -1)
{
msg.Format(_T("connect() 함수실행실패."));
AfxMessageBox(msg);
return;
}
// File Transfer Routine
CFile* pFile = NULL;
char fullfilename[256];
char filename[256];
int filenamelen;
int filelen;
int send_cnt;
char buf[11];
strcpy(fullfilename, (LPCSTR)(LPCTSTR)m_FullFileName);
strcpy(filename, (LPCSTR)(LPCTSTR)m_FileName);
pFile = new CFile((TCHAR *)filename, CFile::modeRead | CFile::shareDenyNone);
filelen = pFile->GetLength();
sprintf(send_msg, "%-256s", filename);
send(s, send_msg, 256, 0);
filenamelen = strlen(filename);
sprintf(send_msg, "%10d", filenamelen);
send(s, send_msg, 10, 0);
sprintf(send_msg, "%10d", filelen);
send(s, send_msg, 10, 0);
send_cnt = 10;
do {
send_cnt = pFile->Read(buf, 10);
send(s, buf, send_cnt, 0);
} while (send_cnt == 10);
closesocket(s);
AfxMessageBox(_T("전송완료"));
'C & C++ > MFC Network' 카테고리의 다른 글
[Web] 웹브라우져 팁 (0) | 2011.05.25 |
---|---|
[Web] 인터넷 연결여부 확인 (0) | 2011.05.25 |
[Web] 인터넷 프로그래밍 (0) | 2011.04.25 |
[MFC] HTTP 소켓 통신 (0) | 2011.04.23 |
[IP] Lan Card 정보 추출 (0) | 2011.04.18 |
댓글