나초보씨의 실전 C# 네크워크 프로그래밍 4편

명세서를 받고 이틀이 지났습니다. 그동안 나초보씨는 무엇을 했을까요? 짜짠~ 간단한 서버, 클라이언트 어플리케이션을 작성했습니다. MSDN이 제공하는 예제를 조금 고친 것에 불과하지만, 처음 C#을 다뤄보는 나초보씨의 입장에서는 대단한 성과가 아닐 수 없습니다. 나초보씨 스스로도 자신이 만들어낸 작품에 만족하는 듯 합니다. 그럼 나초보씨의 첫번째 C# 코드를 다 같이 감상해 보겠습니다.

제대로 짠 걸까?

‘이보다 간단할 순 없다’ 서버 만들어보기

public class EntryPoint
{
	[STAThread]
	static void Main(string[] args)
	{
		try
		{
			// Set the TcpListener on port 13000.
			Int32 port = 13000;
			IPAddress localAddr = IPAddress.Any;

			// TcpListener server = new TcpListener(port);
			TcpListener server = new TcpListener(localAddr, port);

			// Start listening for client requests.
			server.Start();

			// Enter the listening loop.
			while(true)
			{
				Console.WriteLine("(1) 서버가 클라이언트가 접속하기를 기다린다.......");

				// Perform a blocking call to accept requests.
				// You could also user server.AcceptSocket() here.
				TcpClient client = server.AcceptTcpClient();
				Console.WriteLine("(2) 클라이언트가 접속했다!");

				// Get a stream object for reading and writing
				NetworkStream stream = client.GetStream();
				BufferedStream bufferedStream = new BufferedStream(stream);

				Console.WriteLine("(3) 서버가 클라이언트에게 32비트 정수의 가장 큰 값을 보낸다. 이때 정수값을 Network-byte order로 전송한다.");
				BinaryWriter bw = new BinaryWriter(bufferedStream);
				bw.Write( IPAddress.HostToNetworkOrder(int.MaxValue) );
				bw.Flush();
				Console.WriteLine("Send: " + int.MaxValue);

				Console.WriteLine("(6) 서버는 클라이언트에게서 받은 정수값을 출력한다.");
				BinaryReader br = new BinaryReader(bufferedStream);
				Console.WriteLine("Recv: " + IPAddress.NetworkToHostOrder(br.ReadInt32()));

				// Shutdown and end connection
				Console.WriteLine("(7) 연결 종료한다.");
				client.Close();
			}
		}
		catch(SocketException e)
		{
			Console.WriteLine("SocketException: {0}", e);
		}

		Console.WriteLine("
Hit enter to continue...");
		Console.Read();

	}
}

‘이보다 간단할 순 없다’ 클라이언트 만들어보기

class Class1
{
	[STAThread]
	static void Main(string[] args)
	{
		try
		{
			// Set the TcpListener on port 13000.
			Int32 port = 13000;

			TcpClient client = new TcpClient();
			client.Connect("localhost",port);
			Console.WriteLine("(2) 클라이언트가 접속했다!");

			NetworkStream stream = client.GetStream();
			BufferedStream bufferedStream = new BufferedStream(stream);

			Console.WriteLine("(4) 클라이언트는 서버에게서 받은 정수값을 출력한다.");
			BinaryReader br = new BinaryReader(bufferedStream);
			Console.WriteLine("Recv: " + IPAddress.NetworkToHostOrder(br.ReadInt32()));

			Console.WriteLine("(5) 클라이언트는 서버에게 32비트 정수의 가장 작은 값을 보낸다. 마찬가지로 Network-byte order byte로 전송한다.");
			BinaryWriter bw = new BinaryWriter(bufferedStream);
			bw.Write( IPAddress.HostToNetworkOrder(int.MinValue) );
			bw.Flush();
			Console.WriteLine("Send: " + int.MinValue);

			// Shutdown and end connection
			Console.WriteLine("(7) 연결 종료한다.");
			client.Close();
		}
		catch(Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
		finally
		{
			Console.Read();
		}
	}
}

우선 두개의 어플리케이션이 무엇을 하는지 알아보겠습니다.

  1. 서버가 클라이언트가 접속하기를 기다린다.

  2. 클라이언트가 접속한다.

  3. 서버가 클라이언트에게 32비트 정수의 가장 큰 값(231-1)을 보낸다. 이때 정수값을 Network-byte order로 전송한다.

  4. 클라이언트는 서버에게서 받은 정수값을 출력한다.

  5. 클라이언트는 서버에게 32비트 정수의 가장 작은 값(-231)을 보낸다. 마찬가지로 Network-byte order byte로 전송한다.

  6. 서버는 클라이언트에게서 받은 정수값을 출력한다.

  7. 연결 종료한다.

여기서 잠깐! Network-byte order가 뭐냐?

어떤 언어로든 네트워크 프로그래밍하게 되면, Network-byte order라는 것과는 안면을 트게 됩니다. 세상에는 두 종류의 CPU가 있습니다. 하나는 큰 형님(Big Endian)이고, 다른 하나는 작은 아우(Little Endian)입니다. Sun Microsystems의 Sparcs CPU는 역시 커다란 서버에 쓰이는만큼 큰 형님이시고, Intel의 x86 CPU는 조그마한 PC에 많이 쓰이는지라 작은 아우입니다. 영국 귀족은 프랑스어를 쓰고, 하층민은 영어를 쓰던 시절이 있었습니다. 마찬가지로 큰 형님과 작은 아우는 서로 다른 방식으로 데이터를 표기합니다. 이를테면 큰 형님이 1010이라고 말하는 것을 작은 아우는 0101이라고 말합니다. 그런 까닭에 서로 말이 통하지 않았습니다. 결국 큰 형님들은 말합니다. 작은 아우들아. 앞으로 큰 형님들과 이야기할 때는 큰 형님들의 말을 하도록 해라. 아마도 큰 형님들은 아랫 것들의 말을 배우기 싫었나 봅니다. 이리하여 네트워크 세계에서는 Big Endian이 공통 언어로 자리잡게 되었습니다.

참고 자료

헉헉. 숨이 차는군요. 페이지도 제법 길어진 것 같은데, 저 뿐만 아니라 나초보씨의 실전 C# 네크워크 프로그래밍을 읽고 계신 독자분들도 힘들 것 같습니다. 이쯤에서 일단 접고, 나중에 계속 설명하겠습니다. 다음편을 기대해주세요.

최 재훈

블로그, 페이스북, 트위터 고성능 서버 엔진, 데이터베이스, 지속적인 통합 등 다양한 주제에 관심이 많다.
Close Menu