C# TCP IP Socket 프로그래밍 하기(2)

really·2024년 12월 2일

CSharp

목록 보기
2/2

2편에서는 1편의 소켓프로그래밍의 단점을 극복할 수 있는 코드를 작성한다.
clietn에서 보낼 데이터 앞에 2byte header를 추가함으로써 문제를 해결할 수 있다.
그리고 이 header에는 총 보낼 데이터가 몇 byte인지 값을 넣는다.

보낼 데이터가 11byte크기라 header에 11을 넣은 사진이다.

📄<Client.cs>
클라이언트에서 데이터를 보낼때 header의 길이인 2byte를 추가해 newBuffer라는 변수를 선언하여 socket.send(newBuffer)를 해준다.
여기서 dataSize는 header의 길이 2byte를 의미한다.

📄<Server.cs>header의 byte수인 2byte를 clientSocket으로 부터 receive한다.
그리고 받은 바이트수가 1개인 경우로 케이스를 나눔.( else if(totalBytes1 ==1) 부분 )

headerBuffer의 0번째 인덱스에는 데이터가 존재하므로 1번째 인덱스부터 1byte만큼 복사한다.

Receive함수에서 offset부분이 있는데 이것을 MSDN에서 찾아본 결과 다음과 같이 되어 있었다.

the offset parameter refers to the position in the buffer array where the received data should begin to be written.
즉, offset은 버퍼배열에서 수신된 데이터가 write되어질 위치를 의미한다.

코드

📄<Server.cs>

using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Server;

internal class Server
{
	static void Main(string[] args)
	{
		using(Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
		{
			IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("192.168.45.249"), 20000);
			serverSocket.Bind(endPoint);
			serverSocket.Listen(20);

			using (Socket clientSocket = serverSocket.Accept())
			{ 
				// RemoteEndPoint : IP주소와 Port번호 알 수 있음
				Console.WriteLine("연결됨!!" + clientSocket.RemoteEndPoint);
				while (true)
				{
					//byte[] rcvBuffer = new byte[256];
					//header 2byte
					byte[] headerBuffer = new byte[2];
					//int totalBytes1 = clientSocket.Receive(rcvBuffer);
					int totalBytes1 = clientSocket.Receive(headerBuffer);
					if (totalBytes1 < 1)
					{
						Console.WriteLine("클라이언트의 연결 종료");
						return;
					}else if(totalBytes1 == 1)
					{
						// headerBuffer의 0번째 인덱스에는 데이터가 존재하므로 1번째 인덱스부터 1byte만큼 복사
						clientSocket.Receive(headerBuffer, 1, 1, SocketFlags.None);
					}
					// header버퍼를 통해, dataSize지정
					short dataSize = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(headerBuffer)); // 2byte
					byte[] dataBuffer = new byte[dataSize];
					//Console.WriteLine($"dataSize : {dataSize}");
					int totalRecv = 0;

					while(totalRecv < dataSize)
					{
						//실제로 받은 데이터 수가, 받은 데이터 보다 작다면?
						// dataSize : 30byte받아야함
						// 첫루프에서, totalBytes2 = 10이라면 다음 루프에서는 10 offset부터 받고, 30-10=20byte만큼 복사
						int totalBytes2 = clientSocket.Receive(dataBuffer,totalRecv,dataSize - totalRecv, SocketFlags.None);
						totalRecv += totalBytes2;
					}

					// 받은 데이터 역직렬화
					Console.WriteLine($"Server : {Encoding.UTF8.GetString(dataBuffer)}" );
					// Client에게 받은 메시지 전달
					clientSocket.Send(dataBuffer);
				}
            }
		}
    }
}

📄<Client.cs>

using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Client;

internal class Client
{
	static void Main(string[] args)
	{
		using(Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
		{
			IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("192.168.45.249"), 20000);
			socket.Connect(endPoint); //서버와 Connect

			while(true)
			{
				string str = Console.ReadLine();
				if(str == "exit")
				{
					return;
				}
				//입력받은 String을 byte[]로 직렬화
				byte[] strBuffer = Encoding.UTF8.GetBytes(str);
				// newBuffer : strBuffer길이 + header 2byte를 추가한다.
				byte[] newBuffer = new byte[2+strBuffer.Length];
				// 직렬화, header길이는 short 2byte, BigEndian표기로 바꾸고 직렬화
				byte[] dataSize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)strBuffer.Length));
				// 값을 복사한다.
				Array.Copy(dataSize,0,newBuffer,0,dataSize.Length); // dataSize -> newBuffer에 먼저 2byte복사
				Array.Copy(strBuffer, 0, newBuffer, 2, strBuffer.Length); // strBuffer -> newBuffer[2~]부터 복사됨
				socket.Send(newBuffer); // 서버에 입력받은 메시지 전달

				// From server
/*				byte[] rcvBuffer = new byte[256];
				int bytesRead = socket.Receive(rcvBuffer);*/
				byte[] rcvBuffer = new byte[strBuffer.Length];
				int bytesRead = socket.Receive(rcvBuffer);
				if(bytesRead < 1)
				{
                    Console.WriteLine("서버 연결 종료!");
					return;
                } else
				{
                    Console.WriteLine($"From Server : {Encoding.UTF8.GetString(rcvBuffer)}");
				}

			}
		}
	}
}

최종결과

데이터 길이에 구애받지않고 메시지를 전송할 수 있었다.

<참고자료>
인프런 C# TCP/IP 소켓 프로그래밍

0개의 댓글