เรื่องมีอยู่ว่ามี Code เก่าอยู่ชุดนึงที่ใช้ TCPClient+TcpListener ส่ง Data ระหว่าง App กัน โดยก่อนจะส่งใช้ตัว BinaryWriter + BinaryFormatter อย่างที่หลายคนน่าจะทราบกันตัว BinaryFormatter จะถูกเอาออกใน .NET8 ถาวร เนื่องจากเรื่องของความปลอดภัย ตอนนี้ยังใช้ได้อยู่นะ แต่เจ็บแล้ว ทำทีเดียวให้จบเลยดีกว่า และไม่อยากมาแก้ไขเยอะ เพราะยังมีบางส่วนทียังเป็น .NET 4.7.2 และไป .NET6 ต่อไม่ได้อย่าง VSTO ด้วย ภาพรวมเป็นตามนี้เลยครับ

Code เดิม
- Client Snippet
TcpClient clientSocket;
clientSocket = new TcpClient();
try
{
clientSocket.Connect(host, port);
}
catch (SocketException socketEx)
{
throw new DSException("Connect-001", "Socket Exception", EXCEPTION_LEVEL.System, socketEx);
}
catch (ObjectDisposedException disposedException)
{
throw new DSException("Connect-001", "Object Disposed Exception", EXCEPTION_LEVEL.System, disposedException);
}- ถ้าลองดู Code Snippet ในส่วนของ sendRequest / getResult จะมีการใช้ BinaryFormatter เยอะเลยครับ
public void sendRequest(ParamDTO message)
{
try
{
NetworkStream ns = clientSocket.GetStream();
ns.WriteTimeout = WriteTimeOut;
BinaryWriter bw = new BinaryWriter(ns);
MemoryStream ms = new MemoryStream();
new BinaryFormatter().Serialize(memoryStream, obj);
bw.Write(ms.ToArray());
bw.Flush();
}
catch (System.Runtime.Serialization.SerializationException se)
{
throw new DSException("sendRequest-001", "Serialization Exception", EXCEPTION_LEVEL.System, se);
}
catch (ObjectDisposedException disposedException)
{
throw new DSException("sendRequest-002", "Object Disposed Exception", EXCEPTION_LEVEL.System, disposedException);
}
}
public object getResult()
{
try
{
NetworkStream ns = clientSocket.GetStream();
ns.ReadTimeout = ReadTimeOut;
object result =((IFormatter)new BinaryFormatter()).Deserialize((Stream)ns);
return result;
}
catch (ObjectDisposedException disposedException)
{
throw new WmslException("", "Object Disposed Exception", EXCEPTION_LEVEL.System, disposedException);
}
}- Server Snippet
- มีตัว TcpListener เอารับ Request และส่งให้ Handler จัดการต่อไป
IPAddress localAddr = IPAddress.Parse(localHost);
TcpListener serverSocket = new TcpListener(localAddr, port);
serverSocket.Start();
TcpClient clientSocket = null;
while (stillRunning)
{
try
{
if (!serverSocket.Pending())
{
Thread.Sleep(sleepTime); // choose a number (in milliseconds) that makes sense
continue; // skip to next iteration of loop
}
clientSocket = serverSocket.AcceptTcpClient();
IPAddress clientAddr = ((IPEndPoint)clientSocket.Client.RemoteEndPoint).Address;
IRTRequestHandler handler = getRequestHandler();
handler.startClient(clientSocket); //Call InvsRequestHandler
}
catch (SocketException ex)
{
logger.Error("Socket error");
}
catch (ObjectDisposedException ex)
{
logger.Error("Client was disposed");
}
catch (Exception wex)
{
clientSocket.GetStream().Close();
clientSocket.Close();
logger.Error(wex.Message);
}
}
logger.Debug("Stop RT Server");
serverSocket.Stop();- ตัว Handler จัดการกับ Request ที่มาครับ
public abstract class AbstractBaseRequestHandler : IRTRequestHandler
{
protected int readTimeout;
protected int writeTimeout;
protected TcpClient clientSocket;
protected AbstractBaseRequestHandler()
{
this.readTimeout = 20000;
this.writeTimeout = 5000;
}
public void startClient(TcpClient clientSocket)
{
this.clientSocket = clientSocket;
Thread ctThread = new Thread(run);
ctThread.Start();
}
protected abstract void run();
}public class InvsRequestHandler : AbstractBaseRequestHandler, IRTRequestHandler
{
protected override void run()
{
try
{
while (true)
{
NetworkStream ns = clientSocket.GetStream();
object deserialized =((IFormatter)new BinaryFormatter()).Deserialize((Stream)ns);
ParamDTO param = (ParamDTO)deserialized;
//Your Business Logic
IList<BusinnesLogicDTO> resultls = ProcessSomeLogic(param);
//...
//Serialize Object and Send Result Back to Client
BinaryWriter bw = new BinaryWriter(ns);
MemoryStream ms = new MemoryStream();
new BinaryFormatter().Serialize(ms, resultls);
bw.Write(ms.ToArray());
bw.Flush();
}
}
catch (Exception wex)
{
clientSocket.Close();
logger.Error(wex.Message);
}
}
}ตอนนี้อย่างน้อย Class หลักๆของ Protocol TCP อย่างตัว TcpClient Class / TcpListener และ IPAddress ยังได้ไปต่ออยู่ครับ เหลือแต่ BinaryFormatter เท่านั้น
Code ใหม่
ลองมาปรับใช้ตัว JSON แทน มันมีหลาย Case ที่ต้อง Handle เช่น
- Null จะจัดการยังไง ?
- Property Private ต้องมาปรับยังไง ?
- หรือ เคส Nest Object เป็นต้นครับ
ตัว JSON เราสามารถเขียน Contract ตอน Serialize ได้ครับ เขียน Contract และทดสอบครับ จะได้เป็น Helper Class ประมาณนี้ครับ
//JSONHelper
private static readonly JsonSerializerSettings _SerializerSettings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore
};
public static Byte[] serializeObject<T>(T pObj)
{
String JSON = JsonConvert.SerializeObject(pObj, typeof(T), _SerializerSettings);
return Encoding.Unicode.GetBytes(JSON);
}
public static T deserialize<T>(String pObj)
{
return JsonConvert.DeserializeObject<T>(pObj, _SerializerSettings);
}- Client Code Snippet
- ส่วนของการ Connect ที่ใช้ (TcpClient) เหมือนเดิมครับ มีแก้ตรง sendRequest / getResult ครับ
public void sendRequest(ParamDTO message)
{
try
{
NetworkStream ns = clientSocket.GetStream();
ns.WriteTimeout = WriteTimeOut;
Byte[] mb = serializeObject<RequestDictionaryMessage>(message); //PING
ns.Write(mb, 0, mb.Length);
ns.Flush();
}
catch (System.Runtime.Serialization.SerializationException se)
{
throw new WmslException("", "Serialization Exception", EXCEPTION_LEVEL.System, se);
}
catch (ObjectDisposedException disposedException)
{
throw new WmslException("", "Object Disposed Exception", EXCEPTION_LEVEL.System, disposedException);
}
}
public T getResult<T>()
{
try
{
NetworkStream ns = clientSocket.GetStream();
ns.ReadTimeout = ReadTimeOut;
StringBuilder response = new StringBuilder();
var buffer = new byte[clientSocket.ReceiveBufferSize+10];
while (ns.DataAvailable)
{
int bytes = ns.Read(buffer, 0, clientSocket.ReceiveBufferSize);
response.Append(Encoding.Unicode.GetString(buffer, 0, bytes));
}
T result = deserialize<T>(response.ToString());
return result;
}
catch (ObjectDisposedException disposedException)
{
throw new DSException("", "Object Disposed Exception", EXCEPTION_LEVEL.System, disposedException);
}
}- Server Code Snippet
- ส่วนของการ Connect ที่ใช้ (TcpListener) เหมือนเดิม ปรับตรงส่วน Handler InvsRequestHandler ครับ
public class InvsRequestHandler : AbstractBaseRequestHandler, IRTRequestHandler
{
protected override void run()
{
try
{
while (true)
{
NetworkStream ns = clientSocket.GetStream();
StringBuilder response = new StringBuilder();
var buffer = new byte[clientSocket.ReceiveBufferSize+10];
while (ns.DataAvailable)
{
int bytes = ns.Read(buffer, 0, clientSocket.ReceiveBufferSize);
response.Append(Encoding.Unicode.GetString(buffer, 0, bytes));
}
if (String.IsNullOrEmpty(response.ToString()))
{
continue;
}
ParamDTO deserialized = JSONHelper.deserialize<ParamDTO>(response.ToString());
//Your Business Logic
IList<BusinnesLogicDTO> resultls= ProcessSomeLogic(param);
//...
//Serialize Object and Send Result Back to Client
Byte[] mb = JSONHelper.serializeObject<IList<BusinnesLogicDT>(resultls);
ns.Write(mb, 0, mb.Length);
ns.Flush();
}
}
catch (Exception wex)
{
clientSocket.Close();
logger.Error(wex.Message);
}
}
}จบไปแล้วการการแก้ปัญหาตัว BinaryFormatter ที่แบบว่าตัวมันเองสารพัดประโยชน์จริงๆ นอกจาก TCP แล้ว จริงๆ มันใช้กับ REST ได้ด้วย แต่ Client ต้องเป็น .NET ด้วย พอมันเจอเคส Security Risk มันเจ็บพอๆหลักเลย นอกจาก Blog นี้แล้วมีอีกเคสของ Binary ที่แสบพอๆกันครับ DeeCopy/DeepClone นอกจากตัว JSON ผมมีลองตัว MessagePack และ ProtoBuf.NET อันนี้ผมมองว่าถ้า .NET Core ขึ้นไปใช้งาน ควรปรับในอนาคตนะ แต่ที่เลือกใช้ JSON เพราะมองว่าใช้ Cost น้อยที่สุด ทั้งเวลา และจุดที่ต้องแก้ไขครับ
ส่วนปัญหาที่พบหลังปรับเป็น JSON ก็มีนะครับ อาทิ เช่น TCP + JSON บน Windows7 แล้ว JSON แหว่ง
จะว่าไปยังไม่ได้เขียน Blog สรุป Step จาก .NET Framework 4.7.2 > .NET 6 เลย ปลายปี .NET 8 จะมาแล้ว
Reference
- Deserialization risks in use of BinaryFormatter and related types | Microsoft Learn
- C# - Deserialize JSON as a stream | MAKOLYTE
- How to read serialized and deserialized json object from TCPClient using WPF in C#? - Stack Overflow
Discover more from naiwaen@DebuggingSoft
Subscribe to get the latest posts sent to your email.



