글 작성자: Sowhat_93

TCP 연결을 위해서는 우선  포트를 LISTENING 상태로 만들어야 한다.

그래야 TCP 간 3way hand shake를 진행할 수 있다.

이는 netstat -a 로 확인이 가능하다.

 

netstat

소켓을 이용해 구현할 때에는 다음과 같이 진행한다.

매우 간단하다.

1. 소켓을 생성한다.

2. 만들어진 소켓에 주소를 매핑시킨다.

3. listen을 호출한다. 이러면 해당 주소( 해당 소켓) 은 LISTENING 상태가 된다.

SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		//AF_INET       -> 주소체계
		//SOCK_STREAM	-> TCP 전송방식
		//IPPROTO_TCP	-> IPv4 헤더 안에 6이 입력됨.

 

소켓을 만들때에는 socket 함수를 이용해서 만든다.

매개변수에는 주소체계, 소켓타입, 전송 프로토콜 등이 들어간다.마지막의 전송프로토콜의 경우, IPv4 헤더 안에 저 값이 입력되게 된다.와이어샤크를 사용중이라면 간단하게 확인이 가능하다.

 

 

소켓은 커널 오브젝트이기 때문에, 함수의 결과값이 HANDLE 형이다.함수 성공시 HANDLE 값이 나온다. 이 핸들값은 프로세스 내에서만 통용된다.실패시 INVALID_SOCKET이 반환된다.

 

sockaddr_in Myaddr;
inet_pton(AF_INET, "127.0.0.1",&(Myaddr.sin_addr.S_un.S_addr));
Myaddr.sin_family = AF_INET;
Myaddr.sin_port = htons(12345);

//sin_addr_S_un.S_Addr 에는 IP값을 1바이트씩 복사시켜야한다.
//ex) IP 가 255.255.255.255 라면, 0x ff ff ff ff 의 형태로 주어야함.
//또한 빅엔디안 값으로 주어야한다.
//inet_pton 문자열로 표현된 IP 주소값을 network long으로 바꾸고 버퍼에 복사한다.

//sin_port 에는 포트값을 빅엔디안 값으로 입력시켜야한다.
//htons -> host to network short 리틀 엔디안 을 빅엔디안으로. 

if (SOCKET_ERROR == bind(ListenSocket, (sockaddr*)&Myaddr, sizeof(Myaddr))) break;

만든 소켓에 주소를 입혀야 한다.

이때에 bind 함수가 사용된다.

 

대충 'IP 주소 와 port 를 소켓이랑 맞춰준다' 고 생각하면 된다.

 

sockaddr_in 구조체 안에 IP와 port를 넣고, 포인터를 함수에 전달해준다.IP 의 경우 빅엔디안 포맷으로 주소 4자리를 차례대로 복사 시켜주면 된다.port 의 경우 역시 빅엔디안 포맷으로 바꿔주어야 한다.

 

127.0.0.1 이 주소는 잘 알다시피 루프백 주소이다.

많은 책에서 예제를 위해 127.0.0.1 을 준 경우가 참 많은데,

 

마음대로 다른 값을 줄 수도 있다.

이 경우, 사설망을 사용할 경우 매우 유용하다.

원격이 해당 소켓에 대해 해당 설정된 IP 로만 통신을 할 수 있다.

내부의 서버끼리만 통신하고 싶을때,  사설IP 를 여기다가 넣어준다.

 

bind 까지 수행하면, 이제 해당 ip, 해당 port 로 들어오는 패킷에 대해 통신 수행이 가능하다.헌데 아직 해당 소켓은 LISTENING 상태가 안되었다.

if (SOCKET_ERROR == listen(ListenSocket, SOMAXCONN)) break;

//bind			-> 소켓을 IP,PORT와 매칭시킴.   이때부터 통신을 진행할 수 있다.
//listen		-> 소켓을 listen 상태로 만든다. 이때부터 3way hand shake를 수행할 수 있음.

LISTEN 을 호출하면,  해당 port를 LISTENING 상태로 설정한다.

sockaddr_in ClientAddr;
int len = sizeof(ClientAddr);
SOCKET Client = accept(ListenSocket, (sockaddr*)&ClientAddr, &len);
//accept의 결과 연결성립된 Client 소켓이 반환되고,
//이 소켓으로 원격과의 통신을 진행할 수 있다.

char buffer[16];
InetNtopA(AF_INET, &(ClientAddr.sin_addr.S_un.S_addr), buffer, 16);
std::cout << "Remote IP : " << buffer;
std::cout << " Remote port : " << (ClientAddr.sin_port) << std::endl;

 

그리고 위의 과정을 거친 소켓을 인자로 accept를 호출하면접속한 클라이언트에 대해 소켓을 반환하고,

또한 해당 클라이언트의 주소값이 두번째 인자로 전달된 구조체에 채워진다.

이제 반환된 소켓으로 해당 클라이언트와 통신 할 수 있다.

 

listen을 호출해 LISTENING 상태를 만들게 되면 3way handshake를  통한 TCP 연결이 만들어질 수 있음을 의미한다.

연결이 성립된 경우 백로그큐라는 곳에 하나씩 쌓이는데,

accept 는 성립된 연결정보를 꺼내오는 역할을 한다.

따라서 LISTENING 상태에서 연결요청은 많이 이루어지는데 accept를 하지 않으면

백로그 큐가 가득차고,  클라이언트는 연결 실패하게 된다.

 

 

 

클라이언트가 서버로 연결을 요청하는 방법은 매우 간단하다.

