190710
본 글은 교육을 받으면 노트필기 했던 내용을 그대로 올린내용입니다.
#include <WinSock2.h> // 윈도우즈 헤더보다 앞서서 정의한 것이 많다. 주의. 가장 (windows.h)위에 작성
// Windows.h 에서 이전 버전의 winsock.h 을 포함 시키기 떄문에 충돌이 난다.
#include <windows.h>
#include <TCHAR.H>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
WSACleanup();
return 0;
}
다이얼로그 베이스 기반
#include <WinSock2.h>
#include <windows.h>
#include <TCHAR.H>
#include "resource.h"
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1),NULL/*메인윈도는 부모가 널*/, DlgProc);
WSACleanup();
return 0;
}
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
switch (iMsg) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
break;
}
return FALSE;
}
cf. 메인 윈도우는 부모가 NULL 이다. (바탕화면이 NULL 이기 때문에)
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(sock, (SOCKADDR*)& clientaddr, &addrlen);
스레드의 순서제어
TCP는 경계가 없어서 개발자가 만들어 주어야한다.
memcpy(sendBuf, &dataLen, sizeof(int));
memcpy(sendBuf + 4, &dataType, sizeof(int));
String
문자열 저장방식 2가지
1. 길이+문자집합 (객체지향에서 많이 쓰인다.)
2. 문자집합+끝표식(NULL문자 '\0') (C/C++ 에서 쓰인다.)
이미 정해져 있는 상수는 리터럴 상수("ABC",'a' 등) 는 모두 GD메모리에 자리를 잡는다.
GD는 상수영역과 비상수 영역이 있다.
"ABC"는 "ABC"+'\0' 이 저장된 상수영역의 시작주소를 의미한다.
enum,const,define 으로 상수 만들 수도 있다.
서버코드
#include <WinSock2.h>
#include <windows.h>
#include <TCHAR.H>
#include <vector>
#include <algorithm>
#include "resource.h"
using namespace std;
#define HEADSIZE 8
enum { STRTYPE, FILETYPE };
CRITICAL_SECTION cs;//LOCK
BOOL CreateListenSock(SOCKET& listen_sock);
DWORD __stdcall AcceptThread(LPVOID param);
DWORD __stdcall ClientThread(LPVOID param);
void AddSocketList(vector<SOCKET>& sockList, SOCKET sock);
void RemoveSocketList(vector<SOCKET>& sockList, SOCKET sock);
void SendSocketList(vector<SOCKET>& sockList, const TCHAR* head, const TCHAR* buf, const int len);
int recvn(SOCKET s, char* buf, int len, int flags);
int sendn(SOCKET sock, const char* buf, int len, int flag);
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
void AddListBox(const TCHAR* msg);
HWND hDlg;
vector<SOCKET> sockList;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
InitializeCriticalSection(&cs); //LOCK기법 사용
SOCKET listen_sock;
if (CreateListenSock(listen_sock)) { //CreateListenSock 소켓->바인드->대기 까지함
DWORD id;
CloseHandle(CreateThread(NULL, 0, AcceptThread, (LPVOID)listen_sock, 0, &id));
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL/*메인윈도는 부모가 널*/, DlgProc);
closesocket(listen_sock);
}
DeleteCriticalSection(&cs);
WSACleanup();
return 0;
}
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
switch (iMsg) {
case WM_INITDIALOG:
::hDlg = hwnd;
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
break;
}
return FALSE;
}
BOOL CreateListenSock(SOCKET& listen_sock) {
#pragma region socket()
// socket()
listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) return FALSE;
#pragma endregion
#pragma region bind()
// bind()
SOCKADDR_IN serveraddr;
ZeroMemory(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9000);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
int retval = bind(listen_sock, (SOCKADDR*)& serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR) return FALSE;
#pragma endregion
#pragma region listen()
// listen()
retval = listen(listen_sock, SOMAXCONN);
if (retval == SOCKET_ERROR) return FALSE;// 소켓의 listen() 단계까지 기본적으로 처리
#pragma endregion
return true;
}
DWORD __stdcall AcceptThread(LPVOID param) {
SOCKET listen_sock = (SOCKET)param;
SOCKET client_sock;
SOCKADDR_IN clientaddr;
int addrlen;
while (1) {
#pragma region accept()
addrlen = sizeof(clientaddr);
client_sock = accept(listen_sock, (SOCKADDR*)& clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET)
continue;
#pragma endregion
TCHAR buf[500];
wsprintf(buf, "\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
AddListBox(buf);
AddSocketList(sockList, client_sock);
//sockInfo[client_sock] = clientaddr;
#pragma region Thread 생성
DWORD threadID;
HANDLE hThread;
hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)client_sock, 0, &threadID);//accept() 된 클라이언트소켓으로 통신하는 쓰레드를 생성
CloseHandle(hThread);
#pragma endregion
}
return 0;
}
DWORD __stdcall ClientThread(LPVOID param) {
SOCKET sock = (SOCKET)param;
char head[HEADSIZE];
char* buf; // 동적으로 사이즈가 변한다.
int retval;
while (1) {
//recv
retval = recvn(sock, head, HEADSIZE, 0); // 헤드 받기
if (retval <= 0)
break;
int dataLen;
int dataType;
memcpy(&dataLen, head, sizeof(int));
memcpy(&dataType, head + 4, sizeof(int));
switch (dataType){ // 헤더에 있는 정보로 알맞게 데이터를 처리한다.
case STRTYPE:
{
buf = new char[dataLen];
retval = recvn(sock, buf, dataLen, 0); // 데이터 받기
if (retval <= 0) {
delete[] buf;
break;
}
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(sock, (SOCKADDR*)& clientaddr, &addrlen);
TCHAR msg[1024];
wsprintf(msg, "받은 데이터[%s, %d:%d] : %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), sock, buf);
AddListBox(msg);
SendSocketList(sockList, head, buf, retval); // 받은 데이터를 현재 생성된 모든 클라이언트 소켓들에게 보내기 위해서 호출한다.
delete[] buf;
}
break;
case FILETYPE:
break;
}
}
char msg[1024];
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(sock, (SOCKADDR*)& clientaddr, &addrlen);
wsprintf(msg, "\n[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d, sock:%d\n",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), sock);
AddListBox(msg); // 종료되었다는 메세지를 리스트 박스에 추가한다.
RemoveSocketList(sockList, sock); // 클라이언트 소켓 종료시, 리스트에서도 제거한다.
closesocket(sock);
//sockInfo.erase(sock);
return 0;
}
void AddListBox(const TCHAR* msg) {
HWND hList = GetDlgItem(::hDlg, IDC_LIST1);
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
int count = SendMessage(hList, LB_GETCOUNT, 0, 0);
SendMessage(hList, LB_SETCURSEL, count - 1, 0);
SendMessage(hList, LB_SETCURSEL, -1, 0); // 선택 제거 추가 될 때 마다 스크롤을 아래로 내려주기 위해서
}
void AddSocketList(vector<SOCKET>& sockList, SOCKET sock) {
EnterCriticalSection(&cs);
sockList.push_back(sock);
LeaveCriticalSection(&cs);
}
void RemoveSocketList(vector<SOCKET>& sockList, SOCKET sock) {
EnterCriticalSection(&cs);
sockList.erase(find(sockList.begin(), sockList.end(), sock));
LeaveCriticalSection(&cs);
}
void SendSocketList(vector<SOCKET>& sockList,const TCHAR* head, const TCHAR* buf, const int len) {
EnterCriticalSection(&cs);
for (unsigned i = 0; i < sockList.size(); ++i) {
//send모든 클라이언트 소켓에게 전달한다. (같은 채팅방에 있는 것처럼 체감하도록)
int retval = sendn(sockList[i], head, HEADSIZE, 0); //헤더 보내고
if (retval <= 0)
break;
retval = sendn(sockList[i], buf, len, 0); //데이터 보내고
if (retval <= 0)
break;
}
LeaveCriticalSection(&cs);
}
int recvn(SOCKET s, char* buf, int len, int flags)
{
int received;
char* ptr = buf;
int left = len;// 받아야할 데이터 크기
while (left > 0) {
received = recv(s, ptr, left, flags);
if (received == SOCKET_ERROR)
return SOCKET_ERROR;
else if (received == 0)
break;
left -= received;// 받은 만큼 left에서 뺀다.
ptr += received;// 이후 받아야 되는 부분의 시작주소(offset)
}
return (len - left); // 정상 수행됬으면 받아야하는 크기가 반환된다.
}
int sendn(SOCKET sock, const char* buf, int len, int flag) {
const char* ptr = buf;
int left = len;
while (left > 0) { // 보낼 데이터가 남아있다면
int sendLen = send(sock, ptr, left, flag);
if (sendLen <= 0)
break;
left -= sendLen;// 보낸 만큼 left에서 뺀다.
ptr += sendLen; // 보내야할 시작 주소를 갱신한다.
}
return len - left;
}
클라이언트코드
#include <WinSock2.h>
#include <windows.h>
#include <TCHAR.H>
#include <string.h>
#include "resource.h"
#define HEADSIZE 8
enum { STRTYPE, FILETYPE };
SOCKET ConnectServer(const TCHAR * ip, short port);
DWORD __stdcall RecvThread(LPVOID param);
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
void AddListBox(const TCHAR* msg);
int recvn(SOCKET s, char* buf, int len, int flags);
int sendn(SOCKET sock,const char* buf, int len, int flag);
HWND hDlg;
SOCKET sock;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
return -1;
sock = ConnectServer("127.0.0.1", 9000);// 주어진 ip,port로 connect까지 완료한다. (즉 통신소켓 반환받는다.)
if (NULL != sock) {
DWORD id;
CloseHandle(CreateThread(NULL, 0, RecvThread, (LPVOID)sock, 0, &id)); //통신 스레드 생성후 다이얼로그 박스를 띄운다. (항상 recv를 하기위해서. 서버가 전체에 보낼 때마다 받기위해서)
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL/*메인윈도는 부모가 널*/, DlgProc);
closesocket(sock);
}
WSACleanup();
return 0;
}
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) {
switch (iMsg) {
case WM_INITDIALOG:
hDlg = hwnd;
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
case IDC_BUTTON1: // 전송 버튼을 누르면 전송한다.
{
TCHAR msg[1024];
GetDlgItemText(hwnd, IDC_EDIT1, msg, 1024); // 에디트 박스에 입력한 문자를 가져온다.
TCHAR buf[1124];
wsprintf(buf, "[보낸데이터]:%s\n", msg); // 리스트 박스에 출력
AddListBox(buf);
int dataLen = lstrlen(msg) + 1;
int dataType = STRTYPE;
char* sendBuf = new char[HEADSIZE + dataLen];
memcpy(sendBuf, &dataLen, sizeof(int));
memcpy(sendBuf + 4, &dataType, sizeof(int));
strcpy_s(sendBuf + HEADSIZE, dataLen, msg);
int retval = sendn(sock, sendBuf, HEADSIZE+dataLen, 0);// 헤더까지 포함하여 한번에 보낸다.
SetDlgItemText(hwnd, IDC_EDIT1, ""); // 보내고 나서는 에디트 박스 내용 초기화
}
break;
}
break;
}
return FALSE;
}
void AddListBox(const TCHAR* msg) {
HWND hList = GetDlgItem(::hDlg, IDC_LIST1);
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)msg);
int count = SendMessage(hList, LB_GETCOUNT, 0, 0);
SendMessage(hList, LB_SETCURSEL, count - 1, 0);
SendMessage(hList, LB_SETCURSEL, -1, 0); // 선택 제거
}
SOCKET ConnectServer(const TCHAR* ip, short port) {
// socket()
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) return NULL;
// connect()
SOCKADDR_IN serveraddr;
ZeroMemory(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr(ip);
int retval = connect(sock, (SOCKADDR*)& serveraddr, sizeof(serveraddr));
if (retval == SOCKET_ERROR)
return NULL;
return sock;
}
DWORD __stdcall RecvThread(LPVOID param) {
SOCKET sock = (SOCKET)param;
TCHAR head[HEADSIZE];
TCHAR* buf;
int retval;
while (true)
{
retval = recvn(sock, head, HEADSIZE, 0); // 헤드를 받음
if (retval <= 0)
break;
int dataLen;
int dataType;
memcpy(&dataLen, head, sizeof(int));
memcpy(&dataType, head + 4, sizeof(int));
switch (dataType) { // 헤드를 파악하여 알맞게 데이터를 처리한다.
case STRTYPE:
{
buf = new char[dataLen];
retval = recvn(sock, buf, dataLen, 0); // 헤더에 있는 데이터 길이만큼 데이터를 받는다.
if (retval <= 0) {
delete[] buf;
break;
}
TCHAR msg[1024];
wsprintf(msg, "[받은데이터] : %s\n", buf);
AddListBox(msg);
delete[] buf;
}
break;
case FILETYPE:
break;
}
}
return 0;
}
int recvn(SOCKET s, char* buf, int len, int flags)
{
int received;
char* ptr = buf;
int left = len;
while (left > 0) {
received = recv(s, ptr, left, flags);
if (received == SOCKET_ERROR)
return SOCKET_ERROR;
else if (received == 0)
break;
left -= received;
ptr += received;
}
return (len - left);
}
int sendn(SOCKET sock,const char* buf, int len, int flag) {
const char* ptr = buf;
int left = len;
while (left > 0) { // 남아있는 바이트가 있다면,
int sendLen = send(sock, ptr, left, flag);
if (sendLen <= 0)
break;
left -= sendLen;
ptr += sendLen;
}
return len - left;
}
'Socket Programming' 카테고리의 다른 글
비동기 IO (Overlapped IO) (0) | 2019.09.18 |
---|---|
File IO, fopen_s, fseek, fread, fclose + 윈도우 소켓 라이브러리 (0) | 2019.07.23 |
비동기 Notificaion IO 모델, WSAAsyncSelect, WSAEventSelect, WSAWaitForMultipleEvents, WSAEnumNetworkEvents (0) | 2019.07.23 |
[Window Socket] 다중처리 에코 서버, 클라이언트, 데이터 경계 (0) | 2019.07.15 |
[Window Socket] WSAStartup, 바이트 정렬, IP주소변환, 기본함수 (0) | 2019.07.15 |