[소켓 프로그래밍] 손님과 식당의 예로 든 TCP 통신

Jin Hur·2022년 11월 26일
0

Server Programming

목록 보기
4/14

손님 <=> 식당 통화 예시

클라이언트

1) 핸드폰 준비
== 소켓 준비
2) 식당 번호로 문의
== 서버 주소로 connect
3) 식당 안내원이 대리인에 연결, 식당 측과 대화 가능
== 소켓을 통해 서버와 패킷 송수신 가능

서버

1) 식당 안내원 고용
== Linstner 소켓 준비
2) 안내원의 준비 과정 (식당 번호 배정)
== Bind(서버 주소/Port를 소켓에 연동)
3) 영엽 시작
== Listen
4) 안내
== Accept
5) 안내원이 대리인에 연결, 손님과 대화 가능
== 클라이언트 세션을 통해 패킷 송수신


구현

클라이언트

#include "pch.h"

// 윈도우즈 버전의 소켓 라이브러리
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#include <thread>

// 정적 라이브러리 링크
#pragma comment(lib, "ws2_32.lib")

int main() {
	// WinSock 라이브러리(ws2_32.lib) 초기화
	WSAData wsData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsData) != 0) {
		// 초기화 실패
		return 0;	
	}

	// 1. 핸드폰 준비 == 소켓 생성
	//::socket();	// (주소체계, 소켓 타입, 프로토콜)
					// 프로토콜의 경우 0으로 전달하면 알아서 프로토콜을 정해줌
					// TCP/IP 프로토콜 군으로 셋팅될 것
	SOCKET clientSock = ::socket(AF_INET, SOCK_STREAM, 0);
	if (clientSock == INVALID_SOCKET) {
		// 소켓 생성 실패시 에러 메시지 출력
		int32 errCode = ::WSAGetLastError();
		cout << "Socket Err Code: " << errCode << endl;
		return 0;
	}

	// 2. 식당 번호로 문의하기 전 준비 단계 == 서버 주소 준비
	SOCKADDR_IN serverAddr;
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	// (구버전)
	//serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");	// 루프백 주소(스스로의 컴퓨터에 패킷 전달)
						// "127.0.0.1" => 4바이트 정수		// 실제로는 서버 주소를 전달해야함.
	// (신버전)
	::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	serverAddr.sin_port = ::htons(7777);
						// host to network 변환, 호스트에서 네트워크 방식 엔디안으로 변환
						// well-known 포트 번호는 쓰지 않도록..

	// 3. 식당 번호로 문의 == connect
	if (::connect(clientSock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
		int32 errCode = ::WSAGetLastError();
		cout << "Connect Err Code: " << errCode << endl;
		return 0;
	}

	// 4. 로그
	cout << "DONE" << endl;
    
	// 소켓 리소스 반환
	::closesocket(clientSock);
	// 윈속 종료
	::WSACleanup();
}

서버

#include "pch.h"

// 윈도우즈 버전의 소켓 라이브러리
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>

// 정적 라이브러리 링크
#pragma comment(lib, "ws2_32.lib")


