C#網絡編程二:Socket編程

一:什麼是SOCKET程序員

socket的英文原義是「孔」或「插座」。做爲進程通訊機制,取後一種意思。一般也稱做「套接字」,用於描述IP地址和端口,是一個通訊鏈的句柄(其實就是兩個程序通訊用的)。
socket很是相似於電話插座。以一個電話網爲例:電話的通話雙方至關於相互通訊的2個程序,電話號碼就是ip地址。任何用戶在通話以前,首先要佔有一部電話機,至關於申請一個socket;同時要知道對方的號碼,至關於對方有一個固定的socket。而後向對方撥號呼叫,至關於發出鏈接請求。對方假如在場並空閒,拿起電話話筒,雙方就能夠正式通話,至關於鏈接成功。雙方通話的過程,是一方向電話機發出信號和對方從電話機接收信號的過程,至關於向socket發送數據和從socket接收數據。通話結束後,一方掛起電話機至關於關閉socket,撤銷鏈接。編程

一、套接字分類數組

爲了知足不一樣程序對通訊質量和性能的要求,通常的網絡系統都提供瞭如下3種不一樣類型的套接字,以供用戶在設計程序時根據不一樣須要來選擇:安全

流式套接字(SOCK_STREAM):提供了一種可靠的、面向鏈接的雙向數據傳輸服務。實現了數據無差錯,無重複的發送,內設流量控制,被傳輸的數據被看作無記錄邊界的字節流。在TCP/IP協議簇中,使用TCP實現字節流的傳輸,當用戶要發送大批量數據,或對數據傳輸的可靠性有較高要求時使用流式套接字。服務器

數據報套接字(SOCK_DGRAM):提供了一種無鏈接、不可靠的雙向數據傳輸服務。數據以獨立的包形式被髮送,而且保留了記錄邊界,不提供可靠性保證。數據在傳輸過程當中可能會丟失或重複,而且不能保證在接收端數據按發送順序接收。在TCP/IP協議簇中,使用UDP實現數據報套接字。網絡

原始套接字(SOCK_RAW):該套接字容許對較低層協議(如IP或ICMP)進行直接訪問。通常用於對TCP/IP核心協議的網絡編程。異步

二:SOCKET相關概念socket

一、端口性能

在Internet上有不少這樣的主機,這些主機通常運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,並綁定到一個端口上,不一樣的端口對應於不一樣的服務(應用程序),所以,在網絡協議中使用端口號識別主機上不一樣的進程。
例如:http使用80端口,FTP使用21端口。
this

二、協議

2.1 TCP:

TCP是一種面向鏈接的、可靠的,基於字節流的傳輸層通訊協議。爲兩臺主機提供高可靠性的數據通訊服務。它能夠將源主機的數據無差錯地傳輸到目標主機。當有數據要發送時,對應用進程送來的數據進行分片,以適合於在網絡層中傳輸;當接收到網絡層傳來的分組時,它要對收到的分組進行確認,還要對丟失的分組設置超時重發等。爲此TCP須要增長額外的許多開銷,以便在數據傳輸過程當中進行一些必要的控制,確保數據的可靠傳輸。所以,TCP傳輸的效率比較低。

2.1.1 TCP的工做過程

TCP是面向鏈接的協議,TCP協議經過三個報文段完成相似電話呼叫的鏈接創建過程,這個過程稱爲三次握手,如圖所示:

第一次握手:創建鏈接時,客戶端發送SYN包(SEQ=x)到服務器,並進入SYN_SEND狀態,等待服務器確認。

第二次握手:服務器收到SYN包,必須確認客戶的SYN(ACK=x+1),同時本身也發送一個SYN包(SEQ=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態。

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ACK=y+1),此包發送完畢,客戶端和服務器進入Established狀態,完成三次握手。

2.1.2 傳輸數據

