Client (WinForm) Server (WinForm/Service)
1. 建立 SqlParameter[] 5. 反序列化為 SqlParameter[]
2. 序列化為 byte[] ────────────────────► 6. 用 SqlCommand.Parameters.AddRange()
3. Socket.Send(byte[]) 7. 執行 ExecuteNonQuery/Reader
4. 接收結果 byte[] ◄──────────────────── 8. 序列化結果回傳
public static byte[] SerializeParameters(SqlParameter[] parameters) {
using (var ms = new MemoryStream()) {
var formatter = new BinaryFormatter();
// SqlParameter 實作 ISerializable,可直接序列化
formatter.Serialize(ms, parameters);
return ms.ToArray();
}
}
// 使用範例
var parameters = new[] {
new SqlParameter("@Id", 123),
new SqlParameter("@Name", "Test"),
new SqlParameter("@Image", imageBytes) { SqlDbType = SqlDbType.VarBinary }
};
byte[] payload = SerializeParameters(parameters);
// 發送:先送 4 字節長度,再送 payload
using (var ns = client.GetStream()) {
byte[] len = BitConverter.GetBytes(payload.Length);
ns.Write(len, 0, 4);
ns.Write(payload, 0, payload.Length);
}
public static SqlParameter[] DeserializeParameters(byte[] data) {
using (var ms = new MemoryStream(data)) {
var formatter = new BinaryFormatter();
return (SqlParameter[])formatter.Deserialize(ms);
}
}
// 處理請求
byte[] lenBuf = new byte[4];
ns.Read(lenBuf, 0, 4);
int len = BitConverter.ToInt32(lenBuf, 0);
byte[] payload = new byte[len];
int read = 0;
while (read < len) read += ns.Read(payload, read, len - read);
SqlParameter[] parameters = DeserializeParameters(payload);
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand("INSERT INTO T(Id,Name,Image) VALUES(@Id,@Name,@Image)", conn)) {
cmd.Parameters.AddRange(parameters);
conn.Open();
int affected = cmd.ExecuteNonQuery();
// 回傳 affected 等結果...
}
| 問題 | 解決方案 |
|---|---|
| BinaryFormatter 不安全/已過時 | 改用 protobuf-net、MessagePack 或 System.Text.Json (需自訂 SqlParameter 轉 DTO) |
| 直接執行客戶端 SQL 風險極高 (SQL Injection) | 嚴禁傳輸 SQL 字串。伺服端應預定義 Stored Procedure 或參數化命令,客戶端僅傳參數值。 |
| 大型二進制參數 (如圖片) 封包過大 | 分塊傳輸、壓縮 (GZip/Deflate)、或改用檔案伺服器 + 傳 URL。 |
| 連線管理 | 使用連線池、非同步 SendAsync/ReceiveAsync、心跳保活。 |
// 共用 DTO
public class InsertRequest {
public int Id { get; set; }
public string Name { get; set; }
public byte[] Image { get; set; }
}
// 客戶端
var json = JsonSerializer.Serialize(req);
await stream.WriteAsync(Encoding.UTF8.GetBytes(json));
// 伺服端
var req = JsonSerializer.Deserialize<InsertRequest>(json);
var parameters = new[] {
new SqlParameter("@Id", req.Id),
new SqlParameter("@Name", req.Name),
new SqlParameter("@Image", req.Image) { SqlDbType = SqlDbType.VarBinary }
};
Socket 傳輸參數化 SQL:**嚴禁傳 SQL 字串**,客戶端序列化參數值陣列(或 DTO),伺服端反序列化後注入預定義命令。現代專案建議用 **DTO + JSON/gRPC**,避免 BinaryFormatter 安全風險。