主頁 > .NET開發 > C# 網路編程之簡易聊天示例

C# 網路編程之簡易聊天示例

2020-09-11 17:33:51 .NET開發

還記得剛剛開始接觸編程開發時,傻傻的將網站開發和網路編程混為一談,常常因分不清楚而引為笑柄,后來勉強分清楚,又因為各種各樣的協議埠之類的名詞而倍感神秘,所以為了揭開網路編程的神秘面紗,本文嘗試以一個簡單的小例子,簡述在網路編程開發中涉及到的相關知識點,僅供學習分享使用,如有不足之處,還請指正,

概述

在TCP/IP協議族中,傳輸層主要包括TCP和UDP兩種通信協議,它們以不同的方式實作兩臺主機中的不同應用程式之間的資料傳輸,即資料的端到端傳輸,由于它們的實作方式不同,因此各有一套屬于自己的埠號,且相互獨立,采用五元組(協議,信源機IP地址,信源應用行程埠,信宿機IP地址,信宿應用行程埠)來描述兩個應用行程之間的通信關聯,這也是進行網路程式設計最基本的概念,傳輸控制協議(Transmission Control Protocol,TCP)提供一種面向連接的、可靠的資料傳輸服務,保證了端到端資料傳輸的可靠性,

涉及知識點

本例中涉及知識點如下所示:

  1. TcpClient : TcpClient類為TCP網路服務提供客戶端連接,它構建于Socket類之上,以提供較高級別的TCP服務,提供了通過網路連接、發送和接收資料的簡單方法,
  2. TcpListener:構建于Socket之上,提供了更高抽象級別的TCP服務,使得程式員能更方便地撰寫服務器端應用程式,通常情況下,服務器端應用程式在啟動時將首先系結本地網路介面的IP地址和埠號,然后進入偵聽客戶請求的狀態,以便于客戶端應用程式提出顯式請求,
  3. NetworkStream:提供網路訪問的基礎資料流,一旦偵聽到有客戶端應用程式請求連接偵聽埠,服務器端應用將接受請求,并建立一個負責與客戶端應用程式通信的信道,

網路聊天示意圖

如下圖所示:看似兩個在不同網路上的人聊天,實際上都是通過服務端進行接收轉發的,

TCP網路通信示意圖

如下圖所示:首先是服務端進行監聽,當有客戶端進行連接時,則建立通訊通道進行通信,

示例截圖

服務端截圖,如下所示:

客戶端截圖,如下所示:開啟兩個客戶端,開始美猴王和二師兄的對話,

核心代碼

發送資訊類,如下所示:

 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 ChatMessage13     {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 ChatHeader35     {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 ChatMark57     {58         BEGIN  = 0x0000,59         END = 0xFFFF60     }61 62     public enum ChatType {63         TEXT=0,64         IMAGE=165     }66 }
View Code

打包幫助類,如下所示:所有需要發送的資訊,都要進行封裝,打包,編碼成固定格式,方便決議,

 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 PackHelper13     {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 = text32             };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[1];67                 string begin = array[0];68                 string end = array[2];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

服務端類,如下所示:服務端開啟時,需要進行埠監聽,等待鏈接,

 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 MeChatServer17 {18     public class Program19     {20         /// <summary>21         /// 服務端IP22         /// </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

客戶端類,如下所示:客戶端主要進行資料的封裝發送,接收決議等操作,并在頁面關閉時,關閉連接,

  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[ChatInfo.Source], dicNickInfo[ChatInfo.Dest]); 52             this.Text = string.Format("{0}:{1}", this.Text, title); 53             //初始化客戶端連接 54             this.tcpClient = new TcpClient(AddressFamily.InterNetwork); 55             bufferRead = new byte[this.tcpClient.ReceiveBufferSize]; 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[ChatInfo.Source])); 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[t.header.source]));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

備注:本示例中,所有的建立連接,資料接收,發送等都是采用異步方式,防止頁面卡頓,

原始碼下載鏈接

備注

每一次的努力,都是幸運的伏筆,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/6441.html

標籤:WinForm

上一篇:.NET Core 依賴注入框架 框圖示記

下一篇:Dev控制元件使用CheckedListBoxControl獲取items.count為0 的解決方法

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more