一旦通訊雙方創建了TCP鏈接,鏈接中的任何一方都能向對方發送數據和接收對方發來的數據。TCP協議負責把用戶數據(字節流)按必定的格式和長度組成多個數據報進行發送,並在接收到數據報以後按分解順序從新組裝和恢復用戶數據。
利用TCP傳輸數據時,數據是以字節流的形式進行傳輸的。

2.1.3 鏈接的終止

創建一個鏈接須要三次握手,而終止一個鏈接要通過四次握手,這是由TCP的半關閉(half-close)形成的。具體過程如圖所示:

2.1.4 TCP的主要特色

TCP最主要的特色以下。
(1) 是面向鏈接的協議。
(2) 端到端的通訊。每一個TCP鏈接只能有兩個端點,並且只能一對一通訊,不能一點對多點直接通訊。
(3) 高可靠性。經過TCP鏈接傳送的數據,能保證數據無差錯、不丟失、不重複地準確到達接收方,而且保證各數據到達的順序與其發出的順序相同。
(4) 全雙工方式傳輸。
(5) 數據以字節流的方式傳輸。
(6) 傳輸的數據無消息邊界。

2.1.5 同步與異步

同步工做方式是指利用TCP編寫的程序執行到監聽或接收語句時,在未完成工做(偵聽到鏈接請求或收到對方發來的數據)前再也不繼續往下執行,線程處於阻塞狀態,直到該語句完成相應的工做後才繼續執行下一條語句。
異步工做方式是指程序執行到監聽或接收語句時,不論工做是否完成,都會繼續往下執行。

 

2.2 UDP

UDP是一種簡單的、面向數據報的無鏈接的協議,提供的是不必定可靠的傳輸服務。所謂「無鏈接」是指在正式通訊前沒必要與對方先創建鏈接,無論對方狀態如何都直接發送過去。這與發手機短信很是類似,只要知道對方的手機號就能夠了,不要考慮對方手機處於什麼狀態。UDP雖然不能保證數據傳輸的可靠性,但數據傳輸的效率較高。

2.1.1 UDP與TCP的區別
(1) UDP可靠性不如TCP
TCP包含了專門的傳遞保證機制,當數據接收方收到發送方傳來的信息時,會自動向發送方發出確認消息;發送方只有在接收到該確認消息以後才繼續傳送其餘信息,不然將一直等待直到收到確認信息爲止。與TCP不一樣,UDP並不提供數據傳送的保證機制。若是在從發送方到接收方的傳遞過程當中出現數據報的丟失,協議自己並不能作出任何檢測或提示。所以,一般人們把UDP稱爲不可靠的傳輸協議。
(2) UDP不能保證有序傳輸
UDP不能確保數據的發送和接收順序。對於突發性的數據報,有可能會亂序。

2.1.2 UDP的優點

(1) UDP速度比TCP快
因爲UDP不須要先與對方創建鏈接,也不須要傳輸確認,所以其數據傳輸速度比TCP快得多。對於強調傳輸性能而不是傳輸完整性的應用(好比網絡音頻播放、視頻點播和網絡會議等),使用UDP比較合適,由於它的傳輸速度快,使經過網絡播放的視頻音質好、畫面清晰。
(2) UDP有消息邊界
發送方UDP對應用程序交下來的報文,在添加首部後就向下直接交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界。使用UDP不須要考慮消息邊界問題,這樣使得UDP編程相比TCP,在對接收到的數據的處理方面要方便的多。在程序員看來,UDP套接字使用比TCP簡單。UDP的這一特徵也說明了它是一種面向報文的傳輸協議。
(3) UDP能夠一對多傳輸
因爲傳輸數據不創建鏈接,也就不須要維護鏈接狀態(包括收發狀態等),所以一臺服務器能夠同時向多個客戶端傳輸相同的消息。利用UDP可使用廣播或組播的方式同時向子網上的全部客戶進程發送消息,這一點也比TCP方便。
其中,速度快是UDP的首要優點
因爲TCP協議中植入了各類安全保障功能,在實際執行的過程當中會佔用大量的系統開銷,無疑使速度受到嚴重影響。反觀UDP,因爲拋棄了信息可靠傳輸機制,將安全和排序等功能移交給上層應用完成,極大地下降了執行時間,使速度獲得了保證。簡而言之,UDP的「理念」就是「不顧一切,只爲更快地發送數據」。

