马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

C# 网络编程之简易聊天示例

[复制链接]
查看2291 | 回复0 | 2019-9-17 11:15:03 | 显示全部楼层 |阅读模式

还记得刚刚开始接触编程开辟时,傻傻的将网站开辟和网络编程混为一谈,常常因分不清晰而引为笑柄。厥后委曲分清晰,又由于各种各样的协议端口之类的名词而倍感秘密,所以为了揭开网络编程的秘密面纱,本文实验以一个简朴的小例子,简述在网络编程开辟中涉及到的相干知识点,仅供学习分享使用,如有不敷之处,还请指正。

概述

在TCP/IP协议族中,传输层紧张包罗TCP和UDP两种通信协议,它们以差异的方式实现两台主机中的差异应用步调之间的数据传输,即数据的端到端传输。由于它们的实现方式差异,因此各有一套属于自己的端口号,且相互独立。采取五元组(协议,信源机IP地址,信源应用进程端口,信宿机IP地址,信宿应用进程端口)来形貌两个应用进程之间的通信关联,这也是进行网络步调计划最根本的概念。传输控制协议(Transmission Control Protocol,TCP)提供一种面向连接的、可靠的数据传输服务,包管了端到端数据传输的可靠性。

涉及知识点

本例中涉及知识点如下所示:

  1. TcpClient : TcpClient类为TCP网络服务提供客户端连接,它构建于Socket类之上,以提供较高级别的TCP服务,提供了通过网络连接、发送和接收数据的简朴方法。
  2. TcpListener:构建于Socket之上,提供了更高抽象级别的TCP服务,使得步调员能更方便地编写服务器端应用步调。通常环境下,服务器端应用步调在启动时将起首绑定当地网络接口的IP地址和端口号,然后进入侦听客户哀求的状态,以便于客户端应用步调提出显式哀求。
  3. NetworkStream:提供网络访问的根本数据流。一旦侦听到有客户端应用步调哀求连接侦听端口,服务器端应用将接受哀求,并创建一个负责与客户端应用步调通信的信道。

网络谈天表示图

如下图所示:看似两个在差异网络上的人谈天,实际上都是通过服务端进行接收转发的。

111902i7mjxcbos2qm1o2s.png

示例截图

服务端截图,如下所示:

111902u9w4m1nyh1cxhcha.png

111903n2v2dz2s8p48s4s6.png

客户端截图,如下所示:开启两个客户端,开始美猴王和二师兄的对话。

111903xej679u567vkrdzv.png

111903a6d2bh095hbqd8hh.png

核心代码

发送信息类,如下所示:

111904smee9nvmvcgd0b82.gif
111904rqa2gg5a6yl3yjez.gif
  1. 1 using System;
  2. 2 using System.Collections.Generic;
  3. 3 using System.Linq;
  4. 4 using System.Text;
  5. 5 using System.Threading.Tasks;
  6. 6
  7. 7 namespace Common
  8. 8 {
  9. 9 /// <summary>
  10. 10 /// 界说一个类,全部要发送的内容,都按照这个来
  11. 11 /// </summary>
  12. 12 public class ChatMessage
  13. 13 {
  14. 14 /// <summary>
  15. 15 /// 头部信息
  16. 16 /// </summary>
  17. 17 public ChatHeader header { get; set; }
  18. 18
  19. 19 /// <summary>
  20. 20 /// 信息类型,默认为文本
  21. 21 /// </summary>
  22. 22 public ChatType chatType { get; set; }
  23. 23
  24. 24 /// <summary>
  25. 25 /// 内容信息
  26. 26 /// </summary>
  27. 27 public string info { get; set; }
  28. 28
  29. 29 }
  30. 30
  31. 31 /// <summary>
  32. 32 /// 头部信息
  33. 33 /// </summary>
  34. 34 public class ChatHeader
  35. 35 {
  36. 36 /// <summary>
  37. 37 /// id唯一标识
  38. 38 /// </summary>
  39. 39 public string id { get; set; }
  40. 40
  41. 41 /// <summary>
  42. 42 /// 源:发送方
  43. 43 /// </summary>
  44. 44 public string source { get; set; }
  45. 45
  46. 46 /// <summary>
  47. 47 /// 目标:接收方
  48. 48 /// </summary>
  49. 49 public string dest { get; set; }
  50. 50
  51. 51 }
  52. 52
  53. 53 /// <summary>
  54. 54 /// 内容标识
  55. 55 /// </summary>
  56. 56 public enum ChatMark
  57. 57 {
  58. 58 BEGIN = 0x0000,
  59. 59 END = 0xFFFF
  60. 60 }
  61. 61
  62. 62 public enum ChatType {
  63. 63 TEXT=0,
  64. 64 IMAGE=1
  65. 65 }
  66. 66 }
