C# 网络编程之简易聊天示例
<p>还记得刚刚开始接触编程开辟时,傻傻的将网站开辟和网络编程混为一谈,常常因分不清晰而引为笑柄。厥后委曲分清晰,又由于各种各样的协议端口之类的名词而倍感秘密,所以为了揭开网络编程的秘密面纱,本文实验以一个简朴的小例子,简述在网络编程开辟中涉及到的相干知识点,仅供学习分享使用,如有不敷之处,还请指正。</p><h2>概述</h2>
<p>在TCP/IP协议族中,传输层紧张包罗TCP和UDP两种通信协议,它们以差异的方式实现两台主机中的差异应用步调之间的数据传输,即数据的端到端传输。由于它们的实现方式差异,因此各有一套属于自己的端口号,且相互独立。采取五元组(协议,信源机IP地址,信源应用进程端口,信宿机IP地址,信宿应用进程端口)来形貌两个应用进程之间的通信关联,这也是进行网络步调计划最根本的概念。传输控制协议(Transmission Control Protocol,TCP)提供一种面向连接的、可靠的数据传输服务,包管了端到端数据传输的可靠性。</p>
<h2>涉及知识点</h2>
<p>本例中涉及知识点如下所示:</p>
<ol>
<li>TcpClient : TcpClient类为TCP网络服务提供客户端连接,它构建于Socket类之上,以提供较高级别的TCP服务,提供了通过网络连接、发送和接收数据的简朴方法。</li>
<li>TcpListener:构建于Socket之上,提供了更高抽象级别的TCP服务,使得步调员能更方便地编写服务器端应用步调。通常环境下,服务器端应用步调在启动时将起首绑定当地网络接口的IP地址和端口号,然后进入侦听客户哀求的状态,以便于客户端应用步调提出显式哀求。</li>
<li>NetworkStream:提供网络访问的根本数据流。一旦侦听到有客户端应用步调哀求连接侦听端口,服务器端应用将接受哀求,并创建一个负责与客户端应用步调通信的信道。</li>
</ol>
<h2>网络谈天表示图</h2>
<p>如下图所示:看似两个在差异网络上的人谈天,实际上都是通过服务端进行接收转发的。</p>
<p><div align="center"></div></p>
<h2>示例截图</h2>
<p>服务端截图,如下所示:</p>
<p><div align="center"></div></p>
<p><div align="center"></div></p>
<p>客户端截图,如下所示:开启两个客户端,开始美猴王和二师兄的对话。</p>
<p><div align="center"></div></p>
<p><div align="center"></div></p>
<h2>核心代码</h2>
<p>发送信息类,如下所示:</p>
<div align="center"></div><div align="center"></div>
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace Common
8 {
9 /// <summary>
10 /// 界说一个类,全部要发送的内容,都按照这个来
11 /// </summary>
12 public class ChatMessage
13 {
14 /// <summary>
15 /// 头部信息
16 /// </summary>
17 public ChatHeader header { get; set; }
18
19 /// <summary>
20 /// 信息类型,默认为文本
21 /// </summary>
22 public ChatType chatType { get; set; }
23
24 /// <summary>
25 /// 内容信息
26 /// </summary>
27 public string info { get; set; }
28
29 }
30
31 /// <summary>
32 /// 头部信息
33 /// </summary>
34 public class ChatHeader
35 {
36 /// <summary>
37 /// id唯一标识
38 /// </summary>
39 public string id { get; set; }
40
41 /// <summary>
42 /// 源:发送方
43 /// </summary>
44 public string source { get; set; }
45
46 /// <summary>
47 /// 目标:接收方
48 /// </summary>
49 public string dest { get; set; }
50
51 }
52
53 /// <summary>
54 /// 内容标识
55 /// </summary>
56 public enum ChatMark
57 {
58 BEGIN= 0x0000,
59 END = 0xFFFF
60 }
61
62 public enum ChatType {
63 TEXT=0,
64 IMAGE=1
65 }
66 }
View Code
<p>打包帮助类,如下所示:全部必要发送的信息,都要进行封装,打包,编码成固定格式,方便分析。</p>
<div align="center"></div><div align="center"></div>
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading.Tasks;
6
7 namespace Common
8 {
9 /// <summary>
10 /// 包帮助类
11 /// </summary>
12 public class PackHelper
13 {
14 /// <summary>
15 /// 获取待发送的信息
16 /// </summary>
17 /// <param name="text"></param>
18 /// <returns></returns>
19 public static byte[] GetSendMsgBytes(string text, string source, string dest)
20 {
21 ChatHeader header = new ChatHeader()
22 {
23 source = source,
24 dest = dest,
25 id = Guid.NewGuid().ToString()
26 };
27 ChatMessage msg = new ChatMessage()
28 {
29 chatType = ChatType.TEXT,
30 header = header,
31 info = text
32 };
33 string msg01 = GeneratePack<ChatMessage>(msg);
34 byte[] buffer = Encoding.UTF8.GetBytes(msg01);
35 return buffer;
36 }
37
38 /// <summary>
39 /// 天生要发送的包
40 /// </summary>
41 /// <typeparam name="T"></typeparam>
42 /// <param name="t"></param>
43 /// <returns></returns>
44 public static string GeneratePack<T>(T t) {
45 string send = SerializerHelper.JsonSerialize<T>(t);
46 string res = string.Format("{0}|{1}|{2}",ChatMark.BEGIN.ToString("X").PadLeft(4, '0'), send, ChatMark.END.ToString("X").PadLeft(4, '0'));
47 int length = res.Length;
48
49 return string.Format("{0}|{1}", length.ToString().PadLeft(4, '0'), res);
50 }
51
52 /// <summary>
53 /// 分析包
54 /// </summary>
55 /// <typeparam name="T"></typeparam>
56 /// <param name="receive">原始接收数据包</param>
57 /// <returns></returns>
58 public static T ParsePack<T>(string msg, out string error)
59 {
60 error = string.Empty;
61 int len = int.Parse(msg.Substring(0, 4));//传输内容的长度
62 string msg2 = msg.Substring(msg.IndexOf("|") + 1);
63 string[] array = msg2.Split('|');
64 if (msg2.Length == len)
65 {
66 string receive = array;
67 string begin = array;
68 string end = array;
69 if (begin == ChatMark.BEGIN.ToString("X").PadLeft(4, '0') && end == ChatMark.END.ToString("X").PadLeft(4, '0'))
70 {
71 T t = SerializerHelper.JsonDeserialize<T>(receive);
72 if (t != null)
73 {
74 return t;
75
76 }
77 else {
78 error = string.Format("接收的数据有误,无法进行分析");
79 return default(T);
80 }
81 }
82 else {
83 error = string.Format("接收的数据格式有误,无法进行分析");
84 return default(T);
85 }
86 }
87 else {
88 error = string.Format("接收数据失败,长度不匹配,界说长度{0},实际长度{1}", len, msg2.Length);
89 return default(T);
90 }
91 }
92 }
93 }
View Code
<p>服务端类,如下所示:服务端开启时,必要进行端口监听,等候链接。</p>
<div align="center"></div><div align="center"></div>
1 using Common;
2 using System;
3 using System.Collections.Generic;
4 using System.Configuration;
5 using System.IO;
6 using System.Linq;
7 using System.Net;
8 using System.Net.Sockets;
9 using System.Text;
10 using System.Threading;
11 using System.Threading.Tasks;
12
13 /// <summary>
14 /// 形貌:MeChat服务端,用于接收数据
15 /// </summary>
16 namespace MeChatServer
17 {
18 public class Program
19 {
20 /// <summary>
21 /// 服务端IP
22 /// </summary>
23 private static string IP;
24
25 /// <summary>
26 /// 服务端口
27 /// </summary>
28 private static int PORT;
29
30 /// <summary>
31 /// 服务端监听
32 /// </summary>
33 private static TcpListener tcpListener;
34
35
36 public static void Main(string[] args)
37 {
38 //初始化信息
39 InitInfo();
40 IPAddress ipAddr = IPAddress.Parse(IP);
41 tcpListener = new TcpListener(ipAddr, PORT);
42 tcpListener.Start();
43
44 Console.WriteLine("等候连接");
45 tcpListener.BeginAcceptTcpClient(new AsyncCallback(AsyncTcpCallback), "async");
46 //如果用户按下Esc键,则结束
47 while (Console.ReadKey().Key != ConsoleKey.Escape)
48 {
49 Thread.Sleep(200);
50 }
51 tcpListener.Stop();
52 }
53
54 /// <summary>
55 /// 初始化信息
56 /// </summary>
57 private static void InitInfo() {
58 //初始化服务IP和端口
59 IP = ConfigurationManager.AppSettings["ip"];
60 PORT = int.Parse(ConfigurationManager.AppSettings["port"]);
61 //初始化数据池
62 PackPool.ToSendList = new List<ChatMessage>();
63 PackPool.HaveSendList = new List<ChatMessage>();
64 PackPool.obj = new object();
65 }
66
67 /// <summary>
68 /// Tcp异步接收函数
69 /// </summary>
70 /// <param name="ar"></param>
71 public static void AsyncTcpCallback(IAsyncResult ar) {
72 Console.WriteLine("已经连接");
73 ChatLinker linker = new ChatLinker(tcpListener.EndAcceptTcpClient(ar));
74 linker.BeginRead();
75 //继承下一个连接
76 Console.WriteLine("等候连接");
77 tcpListener.BeginAcceptTcpClient(new AsyncCallback(AsyncTcpCallback), "async");
78 }
79 }
80 }
View Code
<p>客户端类,如下所示:客户端紧张进行数据的封装发送,接收分析等操作,并在页面关闭时,关闭连接。</p>
<div align="center"></div><div align="center"></div>
1 using Common;
2 using System;
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.Data;
6 using System.Drawing;
7 using System.Linq;
8 using System.Net.Sockets;
9 using System.Text;
10 using System.Threading;
11 using System.Threading.Tasks;
12 using System.Windows.Forms;
13
14 namespace MeChatClient
15 {
16 /// <summary>
17 /// 谈天页面
18 /// </summary>
19 public partial class FrmMain : Form
20 {
21 /// <summary>
22 /// 链接客户端
23 /// </summary>
24 private TcpClient tcpClient;
25
26 /// <summary>
27 /// 根本访问的数据流
28 /// </summary>
29 private NetworkStream stream;
30
31 /// <summary>
32 /// 读取的缓冲数组
33 /// </summary>
34 private byte[] bufferRead;
35
36 /// <summary>
37 /// 昵称信息
38 /// </summary>
39 private Dictionary<string, string> dicNickInfo;
40
41 public FrmMain()
42 {
43 InitializeComponent();
44 }
45
46 private void MainForm_Load(object sender, EventArgs e)
47 {
48 //获取昵称
49 dicNickInfo = ChatInfo.GetNickInfo();
50 //设置标题
51 string title = string.Format(":{0}-->{1} 的对话",dicNickInfo, dicNickInfo);
52 this.Text = string.Format("{0}:{1}", this.Text, title);
53 //初始化客户端连接
54 this.tcpClient = new TcpClient(AddressFamily.InterNetwork);
55 bufferRead = new byte;
56 this.tcpClient.BeginConnect(ChatInfo.IP, ChatInfo.PORT, new AsyncCallback(RequestCallback), null);
57
58 }
59
60 /// <summary>
61 /// 异步哀求链接函数
62 /// </summary>
63 /// <param name="ar"></param>
64 private void RequestCallback(IAsyncResult ar) {
65 this.tcpClient.EndConnect(ar);
66 this.lblStatus.Text = "连接服务器成功";
67 //获取流
68 stream = this.tcpClient.GetStream();
69 //先发送一个连接信息
70 string text = CommonVar.LOGIN;
71 byte[] buffer = PackHelper.GetSendMsgBytes(text,ChatInfo.Source,ChatInfo.Source);
72 stream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(WriteMessage), null);
73 //只有stream不为空的时候才可以读
74 stream.BeginRead(bufferRead, 0, bufferRead.Length, new AsyncCallback(ReadMessage), null);
75 }
76
77 /// <summary>
78 /// 发送信息
79 /// </summary>
80 /// <param name="sender"></param>
81 /// <param name="e"></param>
82 private void btnSend_Click(object sender, EventArgs e)
83 {
84 string text = this.txtMsg.Text.Trim();
85 if( string.IsNullOrEmpty(text)){
86 MessageBox.Show("要发送的信息为空");
87 return;
88 }
89 byte[] buffer = ChatInfo.GetSendMsgBytes(text);
90 stream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(WriteMessage), null);
91 this.rtAllMsg.AppendText(string.Format("\r\n[{0}]", dicNickInfo));
92 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Right;
93 this.rtAllMsg.AppendText(string.Format("\r\n{0}", text));
94 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Right;
95 }
96
97
98 /// <summary>
99 /// 异步读取信息
100 /// </summary>
101 /// <param name="ar"></param>
102 private void ReadMessage(IAsyncResult ar)
103 {
104 if (stream.CanRead)
105 {
106 int length = stream.EndRead(ar);
107 if (length >= 1)
108 {
109
110 string msg = string.Empty;
111 msg = string.Concat(msg, Encoding.UTF8.GetString(bufferRead, 0, length));
112 //处理接收的数据
113 string error = string.Empty;
114 ChatMessage t = PackHelper.ParsePack<ChatMessage>(msg, out error);
115 if (string.IsNullOrEmpty(error))
116 {
117 this.rtAllMsg.Invoke(new Action(() =>
118 {
119 this.rtAllMsg.AppendText(string.Format("\r\n[{0}]", dicNickInfo));
120 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Left;
121 this.rtAllMsg.AppendText(string.Format("\r\n{0}", t.info));
122 this.rtAllMsg.SelectionAlignment = HorizontalAlignment.Left;
123 this.lblStatus.Text = "接收数据成功!";
124 }));
125 }
126 else {
127 this.lblStatus.Text = "接收数据失败:"+error;
128 }
129 }
130 //继承读数据
131 stream.BeginRead(bufferRead, 0, bufferRead.Length, new AsyncCallback(ReadMessage), null);
132 }
133 }
134
135 /// <summary>
136 /// 发送成功
137 /// </summary>
138 /// <param name="ar"></param>
139 private void WriteMessage(IAsyncResult ar)
140 {
141 this.stream.EndWrite(ar);
142 //发送成功
143 }
144
145 /// <summary>
146 /// 页面关闭,断开连接
147 /// </summary>
148 /// <param name="sender"></param>
149 /// <param name="e"></param>
150 private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
151 {
152 if (MessageBox.Show("正在通话中,确定要关闭吗?", "关闭", MessageBoxButtons.YesNo) == DialogResult.Yes)
153 {
154 e.Cancel = false;
155 string text = CommonVar.QUIT;
156 byte[] buffer = ChatInfo.GetSendMsgBytes(text);
157 stream.Write(buffer, 0, buffer.Length);
158 //发送完成后,关闭连接
159 this.tcpClient.Close();
160
161 }
162 else {
163 e.Cancel = true;
164 }
165 }
166 }
167 }
View Code
<p>备注:本示例中,全部的创建连接,数据接收,发送等都是采取异步方式,防止页面卡顿。</p>
<h2>备注</h2>
<p>每一次的努力,都是幸运的伏笔。</p><br><br/><br/><br/><br/><br/>来源:<a href="https://www.cnblogs.com/hsiang/archive/2019/09/16/11530691.html" target="_blank">https://www.cnblogs.com/hsiang/archive/2019/09/16/11530691.html</a>
页:
[1]