int main() {
	// WinSock 라이브러리(ws2_32.lib) 초기화
	WSAData wsData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsData) != 0) {
		// 초기화 실패
		return 0;
	}

	// 1. 핸드폰 준비 == 소켓 생성
	//::socket();	// (주소체계, 소켓 타입, 프로토콜)
					// 프로토콜의 경우 0으로 전달하면 알아서 프로토콜을 정해줌
					// TCP/IP 프로토콜 군으로 셋팅될 것
	SOCKET listenSock = ::socket(AF_INET, SOCK_STREAM, 0);
	if (listenSock == INVALID_SOCKET) {
		// 소켓 생성 실패시 에러 메시지 출력
		int32 errCode = ::WSAGetLastError();
		cout << "Socket Err Code: " << errCode << endl;
		return 0;
	}

	// 2. 식당 번호를 바탕으로 통신 준비 == 서버 주소를 바탕으로 셋팅
	SOCKADDR_IN serverAddr;
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	serverAddr.sin_port = ::htons(7777);

	// bind
	if (::bind(listenSock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
		int32 errCode = ::WSAGetLastError();
		cout << "Bind Err Code: " << errCode << endl;
		return 0;
	}

	// 3. 영업 시작 == listen
	if (::listen(listenSock, 10) == SOCKET_ERROR) {
						// 백로깅(10): 대기열의 최대 한도
		int32 errCode = ::WSAGetLastError();
		cout << "Listen Err Code: " << errCode << endl;
		return 0;
	}

	// 4. 전화 연결 -> 안내 == accept
	while (true) {
		SOCKADDR_IN clientAddr;;
		::memset(&clientAddr, 0, sizeof(clientAddr));
		int32 clientAddrLen = sizeof(clientAddr);
		
		// 안내원 -> 대리인 전화기로 연결 토스
		// 클라이언트와 연결된 소켓이란 의미로 "clientSock"
		SOCKET clientSock = ::accept(listenSock, (SOCKADDR*)&clientAddr, &clientAddrLen);
		if (clientSock == INVALID_SOCKET) {
			int32 errCode = ::WSAGetLastError();
			cout << "Accept Err Code: " << errCode << endl;
			return 0;
		}

		// 손님 입장
		char ipAddr[16];
		::inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddr, sizeof(ipAddr));
		cout << "Client Connect IP: " << ipAddr << endl;
        
	// 소켓 리소스 반환
	::closesocket(listenSock);
	// 윈속 종료
	::WSACleanup();
}


SEND, RECV 추가 구현

클라이언트

int main() {
	...
    ...

	// 4. 로그
	cout << "DONE" << endl;

	
	while (true) {
		
		this_thread::sleep_for(1s);

		char sendBuffer[100] = "Hello World!";

		int32 retCode = ::send(clientSock, sendBuffer, sizeof(sendBuffer), 0);
		if (retCode == SOCKET_ERROR) {
			int32 errCode = ::WSAGetLastError();
			cout << "Send Err Code: " << errCode << endl;
			return 0;
		}

		cout << "SEND DONE" << endl;

		char recvBuffer[1000];
		int32 retDataSize = ::recv(clientSock, recvBuffer, sizeof(recvBuffer), 0);

		if (retDataSize == 0) {
			cout << "FINISH" << endl;
			break;
		}

		cout << "Recv Data: " << recvBuffer << endl;
		cout << "Recv Data Len: " << retDataSize << endl;
	}
	*/

	// 소켓 리소스 반환
	::closesocket(clientSock);
	// 윈속 종료
	::WSACleanup();
}

서버

int main() {
	...
    ...
    
    // 4. 전화 연결 -> 안내 == accept
	while (true) {
    	...
        ...
        
		// 손님 입장
		char ipAddr[16];
		::inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddr, sizeof(ipAddr));
		cout << "Client Connect IP: " << ipAddr << endl;

		while (true) {
			char recvBuffer[1000];

			int32 retDataSize = ::recv(clientSock, recvBuffer, sizeof(recvBuffer), 0);

			if (retDataSize == 0) {
				cout << "FINISH" << endl;
				break;
			}

			cout << "Recv Data: " << recvBuffer << endl;
			cout << "Recv Data Len: " << retDataSize << endl;


			char ackBuffer[] = "ACK";
			int32 retCode = ::send(clientSock, ackBuffer, sizeof(ackBuffer), 0);
			if (retCode == SOCKET_ERROR) {
				int32 errCode = ::WSAGetLastError();
				cout << "Send Err Code: " << errCode << endl;
				return 0;
			}
		}
	}


	// 소켓 리소스 반환
	::closesocket(listenSock);
	// 윈속 종료
	::WSACleanup();
}

0개의 댓글