复制代码
View Code

打包帮助类,如下所示:全部必要发送的信息,都要进行封装,打包,编码成固定格式,方便分析。

111904gwuyon3g8y7q3q8h.gif
111904mkkqvtartrasgf7o.gif
  1. 1 using System;
  2. 2 using System.Collections.Generic;
  3. 3 using System.Linq;
  4. 4 using System.Text;
  5. 5 using System.Threading.Tasks;
  6. 6
  7. 7 namespace Common
  8. 8 {
  9. 9 /// <summary>
  10. 10 /// 包帮助类
  11. 11 /// </summary>
  12. 12 public class PackHelper
  13. 13 {
  14. 14 /// <summary>
  15. 15 /// 获取待发送的信息
  16. 16 /// </summary>
  17. 17 /// <param name="text"></param>
  18. 18 /// <returns></returns>
  19. 19 public static byte[] GetSendMsgBytes(string text, string source, string dest)
  20. 20 {
  21. 21 ChatHeader header = new ChatHeader()
  22. 22 {
  23. 23 source = source,
  24. 24 dest = dest,
  25. 25 id = Guid.NewGuid().ToString()
  26. 26 };
  27. 27 ChatMessage msg = new ChatMessage()
  28. 28 {
  29. 29 chatType = ChatType.TEXT,
  30. 30 header = header,
  31. 31 info = text
  32. 32 };
  33. 33 string msg01 = GeneratePack<ChatMessage>(msg);
  34. 34 byte[] buffer = Encoding.UTF8.GetBytes(msg01);
  35. 35 return buffer;
  36. 36 }
  37. 37
  38. 38 /// <summary>
  39. 39 /// 天生要发送的包
  40. 40 /// </summary>
  41. 41 /// <typeparam name="T"></typeparam>
  42. 42 /// <param name="t"></param>
  43. 43 /// <returns></returns>
  44. 44 public static string GeneratePack<T>(T t) {
  45. 45 string send = SerializerHelper.JsonSerialize<T>(t);
  46. 46 string res = string.Format("{0}|{1}|{2}",ChatMark.BEGIN.ToString("X").PadLeft(4, '0'), send, ChatMark.END.ToString("X").PadLeft(4, '0'));
  47. 47 int length = res.Length;
  48. 48
  49. 49 return string.Format("{0}|{1}", length.ToString().PadLeft(4, '0'), res);
  50. 50 }
  51. 51
  52. 52 /// <summary>
  53. 53 /// 分析包
  54. 54 /// </summary>
  55. 55 /// <typeparam name="T"></typeparam>
  56. 56 /// <param name="receive">原始接收数据包</param>
  57. 57 /// <returns></returns>
  58. 58 public static T ParsePack<T>(string msg, out string error)
  59. 59 {
  60. 60 error = string.Empty;
  61. 61 int len = int.Parse(msg.Substring(0, 4));//传输内容的长度
  62. 62 string msg2 = msg.Substring(msg.IndexOf("|") + 1);
  63. 63 string[] array = msg2.Split('|');
  64. 64 if (msg2.Length == len)
  65. 65 {
  66. 66 string receive = array[1];
  67. 67 string begin = array[0];
  68. 68 string end = array[2];
  69. 69 if (begin == ChatMark.BEGIN.ToString("X").PadLeft(4, '0') && end == ChatMark.END.ToString("X").PadLeft(4, '0'))
  70. 70 {
  71. 71 T t = SerializerHelper.JsonDeserialize<T>(receive);
  72. 72 if (t != null)
  73. 73 {
  74. 74 return t;
  75. 75
  76. 76 }
  77. 77 else {
  78. 78 error = string.Format("接收的数据有误,无法进行分析");
  79. 79 return default(T);
  80. 80 }
  81. 81 }
  82. 82 else {
  83. 83 error = string.Format("接收的数据格式有误,无法进行分析");
  84. 84 return default(T);
  85. 85 }
  86. 86 }
  87. 87 else {
  88. 88 error = string.Format("接收数据失败,长度不匹配,界说长度{0},实际长度{1}", len, msg2.Length);
  89. 89 return default(T);
  90. 90 }
  91. 91 }
  92. 92 }
  93. 93 }
