最近刚做的一个项目,关于 Socket TCP 通信。
需求方提供了一个 ARM 机器,及数据采集器,需要我做一个服务端与数据采集器进行交互。
目的:
数据采集器:定时将读取到的数据发送到服务端。
服务端:将数据采集器发送过来的数据保存在本地。
要求:
1、通信以 TCP 方式进行交互,端口可配置。
2、自己实现握手、心跳包机制。
3、TCP 包结构包括:包头、有效数据总长度、有效数据、CRC 校验、包尾,其中有效数据包括指令序号和指令内容,为经过 AES 128位加密的 XML 数据。
4、加密算法:AES 128位,加密模式:CBC,填充模式:NoPadding。
5、握手过程:(以下过程均以 TCP 包结构加密形式传输,并只提主要内容数据,省略 CRC 校验说明)
(1)数据采集器发送请求包到服务端,服务端解析包结构后取出数据段进行 AES 解密。
(2)服务端判断该包为请求包后回复一个包含一个随机序列的包到数据采集器。
(3)数据采集器接收到包后将随机序列与 MD5 密钥组合后进行 MD5 加密,并回发给服务端。
(4)服务端接收到加密后的内容,并与本地加密后的内容进行比较,通过则回复 pass,否则回复 fail。
(5)数据采集器收到 pass 则握手成功并建立连接,否则握手失败并断开连接。
6、心跳包:握手建立后,采集器每隔一定时间会往服务端发送一个心跳包,服务端收到心跳包后应立即应答,回复服务器当前时间信息供采集器校时。
7、数据包:采集器在握手建立后,会定时向服务端发送断点数据包和实时数据包,服务端接收到以后将数据保存在本地 PC 并回复 pass。
1、创建服务端Socket
(C#)
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 36829); //本机预使用的IP和端口
Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
newsock.Bind(ipep); //绑定
newsock.Listen(10); //监听
(Node.js)
var server = net.createServer(function(socket) { //'connection' listener
console.log('client connected');
});
server.listen(36829, function() { //'listening' listener
console.log('server bound');
});
(C#)
Console.WriteLine("waiting for a client");
Socket client = newsock.Accept(); //当有可用的客户端连接尝试时执行,并返回一个新的socket,用于与客户端之间的通信
IPEndPoint clientip = (IPEndPoint)client.RemoteEndPoint;
Console.WriteLine("connect with client: " + clientip.Address + " at port: " + clientip.Port);
while (true)
{
byte[] buffer = new byte[1024]; //用于缓存客户端所发送的信息,通过socket传递的信息必须为字节数组
int packageLength = client.Receive(buffer); //用于表示客户端发送的信息长度
if (packageLength == 0)
{
Console.WriteLine("解除循环监听,Disconnected from " + clientip.Address);
break; //当信息长度为0,说明客户端连接断开
}
}
var server = net.createServer(function(socket) { //'connection' listener
console.log('client connected');
socket.on('end', function() {
console.log('client disconnected');
});
socket.on('data', function(data){
console.log(data.length);
console.log(data);
});
});
server.listen(36829, function() { //'listening' listener
console.log('server bound');
});
因为其他内容涉及到保密性,所以下面只贴一下 AES 加解密过程
// AES 解密
public static string AESDecrypt(byte[] data)
{
SymmetricAlgorithm aes = Rijndael.Create();
aes.Key = keyArray; // 密钥
aes.IV = keyArray; // 向量
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
byte[] decryptBytes = new byte[data.Length];
using (MemoryStream ms = new MemoryStream(data))
{
using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
cs.Read(decryptBytes, 0, decryptBytes.Length);
cs.Close();
ms.Close();
}
}
aes.Clear();
return System.Text.Encoding.Default.GetString(decryptBytes).Replace("\0", " ");
}
// AES 加密
public static byte[] AESEncrypt(byte[] data)
{
SymmetricAlgorithm aes = Rijndael.Create();
aes.Key = keyArray;
aes.IV = keyArray;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
cs.FlushFinalBlock();
byte[] cipherBytes = ms.ToArray(); // 得到加密后的字节数组
cs.Close();
ms.Close();
aes.Clear();
return cipherBytes;
}
}
}