-
Notifications
You must be signed in to change notification settings - Fork 5
/
SocketClient.cs
584 lines (529 loc) · 19.7 KB
/
SocketClient.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
namespace SinGooCMS.Utility
{
/// <summary>
/// Socket客户端操作类
/// </summary>
[Obsolete("似乎有点问题,暂时不建议使用")]
public static class SocketClient
{
#region 私有字段
/// <summary>
/// 设置数据缓冲区大小 默认1024
/// </summary>
private static readonly int m_maxpacket = 1024 * 4;
#endregion
#region 服务器侦听
/// <summary>
/// 服务器侦听方法 返回null则说明没有链接上
/// </summary>
/// <returns>返回一个套接字(Socket)</returns>
public static Socket ListenerSocket(this TcpListener listener)
{
try
{
return listener.AcceptSocket();
}
catch (Exception ex)
{
return null;
}
}
/// <summary>
/// 服务器侦听方法 返回null则说明没有链接上
/// </summary>
/// <param name="listener">TCP监听对象</param>
/// <returns>返回一个网络流</returns>
public static NetworkStream ListenerStream(this TcpListener listener)
{
try
{
return listener.AcceptTcpClient().GetStream();
}
catch (Exception)
{
return null;
}
}
#endregion
#region 客户端连接
/// <summary>
/// 从客户端连接获取socket对象
/// </summary>
/// <param name="tcpclient">TCP客户端</param>
/// <param name="ipendpoint">客户端节点</param>
/// <returns>客户端socket</returns>
public static Socket ConnectSocket(this TcpClient tcpclient, IPEndPoint ipendpoint)
{
try
{
tcpclient.Connect(ipendpoint);
return tcpclient.Client;
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// 从客户端连接获取socket对象
/// </summary>
/// <param name="tcpclient">TCP客户端</param>
/// <param name="ipadd">IP地址</param>
/// <param name="port">端口号</param>
/// <returns>客户端socket</returns>
public static Socket ConnectSocket(this TcpClient tcpclient, IPAddress ipadd, int port)
{
try
{
tcpclient.Connect(ipadd, port);
return tcpclient.Client;
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// 从客户端获取网络流对象
/// </summary>
/// <param name="tcpclient">TCP客户端</param>
/// <param name="ipendpoint">客户端节点</param>
/// <returns>客户端的网络流</returns>
public static NetworkStream ConnectStream(this TcpClient tcpclient, IPEndPoint ipendpoint)
{
try
{
tcpclient.Connect(ipendpoint);
return tcpclient.GetStream();
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// 从客户端获取网络流对象
/// </summary>
/// <param name="tcpclient">TCP客户端</param>
/// <param name="ipadd">IP地址</param>
/// <param name="port">端口号</param>
/// <returns>客户端网络流对象</returns>
public static NetworkStream ConnectStream(this TcpClient tcpclient, IPAddress ipadd, int port)
{
try
{
tcpclient.Connect(ipadd, port);
return tcpclient.GetStream();
}
catch (Exception)
{
return null;
}
}
#endregion
#region Socket接收数据
/// <summary>
/// 接受固定长度字符串
/// </summary>
/// <param name="socket">socket对象</param>
/// <param name="size">字符串长度</param>
/// <returns>字节数据</returns>
public static byte[] ReceiveFixData(this Socket socket, int size)
{
int offset = 0;
int dataleft = size;
byte[] msg = new byte[size];
while (dataleft > 0)
{
var recv = socket.Receive(msg, offset, dataleft, 0);
if (recv == 0)
{
break;
}
offset += recv;
dataleft -= recv;
}
return msg;
}
/// <summary>
/// 接收变长字符串
/// 为了处理粘包问题 ,每次发送数据时 包头(数据字节长度) + 正文
/// 这个发送小数据
/// 设置包头的字节为8,不能超过8位数的字节数组
/// </summary>
/// <param name="socket">客户端socket</param>
/// <returns>byte[]数组</returns>
public static byte[] ReceiveVarData(this Socket socket)
{
//每次接受数据时,接收固定长度的包头,包头长度为8
byte[] lengthbyte = ReceiveFixData(socket, 8);
//length得到字符长度 然后加工处理得到数字
int length = GetPacketLength(lengthbyte);
//得到正文
return ReceiveFixData(socket, length);
}
/// <summary>
/// 接收T类对象,反序列化
/// </summary>
/// <typeparam name="T">接收T类对象,T类必须是一个可序列化类</typeparam>
/// <param name="socket">客户端socket</param>
/// <returns>强类型对象</returns>
public static T ReceiveVarData<T>(this Socket socket)
{
//先接收包头长度 固定8个字节
byte[] lengthbyte = ReceiveFixData(socket, 8);
//得到字节长度
int length = GetPacketLength(lengthbyte);
byte[] bytecoll = new byte[m_maxpacket];
IFormatter format = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
int offset = 0; //接收字节个数
int lastdata = length; //还剩下多少没有接收,初始大小等于实际大小
int receivedata = m_maxpacket; //每次接收大小
//循环接收
int mark = 0; //标记几次接收到的数据为0长度
while (true)
{
//剩下的字节数是否小于缓存大小
if (lastdata < m_maxpacket)
{
receivedata = lastdata; //就只接收剩下的字节数
}
int count = socket.Receive(bytecoll, 0, receivedata, 0);
if (count > 0)
{
stream.Write(bytecoll, 0, count);
offset += count;
lastdata -= count;
mark = 0;
}
else
{
mark++;
if (mark == 10)
{
break;
}
}
if (offset == length)
{
break;
}
}
stream.Seek(0, SeekOrigin.Begin); //必须要这个 或者stream.Position = 0;
T t = (T)format.Deserialize(stream);
return t;
}
/// <summary>
/// 在预先得到文件的文件名和大小
/// 调用此方法接收文件
/// </summary>
/// <param name="socket">socket服务端</param>
/// <param name="path">路径必须存在</param>
/// <param name="filename">文件名</param>
/// <param name="size">预先知道的文件大小</param>
/// <param name="progress">处理过程</param>
public static bool ReceiveFile(this Socket socket, string path, string filename, long size, Action<int> progress)
{
if (!Directory.Exists(path))
{
return false;
}
//主要是防止有重名文件
string savepath = GetPath(path, filename); //得到文件路径
//缓冲区
byte[] file = new byte[m_maxpacket];
int receivedata = m_maxpacket; //每次要接收的长度
long offset = 0; //循环接收的总长度
long lastdata = size; //剩余多少还没接收
int mark = 0;
using (var fs = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.Write))
{
if (size <= 0)
{
return false;
}
bool ret = false;
while (true)
{
if (lastdata < receivedata)
{
receivedata = Convert.ToInt32(lastdata);
}
var count = socket.Receive(file, 0, receivedata, SocketFlags.None); //每次接收的实际长度
if (count > 0)
{
fs.Write(file, 0, count);
offset += count;
lastdata -= count;
mark = 0;
}
else
{
mark++; //连续5次接收为0字节 则跳出循环
if (mark == 10)
{
break;
}
}
//接收进度
progress(Convert.ToInt32(Convert.ToDouble(offset) / Convert.ToDouble(size) * 100));
//接收完毕
if (offset == size)
{
ret = true;
break;
}
}
return ret;
}
}
/// <summary>
/// 从socket服务端接收文件
/// </summary>
/// <param name="socket">socket服务端</param>
/// <param name="path">文件保存路径(必须存在)</param>
/// <param name="filename">文件名</param>
/// <param name="size">预先知道的文件大小</param>
/// <returns>处理结果</returns>
public static bool ReceiveFile(this Socket socket, string path, string filename, long size)
{
return ReceiveFile(socket, path, filename, size, null);
}
/// <summary>
/// 预先不知道文件名和文件大小 用此方法接收
/// 此方法对于的发送方法是SendFile()
/// </summary>
/// <param name="socket">socket服务端</param>
/// <param name="path">要保存的目录</param>
public static void ReceiveFile(this Socket socket, string path)
{
//得到包头信息字节数组 (文件名 + 文件大小 的字符串长度)
//取前8位
byte[] info_bt = ReceiveFixData(socket, 8);
//得到包头信息字符长度
int info_length = GetPacketLength(info_bt);
//提取包头信息,(文件名 + 文件大小 的字符串长度)
byte[] info = ReceiveFixData(socket, info_length);
//得到文件信息字符串 (文件名 + 文件大小)
string info_str = Encoding.UTF8.GetString(info);
string[] strs = info_str.Split('|');
string filename = strs[0]; //文件名
long length = Convert.ToInt64(strs[1]); //文件大小
//开始接收文件
ReceiveFile(socket, path, filename, length);
}
private static int GetPacketLength(byte[] length)
{
string str = Encoding.UTF8.GetString(length);
str = str.TrimEnd('*');
return int.TryParse(str, out var _length) ? _length : 0;
}
private static int i;
private static string markPath = string.Empty;
/// <summary>
/// 得到文件路径(防止有文件名重复)
/// 如:aaa.txt已经在directory目录下存在,则会得到文件aaa(1).txt
/// </summary>
/// <param name="directory">目录名</param>
/// <param name="file">文件名</param>
/// <returns>文件路径</returns>
public static string GetPath(string directory, string file)
{
if (markPath == string.Empty)
{
markPath = Path.Combine(directory, file);
}
string path = Path.Combine(directory, file);
if (File.Exists(path))
{
i++;
string filename = Path.GetFileNameWithoutExtension(markPath) + "(" + i + ")";
string extension = Path.GetExtension(markPath);
return GetPath(directory, filename + extension);
}
i = 0;
markPath = string.Empty;
return path;
}
#endregion
#region Socket发送数据
/// <summary>
/// 发送固定长度消息
/// 发送字节数不能大于int型最大值
/// </summary>
/// <param name="socket">源socket</param>
/// <param name="msg">消息的字节数组</param>
/// <returns>返回发送字节个数</returns>
public static int SendFixData(this Socket socket, byte[] msg)
{
int size = msg.Length; //要发送字节长度
int offset = 0; //已经发送长度
int dataleft = size; //剩下字符
int senddata = m_maxpacket; //每次发送大小
while (true)
{
//如过剩下的字节数 小于 每次发送字节数
if (dataleft < senddata)
{
senddata = dataleft;
}
int count = socket.Send(msg, offset, senddata, SocketFlags.None);
offset += count;
dataleft -= count;
if (offset == size)
{
break;
}
}
return offset;
}
/// <summary>
/// 发送变长信息 格式 包头(包头占8位) + 正文
/// </summary>
/// <param name="socket">发送方socket对象</param>
/// <param name="contact">发送文本</param>
/// <returns>发送的数据内容长度</returns>
public static int SendVarData(this Socket socket, string contact)
{
//得到字符长度
int size = Encoding.UTF8.GetBytes(contact).Length;
//包头字符
string length = GetSendPacketLengthStr(size);
//包头 + 正文
byte[] sendbyte = Encoding.UTF8.GetBytes(length + contact);
//发送
return SendFixData(socket, sendbyte);
}
/// <summary>
/// 发送变成信息
/// </summary>
/// <param name="socket">发送方socket对象</param>
/// <param name="bytes">消息的 字节数组</param>
/// <returns>消息长度</returns>
public static int SendVarData(this Socket socket, byte[] bytes)
{
//得到包头字节
int size = bytes.Length;
string length = GetSendPacketLengthStr(size);
byte[] lengthbyte = Encoding.UTF8.GetBytes(length);
//发送包头
SendFixData(socket, lengthbyte); //因为不知道正文是什么编码所以没有合并
//发送正文
return SendFixData(socket, bytes);
}
/// <summary>
/// 发送T类型对象,序列化
/// </summary>
/// <typeparam name="T">T类型</typeparam>
/// <param name="socket">发送方的socket对象</param>
/// <param name="obj">T类型对象,必须是可序列化的</param>
/// <returns>消息长度</returns>
public static int SendSerializeObject<T>(this Socket socket, T obj)
{
byte[] bytes = SerializeObject(obj);
return SendVarData(socket, bytes);
}
/// <summary>
/// 发送文件
/// </summary>
/// <param name="socket">socket对象</param>
/// <param name="path">文件路径</param>
/// <param name="issend">是否发送文件(头)信息,如果当前知道文件[大小,名称]则为false</param>
/// <param name="progress">处理过程</param>
/// <returns>处理结果</returns>
public static bool SendFile(this Socket socket, string path, bool issend, Action<int> progress)
{
if (!File.Exists(path))
{
return false;
}
var fileinfo = new FileInfo(path);
string filename = fileinfo.Name;
long length = fileinfo.Length;
//发送文件信息
if (issend)
{
SendVarData(socket, filename + "|" + length);
}
//发送文件
long offset = 0;
byte[] b = new byte[m_maxpacket];
int mark = 0;
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
int senddata = b.Length;
//循环读取发送
while (true)
{
int count = fs.Read(b, 0, senddata);
if (count > 0)
{
socket.Send(b, 0, count, SocketFlags.None);
offset += count;
mark = 0;
}
else
{
mark++;
if (mark == 10)
{
break;
}
}
progress(Convert.ToInt32(Convert.ToDouble(offset) / Convert.ToDouble(length) * 100));
if (offset == length)
{
return true;
}
Thread.Sleep(50); //设置等待时间,以免粘包
}
}
return false;
}
/// <summary>
/// 发送文件,不需要进度信息
/// </summary>
/// <param name="socket">socket对象</param>
/// <param name="path">文件路径</param>
/// <param name="issend">是否发生(头)信息</param>
/// <returns>处理结果</returns>
public static bool SendFile(this Socket socket, string path, bool issend)
{
return SendFile(socket, path, issend, null);
}
/// <summary>
/// 发送文件,不需要进度信息和(头)信息
/// </summary>
/// <param name="socket">socket对象</param>
/// <param name="path">文件路径</param>
/// <returns>处理结果</returns>
public static bool SendFile(this Socket socket, string path)
{
return SendFile(socket, path, false, null);
}
private static byte[] SerializeObject(object obj)
{
IFormatter format = new BinaryFormatter();
using (var stream = new MemoryStream())
{
format.Serialize(stream, obj);
return stream.ToArray();
}
}
private static string GetSendPacketLengthStr(int size)
{
string length = size + "********"; //得到size的长度
return length.Substring(0, 8); //截取前前8位
}
#endregion
}
}