三:socket通常應用模式:

 

 

四:SOCKET通訊基本流程圖:

根據socket通訊基本流程圖,總結通訊的基本步驟:

服務器端:

第一步:建立一個用於監聽鏈接的Socket對像;

第二步:用指定的端口號和服務器的ip創建一個EndPoint對像;

第三步:用socket對像的Bind()方法綁定EndPoint;

第四步:用socket對像的Listen()方法開始監聽;

第五步:接收到客戶端的鏈接,用socket對像的Accept()方法建立一個新的用於和客戶端進行通訊的socket對像;

第六步:通訊結束後必定記得關閉socket;

客戶端:

第一步:創建一個Socket對像;

第二步:用指定的端口號和服務器的ip創建一個EndPoint對像;

第三步:用socket對像的Connect()方法以上面創建的EndPoint對像作爲參數,向服務器發出鏈接請求;

第四步:若是鏈接成功,就用socket對像的Send()方法向服務器發送信息;

第五步:用socket對像的Receive()方法接受服務器發來的信息 ;

第六步:通訊結束後必定記得關閉socket;

五:示例程序

服務端界面:

代碼實現以下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Net;
  8 using System.Net.Sockets;
  9 using System.Text;
 10 using System.Threading.Tasks;
 11 using System.Windows.Forms;
 12 using System.Threading;
 13 using System.IO;
 14 
 15 namespace SocketServer
 16 {
 17     public partial class FrmServer : Form
 18     {
 19         public FrmServer()
 20         {
 21             InitializeComponent();
 22         }
 23 
 24         //定義回調:解決跨線程訪問問題
 25         private delegate void SetTextValueCallBack(string strValue);
 26         //定義接收客戶端發送消息的回調
 27         private delegate void ReceiveMsgCallBack(string strReceive);
 28         //聲明回調
 29         private SetTextValueCallBack setCallBack;
 30         //聲明
 31         private ReceiveMsgCallBack receiveCallBack;
 32         //定義回調:給ComboBox控件添加元素
 33         private delegate void SetCmbCallBack(string strItem);
 34         //聲明
 35         private SetCmbCallBack setCmbCallBack;
 36         //定義發送文件的回調
 37         private delegate void SendFileCallBack(byte[] bf);
 38         //聲明
 39         private SendFileCallBack sendCallBack;
 40 
 41         //用於通訊的Socket
 42         Socket socketSend;
 43         //用於監聽的SOCKET
 44         Socket socketWatch;
 45 
 46         //將遠程鏈接的客戶端的IP地址和Socket存入集合中
 47         Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
 48 
 49         //建立監聽鏈接的線程
 50         Thread AcceptSocketThread;
 51         //接收客戶端發送消息的線程
 52         Thread threadReceive;
 53 
 54         /// <summary>
 55         /// 開始監聽
 56         /// </summary>
 57         /// <param name="sender"></param>
 58         /// <param name="e"></param>
 59         private void btn_Start_Click(object sender, EventArgs e)
 60         {
 61             //當點擊開始監聽的時候 在服務器端建立一個負責監聽IP地址和端口號的Socket
 62             socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 63             //獲取ip地址
 64             IPAddress ip=IPAddress.Parse(this.txt_IP.Text.Trim());
 65             //建立端口號
 66             IPEndPoint point=new IPEndPoint(ip,Convert.ToInt32(this.txt_Port.Text.Trim()));
 67             //綁定IP地址和端口號
 68             socketWatch.Bind(point);
 69             this.txt_Log.AppendText("監聽成功"+" \r \n");
 70             //開始監聽:設置最大能夠同時鏈接多少個請求
 71             socketWatch.Listen(10);
 72 
 73             //實例化回調
 74             setCallBack = new SetTextValueCallBack(SetTextValue);
 75             receiveCallBack = new ReceiveMsgCallBack(ReceiveMsg);
 76             setCmbCallBack = new SetCmbCallBack(AddCmbItem);
 77             sendCallBack = new SendFileCallBack(SendFile);
 78 
 79             //建立線程
 80             AcceptSocketThread = new Thread(new ParameterizedThreadStart(StartListen));
 81             AcceptSocketThread.IsBackground = true;
 82             AcceptSocketThread.Start(socketWatch);
 83         }
 84 
 85         /// <summary>
 86         /// 等待客戶端的鏈接,而且建立與之通訊用的Socket
 87         /// </summary>
 88         /// <param name="obj"></param>
 89         private void StartListen(object obj)
 90         {
 91             Socket socketWatch = obj as Socket;
 92             while (true)
 93             {               
 94                 //等待客戶端的鏈接,而且建立一個用於通訊的Socket
 95                 socketSend = socketWatch.Accept();
 96                 //獲取遠程主機的ip地址和端口號
 97                 string strIp=socketSend.RemoteEndPoint.ToString();
 98                 dicSocket.Add(strIp, socketSend);
 99                 this.cmb_Socket.Invoke(setCmbCallBack, strIp);
100                 string strMsg = "遠程主機:" + socketSend.RemoteEndPoint + "鏈接成功";
101                 //使用回調
102                 txt_Log.Invoke(setCallBack, strMsg);
103 
104                 //定義接收客戶端消息的線程
105                 Thread threadReceive = new Thread(new ParameterizedThreadStart(Receive));
106                 threadReceive.IsBackground = true;
107                 threadReceive.Start(socketSend);
108 
109             }
110         }
111 
112        
113 
114         /// <summary>
115         /// 服務器端不停的接收客戶端發送的消息
116         /// </summary>
117         /// <param name="obj"></param>
118         private void Receive(object obj)
119         {
120             Socket socketSend = obj as Socket;
121             while (true)
122             {
123                 //客戶端鏈接成功後,服務器接收客戶端發送的消息
124                 byte[] buffer = new byte[2048];
125                 //實際接收到的有效字節數
126                 int count = socketSend.Receive(buffer);
127                 if (count == 0)//count 表示客戶端關閉,要退出循環
128                 {
129                     break;
130                 }
131                 else
132                 {
133                     string str = Encoding.Default.GetString(buffer, 0, count);
134                     string strReceiveMsg = "接收:" + socketSend.RemoteEndPoint + "發送的消息:" + str;
135                     txt_Log.Invoke(receiveCallBack, strReceiveMsg);
136                 }
137             }
138         }
139 
140         /// <summary>
141         /// 回調委託須要執行的方法
142         /// </summary>
143         /// <param name="strValue"></param>
144         private void SetTextValue(string strValue)
145         {
146             this.txt_Log.AppendText(strValue + " \r \n");
147         }
148 
149 
150         private void ReceiveMsg(string strMsg)
151         {
152             this.txt_Log.AppendText(strMsg + " \r \n");
153         }
154 
155         private void AddCmbItem(string strItem)
156         {
157             this.cmb_Socket.Items.Add(strItem);
158         }
159 
160         /// <summary>
161         /// 服務器給客戶端發送消息
162         /// </summary>
163         /// <param name="sender"></param>
164         /// <param name="e"></param>
165         private void btn_Send_Click(object sender, EventArgs e)
166         {
167             try
168             {
169                 string strMsg = this.txt_Msg.Text.Trim();
170                 byte[] buffer = Encoding.Default.GetBytes(strMsg);
171                 List<byte> list = new List<byte>();
172                 list.Add(0);
173                 list.AddRange(buffer);
174                 //將泛型集合轉換爲數組
175                 byte[] newBuffer = list.ToArray();
176                 //得到用戶選擇的IP地址
177                 string ip = this.cmb_Socket.SelectedItem.ToString();
178                 dicSocket[ip].Send(newBuffer);
179             }
180             catch (Exception ex)
181             {
182                 MessageBox.Show("給客戶端發送消息出錯:"+ex.Message);
183             }
184             //socketSend.Send(buffer);
185         }
186 
187         /// <summary>
188         /// 選擇要發送的文件
189         /// </summary>
190         /// <param name="sender"></param>
191         /// <param name="e"></param>
192         private void btn_Select_Click(object sender, EventArgs e)
193         {
194             OpenFileDialog dia = new OpenFileDialog();
195             //設置初始目錄
196             dia.InitialDirectory = @"";
197             dia.Title = "請選擇要發送的文件";
198             //過濾文件類型
199             dia.Filter = "全部文件|*.*";
200             dia.ShowDialog();
201             //將選擇的文件的全路徑賦值給文本框
202             this.txt_FilePath.Text = dia.FileName;
203         }
204 
205         /// <summary>
206         /// 發送文件
207         /// </summary>
208         /// <param name="sender"></param>
209         /// <param name="e"></param>
210         private void btn_SendFile_Click(object sender, EventArgs e)
211         {
212             List<byte> list = new List<byte>();
213             //獲取要發送的文件的路徑
214             string strPath = this.txt_FilePath.Text.Trim();
215             using (FileStream sw = new FileStream(strPath,FileMode.Open,FileAccess.Read))
216             {
217                 byte[] buffer = new byte[2048];
218                 int r = sw.Read(buffer, 0, buffer.Length);
219                 list.Add(1);
220                 list.AddRange(buffer);
221 
222                 byte[] newBuffer = list.ToArray();
223                 //發送
224                 //dicSocket[cmb_Socket.SelectedItem.ToString()].Send(newBuffer, 0, r+1, SocketFlags.None);
225                 btn_SendFile.Invoke(sendCallBack, newBuffer);
226 
227                 
228             }
229             
230         }
231 
232         private void SendFile(byte[] sendBuffer)
233         {
234 
235             try
236             {
237                 dicSocket[cmb_Socket.SelectedItem.ToString()].Send(sendBuffer, SocketFlags.None);
238             }
239             catch (Exception ex)
240             {
241                 MessageBox.Show("發送文件出錯:"+ex.Message);
242             }
243         }
244 
245         private void btn_Shock_Click(object sender, EventArgs e)
246         {
247             byte[] buffer = new byte[1] { 2};
248             dicSocket[cmb_Socket.SelectedItem.ToString()].Send(buffer);
249         }
250 
251         /// <summary>
252         /// 中止監聽
253         /// </summary>
254         /// <param name="sender"></param>
255         /// <param name="e"></param>
256         private void btn_StopListen_Click(object sender, EventArgs e)
257         {
258             socketWatch.Close();
259             socketSend.Close();
260             //終止線程
261             AcceptSocketThread.Abort();
262             threadReceive.Abort();
263         }
264     }
265 }

