글 작성자: Sowhat_93

Winsock - 1. WSAGetLastError, FormetMessage

 https://so-what-93.tistory.com/42 

Winsock - 2. TCP 연결 bind,listen,accept 그리고 connect 

https://so-what-93.tistory.com/43

 

연결이 되었으니 데이터 송수신을 진행해 보기로 한다.

 

송수신은 send와 recv를 이용한다.

send와 recv는 사용법이 보이듯이 매우 간단하다.

사용법은 매우 간단하다.

send가 보내는 함수라고 생각할 수 있고,

recv가 받는 함수라고 생각할 수 있다.

 

허나 실제적인 동작은 어디까지나 송수신 커널 버퍼에 대한 복사 작업이다.

 

send 함수의 경우, 커널의 송신 버퍼에 buf의 내용을 len 만큼 카피한다. 

송신버퍼가 담을 수 있는 만큼 최대한 카피한다.

그리고 함수는 얼마나 카피에 성공했는지 반환한다.

 

(예제의 코드는 블로킹 소켓을 사용하고 있다.)

블로킹 소켓일 경우, 커널 송신 버퍼에 빈자리가 1byte 도 없으면 블로킹 된다.

(윈도우즈의 경우 이렇고, 다른 운영체제는 다를수 있다.)

 

recv 함수의 경우, 커널의 수신 버퍼로 부터 buf로 내용을 카피한다.

커널의 수신버퍼로부터 최대로 퍼서 담을수 있는 크기가 len이다.

(App에서 공지한 버퍼 크기이기 때문에 이건 당연하다.)

1byte라도 수신버퍼에 데이터가 있다면 인자로 제공된 버퍼에 카피한다.

그리고 함수는 얼마나 수신버퍼로부터 카피했는지를 반환한다.

 

블로킹 소켓일 경우,  만약 수신버퍼가 비어있다면 데이터가 들어올때까지 블로킹 된다.

 

0 바이트가 return 되는 경우가 있는데, 이것은 상대방으로부터의 연결이 종료된 경우이다.

 

 

msdn을 읽어보면 관련한 내용이 나온다.

https://docs.microsoft.com/en-us/troubleshoot/windows/win32/data-segment-tcp-winsock?WT.mc_id=DT-MVP-4038148 

 

Send data segment over TCP by using Winsock - Application Developer

This article provides a series of recommendations for sending small data packets efficiently from a Winsock application.

docs.microsoft.com

 

위 문서를 읽어보면 아래와 같은 구절이 나온다.

In most cases, the send completion in the application only indicates the data buffer in an application send call is copied to the Winsock kernel buffer and doesn't indicate that the data has hit the network medium. The only exception is when you disable the Winsock buffering by setting SO_SNDBUF to 0.

 

이것 외에도 TCP의 송수신 버퍼사이즈와 윈도우 스케일링등 관련한 많은 내용이 있는데,

이것은 나중에 따로 포스팅하기로 한다.

 

#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;

			strcpy_s(buffer, "Hello Client ^^");
			send(Client, buffer, 1, 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();

}

 

#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;

		while (1)
		{
			char buffer[256] = { 0, };
			int ByteReceived = recv(IWantToConnect2Server, buffer, 256, 0);
			std::cout << "Received " << ByteReceived << " Bytes From Server " << buffer << std::endl;
		}

		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;
}