SocketClient.cs 19 KB

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