客戶端界面

代碼實現以下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Threading.Tasks;
  9 using System.Windows.Forms;
 10 using System.Net.Sockets;
 11 using System.Net;
 12 using System.Threading;
 13 using System.IO;
 14 
 15 namespace SocketClient
 16 {
 17     public partial class FrmClient : Form
 18     {
 19         public FrmClient()
 20         {
 21             InitializeComponent();
 22         }
 23 
 24         //定義回調
 25         private delegate void SetTextCallBack(string strValue);
 26         //聲明
 27         private SetTextCallBack setCallBack;
 28 
 29         //定義接收服務端發送消息的回調
 30         private delegate void ReceiveMsgCallBack(string strMsg);
 31         //聲明
 32         private ReceiveMsgCallBack receiveCallBack;
 33 
 34         //建立鏈接的Socket
 35         Socket socketSend;
 36         //建立接收客戶端發送消息的線程
 37         Thread threadReceive;
 38 
 39         /// <summary>
 40         /// 鏈接
 41         /// </summary>
 42         /// <param name="sender"></param>
 43         /// <param name="e"></param>
 44         private void btn_Connect_Click(object sender, EventArgs e)
 45         {
 46             try
 47             {
 48                 socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 49                 IPAddress ip = IPAddress.Parse(this.txt_IP.Text.Trim());
 50                 socketSend.Connect(ip, Convert.ToInt32(this.txt_Port.Text.Trim()));
 51                 //實例化回調
 52                 setCallBack = new SetTextCallBack(SetValue);
 53                 receiveCallBack = new ReceiveMsgCallBack(SetValue);
 54                 this.txt_Log.Invoke(setCallBack, "鏈接成功");
 55 
 56                 //開啓一個新的線程不停的接收服務器發送消息的線程
 57                 threadReceive = new Thread(new ThreadStart(Receive));
 58                 //設置爲後臺線程
 59                 threadReceive.IsBackground = true;
 60                 threadReceive.Start();
 61             }
 62             catch (Exception ex)
 63             {
 64                 MessageBox.Show("鏈接服務端出錯:" + ex.ToString());
 65             }
 66         }
 67 
 68         /// <summary>
 69         /// 接口服務器發送的消息
 70         /// </summary>
 71         private void Receive()
 72         {
 73             try
 74             {
 75                 while (true)
 76                 {
 77                     byte[] buffer = new byte[2048];
 78                     //實際接收到的字節數
 79                     int r = socketSend.Receive(buffer);
 80                     if (r == 0)
 81                     {
 82                         break;
 83                     }
 84                     else
 85                     {
 86                         //判斷髮送的數據的類型
 87                         if (buffer[0] == 0)//表示發送的是文字消息
 88                         {
 89                             string str = Encoding.Default.GetString(buffer, 1, r - 1);
 90                             this.txt_Log.Invoke(receiveCallBack, "接收遠程服務器:" + socketSend.RemoteEndPoint + "發送的消息:" + str);
 91                         }
 92                         //表示發送的是文件
 93                         if (buffer[0] == 1)
 94                         {
 95                             SaveFileDialog sfd = new SaveFileDialog();
 96                             sfd.InitialDirectory = @"";
 97                             sfd.Title = "請選擇要保存的文件";
 98                             sfd.Filter = "全部文件|*.*";
 99                             sfd.ShowDialog(this);
100 
101                             string strPath = sfd.FileName;
102                             using (FileStream fsWrite = new FileStream(strPath, FileMode.OpenOrCreate, FileAccess.Write))
103                             {
104                                 fsWrite.Write(buffer, 1, r - 1);
105                             }
106 
107                             MessageBox.Show("保存文件成功");
108                         }
109                     }
110 
111 
112                 }
113             }
114             catch (Exception ex)
115             {
116                 MessageBox.Show("接收服務端發送的消息出錯:" + ex.ToString());
117             }
118         }
119 
120 
121         private void SetValue(string strValue)
122         {
123             this.txt_Log.AppendText(strValue + "\r \n");
124         }
125 
126         /// <summary>
127         /// 客戶端給服務器發送消息
128         /// </summary>
129         /// <param name="sender"></param>
130         /// <param name="e"></param>
131         private void btn_Send_Click(object sender, EventArgs e)
132         {
133             try
134             {
135                 string strMsg = this.txt_Msg.Text.Trim();
136                 byte[] buffer = new byte[2048];
137                 buffer = Encoding.Default.GetBytes(strMsg);
138                 int receive = socketSend.Send(buffer);
139             }
140             catch (Exception ex)
141             {
142                 MessageBox.Show("發送消息出錯:" + ex.Message);
143             }
144         }
145 
146         private void FrmClient_Load(object sender, EventArgs e)
147         {
148             Control.CheckForIllegalCrossThreadCalls = false;
149         }
150 
151         /// <summary>
152         /// 斷開鏈接
153         /// </summary>
154         /// <param name="sender"></param>
155         /// <param name="e"></param>
156         private void btn_CloseConnect_Click(object sender, EventArgs e)
157         {
158             //關閉socket
159             socketSend.Close();
160             //終止線程
161             threadReceive.Abort();
162         }
163     }
164 }
相關文章
相關標籤/搜索