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