复制代码
View Code

服务端类,如下所示:服务端开启时,必要进行端口监听,等候链接。

111904xx0eo44mnbjjmeex.gif
111904s5m5bzidf5dw53ia.gif
  1. 1 using Common;
  2. 2 using System;
  3. 3 using System.Collections.Generic;
  4. 4 using System.Configuration;
  5. 5 using System.IO;
  6. 6 using System.Linq;
  7. 7 using System.Net;
  8. 8 using System.Net.Sockets;
  9. 9 using System.Text;
  10. 10 using System.Threading;
  11. 11 using System.Threading.Tasks;
  12. 12
  13. 13 /// <summary>
  14. 14 /// 形貌:MeChat服务端,用于接收数据
  15. 15 /// </summary>
  16. 16 namespace MeChatServer
  17. 17 {
  18. 18 public class Program
  19. 19 {
  20. 20 /// <summary>
  21. 21 /// 服务端IP
  22. 22 /// </summary>
  23. 23 private static string IP;
  24. 24
  25. 25 /// <summary>
  26. 26 /// 服务端口
  27. 27 /// </summary>
  28. 28 private static int PORT;
  29. 29
  30. 30 /// <summary>
  31. 31 /// 服务端监听
  32. 32 /// </summary>
  33. 33 private static TcpListener tcpListener;
  34. 34
  35. 35
  36. 36 public static void Main(string[] args)
  37. 37 {
  38. 38 //初始化信息
  39. 39 InitInfo();
  40. 40 IPAddress ipAddr = IPAddress.Parse(IP);
  41. 41 tcpListener = new TcpListener(ipAddr, PORT);
  42. 42 tcpListener.Start();
  43. 43
  44. 44 Console.WriteLine("等候连接");
  45. 45 tcpListener.BeginAcceptTcpClient(new AsyncCallback(AsyncTcpCallback), "async");
  46. 46 //如果用户按下Esc键,则结束
  47. 47 while (Console.ReadKey().Key != ConsoleKey.Escape)
  48. 48 {
  49. 49 Thread.Sleep(200);
  50. 50 }
  51. 51 tcpListener.Stop();
  52. 52 }
  53. 53
  54. 54 /// <summary>
  55. 55 /// 初始化信息
  56. 56 /// </summary>
  57. 57 private static void InitInfo() {
  58. 58 //初始化服务IP和端口
  59. 59 IP = ConfigurationManager.AppSettings["ip"];
  60. 60 PORT = int.Parse(ConfigurationManager.AppSettings["port"]);
  61. 61 //初始化数据池
  62. 62 PackPool.ToSendList = new List<ChatMessage>();
  63. 63 PackPool.HaveSendList = new List<ChatMessage>();
  64. 64 PackPool.obj = new object();
  65. 65 }
  66. 66
  67. 67 /// <summary>
  68. 68 /// Tcp异步接收函数
  69. 69 /// </summary>
  70. 70 /// <param name="ar"></param>
  71. 71 public static void AsyncTcpCallback(IAsyncResult ar) {
  72. 72 Console.WriteLine("已经连接");
  73. 73 ChatLinker linker = new ChatLinker(tcpListener.EndAcceptTcpClient(ar));
  74. 74 linker.BeginRead();
  75. 75 //继承下一个连接
  76. 76 Console.WriteLine("等候连接");
  77. 77 tcpListener.BeginAcceptTcpClient(new AsyncCallback(AsyncTcpCallback), "async");
  78. 78 }
  79. 79 }
  80. 80 }