sockaddr_in ServerAddr;
ServerAddr.sin_addr.S_un.S_addr = ADDR_ANY;
inet_pton(AF_INET, "127.0.0.1", &(ServerAddr.sin_addr.S_un.S_addr));
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(12345);

서버가 어딘지는 알아야 하니 우선 주소특정을 위해 구조체를 만들고 거기에 값을 기입한다.

SOCKET IWantToConnect2Server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == IWantToConnect2Server) break;
if (SOCKET_ERROR == connect(IWantToConnect2Server, (sockaddr*)&ServerAddr, sizeof(ServerAddr))) break;

서버측이랑 연결되었을시 통신할 소켓을 미리 하나만들고,

서버주소를 넣어서 connect  함수를 호출하면 된다. 

 

실패할 경우 SOCKET_ERROR가 반환된다.실패하는 경우는 몇가지 경우가 있는데, 1. connect 함수가 바로 실패하며 리턴하는 경우. 이 경우는 상대 서버측이 즉각 거부의사를 밝힌 것 이므로방화벽에 막힌 경우 혹은 해당 주소가 LISTENING 상태가 아닌 경우 이다.2. connect 함수가 블로킹 되었다가 타임아웃 시간 이후에 리턴되는 경우.연결하려고 하는 서버의 주소가 있는지도 없는지도 확실치 않은경우 이럴 확률이 높다.헤메다가 결국 타임아웃 되는 경우이다.

 

 

#pragma comment(lib,"ws2_32.lib")			//WSA 라이브러리	
#include <WinSock2.h>						//WSA 헤더.
#include <WS2tcpip.h>						//tcp/ip 주소 변환 관련 함수가 여기에 많다.
#include <iostream>

int main()
{
	WSADATA wsa;
	WSAStartup(0x0202, &wsa);

	int ErrorCode = 0;

	do
	{
		SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		//AF_INET		-> 주소체계
		//SOCK_STREAM	-> TCP 전송방식
		//IPPROTO_TCP	-> IPv4 헤더 안에 6이 입력됨.

		if (INVALID_SOCKET == ListenSocket) break;

		sockaddr_in Myaddr;
		inet_pton(AF_INET, "127.0.0.1",&(Myaddr.sin_addr.S_un.S_addr));
		Myaddr.sin_family = AF_INET;
		Myaddr.sin_port = htons(12345);

		//sin_addr_S_un.S_Addr 에는 IP값을 1바이트씩 복사시켜야한다.
		//ex) IP 가 255.255.255.255 라면, 0x ff ff ff ff 의 형태로 주어야함.
		//또한 빅엔디안 값으로 주어야한다.
		//inet_pton 문자열로 표현된 IP 주소값을 network long으로 바꾸고 버퍼에 복사한다.
		
		//sin_port 에는 포트값을 빅엔디안 값으로 입력시켜야한다.
		//htons -> host to network short 리틀 엔디안 을 빅엔디안으로. 

		if (SOCKET_ERROR == bind(ListenSocket, (sockaddr*)&Myaddr, sizeof(Myaddr))) break;
		if (SOCKET_ERROR == listen(ListenSocket, SOMAXCONN)) break;

		//bind			-> 소켓을 IP,PORT와 매칭시킴.   이때부터 통신을 진행할 수 있다.
		//listen		-> 소켓을 listen 상태로 만든다. 이때부터 3way hand shake를 수행할 수 있음.

		while (1)
		{
			sockaddr_in ClientAddr;
			int len = sizeof(ClientAddr);
			SOCKET Client = accept(ListenSocket, (sockaddr*)&ClientAddr, &len);
			//accept의 결과 연결성립된 Client 소켓이 반환되고,
			//이 소켓으로 원격과의 통신을 진행할 수 있다.

			char buffer[16];
			InetNtopA(AF_INET, &(ClientAddr.sin_addr.S_un.S_addr), buffer, 16);
			std::cout << "Remote IP : " << buffer;
			std::cout << " Remote port : " << (ClientAddr.sin_port) << std::endl;
		}

	} while (false);


	ErrorCode = WSAGetLastError();

	char msgBuf[1024];
	FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msgBuf, 1024, NULL);

	std::cout << msgBuf << std::endl;

	WSACleanup();

}

 

 

#pragma comment(lib,"ws2_32.lib")               //WSA 라이브러리	
#include <WinSock2.h>						    //WSA 헤더.
#include <WS2tcpip.h>					        //tcp/ip 주소 변환 관련 함수가 여기에 많다.
#include <iostream>

/*

Connect

*/

int main()
{
	WSADATA wsa;
	WSAStartup(0x0202, &wsa);

	int ErrorCode = 0;

	//서버의 주소를 설정한다.
	sockaddr_in ServerAddr;
	ServerAddr.sin_addr.S_un.S_addr = ADDR_ANY;
	inet_pton(AF_INET, "127.0.0.1", &(ServerAddr.sin_addr.S_un.S_addr));
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons(12345);


	do
	{
		SOCKET IWantToConnect2Server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (INVALID_SOCKET == IWantToConnect2Server) break;
		if (SOCKET_ERROR == connect(IWantToConnect2Server, (sockaddr*)&ServerAddr, sizeof(ServerAddr))) break;

		std::cout << "Connect To Server Success !" << std::endl;

		system("pause");
		WSACleanup();
		return 0;

	} while (false);

	ErrorCode = WSAGetLastError();

	char msgBuf[1024];
	FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msgBuf, 1024, NULL);

	std::cout << msgBuf << std::endl;

	WSACleanup();

	return 0;
}

와이어샤크로 루프백 패킷을 캡처하면 3way handshake가 진행되는것을 확인할 수 있다.