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

来源:https://www.cnblogs.com/hsiang/archive/2019/09/16/11530691.html
-Advertisement-
Play Games

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


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

概述

在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 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

打包幫助類,如下所示:所有需要發送的信息,都要進行封裝,打包,編碼成固定格式,方便解析。

 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[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 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

客戶端類,如下所示:客戶端主要進行數據的封裝發送,接收解析等操作,併在頁面關閉時,關閉連接。

  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         /// 頁面關閉,

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 又是兩個月的時間過去了,上一次寫博客是7月14號,時間還是過的很快的,那麼問題來了,為什麼這麼長時間都沒有寫東西了呢?難道是在打醬油? 哈哈,說起來很慚愧,剛剛開始工作,碰到各種的問題要去學習要去解決,然後業餘的時間又去學了一些奇奇怪怪的東西,導致博客一直都落下了,歸根到底,還是自己懶惰了,因為心中 ...
  • 面試題 dubbo 的 spi 思想是什麼? 面試官心理分析 繼續深入問唄,前面一些基礎性的東西問完了,確定你應該都 ok,瞭解 dubbo 的一些基本東西,那麼問個稍微難一點點的問題,就是 spi,先問問你 spi 是啥?然後問問你 dubbo 的 spi 是怎麼實現的? 其實就是看看你對 dub ...
  • AOP簡介 今天來介紹一下AOP。AOP,中文常被翻譯為“面向切麵編程”,其作為OOP的擴展,其思想除了在Spring中得到了應用,也是不錯的設計方法。通常情況下,一個軟體系統,除了正常的業務邏輯代碼,往往還有一些功能性的代碼,比如:記錄日誌、數據校驗等等。最原始的辦法就是直接在你的業務邏輯代碼中編 ...
  • 前言 介面是Spring中一個非常重要的介面,它的介面定義如下 當你實現了這個介面的時候,Spring會保證在每一個bean對象初始化方法調用之前調用 方法,在初始化方法調用之後調用 的註冊 看過我之前寫的IOC源碼分析系列文章的同學應該對這個都比較有印象 ) Spring在執行到這的時候會把所有實 ...
  • 使用資源綁定器綁定屬性配置 實際開發中不建議把連接資料庫的信息寫死到Java程式中 //使用資源綁定器綁定屬性配置 ResourceBundle bundle = ResourceBundle.getBundle("jdbc"); String driver = bundle.getString(" ...
  • 1.每個service的impl都可以指定名稱(使用@Service(“名稱”)) 2.Controller中註入service的時候使用名稱來指定註入哪一個。 (1). (2). 代碼如下: ...
  • 上一篇使用了Eureka與Ribbon組件做了最簡單的的服務註冊與發現,我們知道Eureka是實現服務治理中心的組件,但是上一篇Eureka沒有實現集群,這樣沒有保證到Eureka Server的高可用。 理論上來講,因為服務消費者本地緩存了服務提供者的地址,即使Eureka Server宕機,也不 ...
  • 要求: 文本框居中,用戶不能修改運算結果 當用戶選擇不同的運算類型時 下方GroupBox的標題與所選運算類型相對應 且文本框數字立即清空 單擊【計算】按鈕時 如果文本框輸入的內容非法 結果文本框顯示問號 運行效果: XAML: 後臺代碼: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...