還記得剛剛開始接觸編程開發時,傻傻的將網站開發和網路編程混為一談,常常因分不清楚而引為笑柄,后來勉強分清楚,又因為各種各樣的協議埠之類的名詞而倍感神秘,所以為了揭開網路編程的神秘面紗,本文嘗試以一個簡單的小例子,簡述在網路編程開發中涉及到的相關知識點,僅供學習分享使用,如有不足之處,還請指正,
概述
在TCP/IP協議族中,傳輸層主要包括TCP和UDP兩種通信協議,它們以不同的方式實作兩臺主機中的不同應用程式之間的資料傳輸,即資料的端到端傳輸,由于它們的實作方式不同,因此各有一套屬于自己的埠號,且相互獨立,采用五元組(協議,信源機IP地址,信源應用行程埠,信宿機IP地址,信宿應用行程埠)來描述兩個應用行程之間的通信關聯,這也是進行網路程式設計最基本的概念,傳輸控制協議(Transmission Control Protocol,TCP)提供一種面向連接的、可靠的資料傳輸服務,保證了端到端資料傳輸的可靠性,
涉及知識點
本例中涉及知識點如下所示:
- TcpClient : TcpClient類為TCP網路服務提供客戶端連接,它構建于Socket類之上,以提供較高級別的TCP服務,提供了通過網路連接、發送和接收資料的簡單方法,
- TcpListener:構建于Socket之上,提供了更高抽象級別的TCP服務,使得程式員能更方便地撰寫服務器端應用程式,通常情況下,服務器端應用程式在啟動時將首先系結本地網路介面的IP地址和埠號,然后進入偵聽客戶請求的狀態,以便于客戶端應用程式提出顯式請求,
- 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