复制代码
View Code

客户端类,如下所示:客户端紧张进行数据的封装发送,接收分析等操作,并在页面关闭时,关闭连接。

111905s8udyuvz6y1d161d.gif
111905hr0f2ji2aazjt00z.gif
  1. 1 using Common;
  2. 2 using System;
  3. 3 using System.Collections.Generic;
  4. 4 using System.ComponentModel;
  5. 5 using System.Data;
  6. 6 using System.Drawing;
  7. 7 using System.Linq;
  8. 8 using System.Net.Sockets;
  9. 9 using System.Text;
  10. 10 using System.Threading;
  11. 11 using System.Threading.Tasks;
  12. 12 using System.Windows.Forms;
  13. 13
  14. 14 namespace MeChatClient
  15. 15 {
  16. 16 /// <summary>
  17. 17 /// 谈天页面
  18. 18 /// </summary>
  19. 19 public partial class FrmMain : Form
  20. 20 {
  21. 21 /// <summary>
  22. 22 /// 链接客户端
  23. 23 /// </summary>
  24. 24 private TcpClient tcpClient;
  25. 25
  26. 26 /// <summary>
  27. 27 /// 根本访问的数据流
  28. 28 /// </summary>
  29. 29 private NetworkStream stream;
  30. 30
  31. 31 /// <summary>
  32. 32 /// 读取的缓冲数组
  33. 33 /// </summary>
  34. 34 private byte[] bufferRead;
  35. 35
  36. 36 /// <summary>
  37. 37 /// 昵称信息
  38. 38 /// </summary>
  39. 39 private Dictionary<string, string> dicNickInfo;
  40. 40
  41. 41 public FrmMain()
  42. 42 {
  43. 43 InitializeComponent();
  44. 44 }
  45. 45
  46. 46 private void MainForm_Load(object sender, EventArgs e)
  47. 47 {
  48. 48 //获取昵称
  49. 49 dicNickInfo = ChatInfo.GetNickInfo();
  50. 50 //设置标题
  51. 51 string title = string.Format(":{0}-->{1} 的对话",dicNickInfo[ChatInfo.Source], dicNickInfo[ChatInfo.Dest]);
  52. 52 this.Text = string.Format("{0}:{1}", this.Text, title);
  53. 53 //初始化客户端连接
  54. 54 this.tcpClient = new TcpClient(AddressFamily.InterNetwork);
  55. 55 bufferRead = new byte[this.tcpClient.ReceiveBufferSize];
  56. 56 this.tcpClient.BeginConnect(ChatInfo.IP, ChatInfo.PORT, new AsyncCallback(RequestCallback), null);
  57. 57
  58. 58 }
  59. 59
  60. 60 /// <summary>
  61. 61 /// 异步哀求链接函数
  62. 62 /// </summary>
  63. 63 /// <param name="ar"></param>
  64. 64 private void RequestCallback(IAsyncResult ar) {
  65. 65 this.tcpClient.EndConnect(ar);
  66. 66 this.lblStatus.Text = "连接服务器成功";
  67. 67 //获取流
  68. 68 stream = this.tcpClient.GetStream();
  69. 69 //先发送一个连接信息
  70. 70 string text = CommonVar.LOGIN;
  71. 71 byte[] buffer = PackHelper.GetSendMsgBytes(text,ChatInfo.Source,ChatInfo.Source);
  72. 72 stream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(WriteMessage), null);
  73. 73 //只有stream不为空的时候才可以读
  74. 74 stream.BeginRead(bufferRead, 0, bufferRead.Length, new AsyncCallback(ReadMessage), null);
  75. 75 }
  76. 76
  77. 77 /// <summary>
  78. 78 /// 发送信息
  79. 79 /// </summary>
  80. 80 /// <param name="sender"></param>
  81. 81 /// <param name="e"></param>
  82. 82 private void btnSend_Click(object sender, EventArgs e)
  83. 83 {
  84. 84 string text = this.txtMsg.Text.Trim();
  85. 85 if( string.IsNullOrEmpty(text)){
  86. 86 MessageBox.Show("要发送的信息为空");
  87. 87 return;
  88. 88 }
  89. 89 byte[] buffer = ChatInfo.GetSendMsgBytes(text);
  90. 90 stream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(WriteMessage), null);
  91. 91 this.rtAllMsg.AppendText(string.Format("\r\n[{0}]", dicNickInfo[ChatInfo.Source]));
  92. 92 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Right;
  93. 93 this.rtAllMsg.AppendText(string.Format("\r\n{0}", text));
  94. 94 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Right;
  95. 95 }
  96. 96
  97. 97
  98. 98 /// <summary>
  99. 99 /// 异步读取信息
  100. 100 /// </summary>
  101. 101 /// <param name="ar"></param>
  102. 102 private void ReadMessage(IAsyncResult ar)
  103. 103 {
  104. 104 if (stream.CanRead)
  105. 105 {
  106. 106 int length = stream.EndRead(ar);
  107. 107 if (length >= 1)
  108. 108 {
  109. 109
  110. 110 string msg = string.Empty;
  111. 111 msg = string.Concat(msg, Encoding.UTF8.GetString(bufferRead, 0, length));
  112. 112 //处理接收的数据
  113. 113 string error = string.Empty;
  114. 114 ChatMessage t = PackHelper.ParsePack<ChatMessage>(msg, out error);
  115. 115 if (string.IsNullOrEmpty(error))
  116. 116 {
  117. 117 this.rtAllMsg.Invoke(new Action(() =>
  118. 118 {
  119. 119 this.rtAllMsg.AppendText(string.Format("\r\n[{0}]", dicNickInfo[t.header.source]));
  120. 120 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Left;
  121. 121 this.rtAllMsg.AppendText(string.Format("\r\n{0}", t.info));
  122. 122 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Left;
  123. 123 this.lblStatus.Text = "接收数据成功!";
  124. 124 }));
  125. 125 }
  126. 126 else {
  127. 127 this.lblStatus.Text = "接收数据失败:"+error;
  128. 128 }
  129. 129 }
  130. 130 //继承读数据
  131. 131 stream.BeginRead(bufferRead, 0, bufferRead.Length, new AsyncCallback(ReadMessage), null);
  132. 132 }
  133. 133 }
  134. 134
  135. 135 /// <summary>
  136. 136 /// 发送成功
  137. 137 /// </summary>
  138. 138 /// <param name="ar"></param>
  139. 139 private void WriteMessage(IAsyncResult ar)
  140. 140 {
  141. 141 this.stream.EndWrite(ar);
  142. 142 //发送成功
  143. 143 }
  144. 144
  145. 145 /// <summary>
  146. 146 /// 页面关闭,断开连接
  147. 147 /// </summary>
  148. 148 /// <param name="sender"></param>
  149. 149 /// <param name="e"></param>
  150. 150 private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
  151. 151 {
  152. 152 if (MessageBox.Show("正在通话中,确定要关闭吗?", "关闭", MessageBoxButtons.YesNo) == DialogResult.Yes)
  153. 153 {
  154. 154 e.Cancel = false;
  155. 155 string text = CommonVar.QUIT;
  156. 156 byte[] buffer = ChatInfo.GetSendMsgBytes(text);
  157. 157 stream.Write(buffer, 0, buffer.Length);
  158. 158 //发送完成后,关闭连接
  159. 159 this.tcpClient.Close();
  160. 160
  161. 161 }
  162. 162 else {
  163. 163 e.Cancel = true;
  164. 164 }
  165. 165 }
  166. 166 }
  167. 167 }
复制代码
View Code

备注:本示例中,全部的创建连接,数据接收,发送等都是采取异步方式,防止页面卡顿。

备注

每一次的努力,都是幸运的伏笔。







来源:https://www.cnblogs.com/hsiang/archive/2019/09/16/11530691.html
C#论坛 www.ibcibc.com IBC编程社区
C#
C#论坛
IBC编程社区
*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则