c# socket Header 포함한 통신 방법
C# 소켓 통신 그리고 헤더까지 포함해서 통신하는 방법에 대해 알아보겠습니다.
갑작스럽게 소켓 통신을 그것도 c#으로 만들게 되는 경험을 하게 되었습니다.
저에게 c#은 처음 사용해보는 언어였고 소켓 통신에서 헤더를 사용하는 예제를 찾아봐도 없더군요..
그리고 지금 일하고 있는 회사에서도 아무도 소켓 통신에서 헤더를 사용해본 경험이 없다고 했었어요 하하;
그래서 [ STN, Header, Payload, ETN ] 을 이용해 해결해 본 경험을 바탕으로 다른 분들께 도움이 되고자 작성해 봅니다.
이 글에서 소켓 통신은 Socket 클래스를 기반으로 랩핑되어 있는 TcpListener 와 TcpClient를 사용 합니다.
설명을 보는 사람들이 이해하기 편하도록 튜토리얼 예제를 이용하겠습니다
C# 공식 문서 TcpListener 예제
https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener?view=net-6.0
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
class MyTcpListener
{
public static void Main()
{
TcpListener server=null;
try
{
// Set the TcpListener on port 13000.
Int32 port = 13000;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
// TcpListener server = new TcpListener(port);
server = new TcpListener(localAddr, port);
// Start listening for client requests.
server.Start();
// Buffer for reading data
Byte[] bytes = new Byte[256];
String data = null;
// Enter the listening loop.
while(true)
{
Console.Write("Waiting for a connection... ");
// Perform a blocking call to accept requests.
// You could also use server.AcceptSocket() here.
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");
data = null;
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream();
//------------------------해당 위치가 수정----------------------------------
int i;
// Loop to receive all the data sent by the client.
while((i = stream.Read(bytes, 0, bytes.Length))!=0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("Received: {0}", data);
// Process the data sent by the client.
data = data.ToUpper();
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
// Send back a response.
stream.Write(msg, 0, msg.Length);
Console.WriteLine("Sent: {0}", data);
//-----------------------여기까지 !!!----------------------------------
}
// Shutdown and end connection
client.Close();
}
}
catch(SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
finally
{
// Stop listening for new clients.
server.Stop();
}
Console.WriteLine("\nHit enter to continue...");
Console.Read();
}
}
우선 위에서 부터 호스트와 포트를 지정하여 서버를 생성하고 NetworkStream을 생성합니다.
TcpListener 에서는 Socket 통신의 데이터를 NetworkStream stream 객체로 받게 됩니다.
이 데이터를 Byte[] bytes = new Byte[256]로 선언한 변수에 설정한 초기 값 Byte 단위(이 예제에서는 256 Byte)만큼 씩 나누어 받게 만드는 구조로 되어 있습니다.
문제는 이 예제에서 나와 있는 대로 한다면 문자밖에 받지 못합니다.
다음 코드는 받은 bytes들을 String으로 바로 변환하는 코드입니다.
System.Text.Encoding.ASCII.GetString(bytes, 0, i)
처음에는 이 부분에서 해당 언어에 대한 지식이 없으니까
어떻게 해야 할지 모르겠어서 일단 byte 배열에서 byte들을 하나 씩 뽑아서 모아
2개는 char로, 4개는 int32로, 8개는 int64(long)으로 치환했네요 하하;
우선 빨리 만들어야 해서 구조부터 짜고 쭉쭉 만들어 나가면서 지속적으로 수정하며 작성해본 결과 예시 입니다.
헤더에는 반드시 들어가야 할 데이터가 있을 수 있지만 저는 다음과 같이 사용하겠습니다.
데이터셋
- stx : 시작 값 (1byte)
- version : 버전 번호 (4byte)
- command : 명령 (8byte)
- payload : 데이터 값 (String 100byte)
- etx : 끝 값 (1byte)
TcpClient를 작성해 보겠습니다.
아래 코드를 보시면 보내는 값에 대해 ————— 줄을 쳐 놨습니다.
- 숫자들은 BitConverter.GetBytes()
- 문자는 Encoding.ASCII.GetBytes() 한 것을 확인 할 수 있습니다
- String으로 100byte만큼의 값이 고정으로 들어가도록 Resize 한 것을 확인 할 수 있습니다.
데이터는 Byte[] data에 하나로 묶어서 한번에 보냈습니다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class MyTcpClient
{
static void Main()
{
try
{
// Create a TcpClient.
// Note, for this client to work you need to have a TcpServer
// connected to the same address as specified by the server, port
// combination.
Int32 port = 13000;
TcpClient client = new TcpClient("127.0.0.1", port);
// Get a client stream for reading and writing.
// Stream stream = client.GetStream();
// --------------------------------------------------------------------
NetworkStream stream = client.GetStream();
byte[] stx = { 0x02 };
byte[] version = BitConverter.GetBytes(1);
byte[] command = BitConverter.GetBytes(1003L);
byte[] payload = Encoding.ASCII.GetBytes("Add 1, Please ^^");
Array.Resize(ref payload, 100);
byte[] etx = { 0x03 };
Byte[] data = stx.Concat(version).Concat(command).Concat(payload).Concat(etx).ToArray();
// 데이터 보내기
stream.Write(data, 0, data.Length);
// --------------------------------------------------------------------
// Receive the TcpServer.response.
// Buffer to store the response bytes.
Byte[] data2 = new Byte[256];
// String to store the response ASCII representation.
String responseData = String.Empty;
// Read the first batch of the TcpServer response bytes.
Int32 bytes = stream.Read(data2, 0, data2.Length);
responseData = System.Text.Encoding.ASCII.GetString(data2, 0, bytes);
Console.WriteLine("Received: {0}", responseData);
// Close everything.
stream.Close();
client.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
Console.WriteLine("\n Press Enter to continue...");
Console.Read();
}
}
TcpListener를 작성해 보겠습니다.
stx, header, payload, etx를 구분 할 수 있도록 나누어 받도록 작성했습니다.
자세한 내용은 코드에 주석으로 작성해 놓았습니다.
한 가지 주의하실 사항은 데이터를 받는 byte는 256 byte가 다가 아닙니다.
즉 1500byte가 넘어올지, 5000byte가 넘어올지 모르기에 모든 데이터를 받을 때까지 256byte로 계속해서 나눠 받는다는 것입니다.
위 설명한 부분을 고려해서 추후 직접 구현해 보시면 좋을 것 같네요 ^^!
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
class MyTcpListener
{
public static void Main()
{
TcpListener server = null;
try
{
Int32 port = 13000;
IPAddress localAddr = IPAddress.Parse("127.0.0.1");
server = new TcpListener(localAddr, port);
server.Start();
Byte[] bytes = new Byte[256];
String data = null;
while (true)
{
Console.Write("Waiting for a connection... ");
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Connected!");
data = null;
NetworkStream stream = client.GetStream();
bool stx = true;
bool header = true;
int startIndex = 0;
int stxSize = 1;
int versionSize = 4;
int commandSize = 8;
int payloadSize = 100;
int version;
long command;
string payload;
int i;
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// 1. check stx
if (stx)
{
if (bytes[0] != 0x02)
{
break;
}
stx = false;
}
// 2. check header
if (header)
{
// ToInt32 이기 때문에 [1] 인덱스 부터 4Byte를 읽어 자동으로 int32로 변환
// 1 = 0 + 1
version = BitConverter.ToInt32(bytes, startIndex = stxSize);
Console.WriteLine("version : {0}", version);
// ToInt64 이기 때문에 [5] 인덱스 부터 8Byte를 읽어 자동으로 int64(long)으로 변환
// 5 = 1 + 4
command = BitConverter.ToInt64(bytes, startIndex += versionSize);
Console.WriteLine("command : {0}", command);
header = false;
}
// [13] 인덱스부터 100 byte에 대해 string으로 읽어온다
payload = Encoding.ASCII.GetString(bytes, startIndex += commandSize, payloadSize);
Console.WriteLine("Received: {0}", payload);
// 이번 while loop에서 받은 데이터의 맨 끝의 값이 etx면 종료한다
if (bytes[i - 1] == 0x03)
{
string str = "Add 1003 + 1 : " + (1003 + 1);
byte[] msg = System.Text.Encoding.ASCII.GetBytes(str);
// 반환
stream.Write(msg, 0, msg.Length);
Console.WriteLine("msg : {0}", str);
Console.WriteLine("Connection Close !");
}
}
// Shutdown and end connection
client.Close();
}
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
finally
{
// Stop listening for new clients.
server.Stop();
}
Console.WriteLine("\nHit enter to continue...");
Console.Read();
}
}
결과
서버에서 소켓 통신으로 받고 되돌려 준 데이터
클라이언트에서 소켓 통신 후 받은 데이터
위 방법이 정답인지는 모르겠습니다 ㅎㅎ..
그래도 번뜩이는 아이디어가 나오게 도움이 되었길 바랍니다.
감사합니다.
추가작성
어떠한 문제를 풀어나가냐에 따라 다르겠지만 socket 통신을 사용 할 때
보통 비동기 통신을 주로 사용하는 것으로 확인했습니다