<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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核心協定的網路程式設計。
在Internet上有很多這樣的主機,這些主機一般執行了多個服務軟體,同時提供幾種服務。每種服務都開啟一個Socket,並繫結到一個埠上,不同的埠對應於不同的服務(應用程式),因此,在網路協定中使用埠號識別主機上不同的程序。
例如:http使用80埠,FTP使用21埠。
TCP是一種面向連線的、可靠的,基於位元組流的傳輸層通訊協定。為兩臺主機提供高可靠性的資料通訊服務。它可以將源主機的資料無差錯地傳輸到目標主機。當有資料要傳送時,對應用程序送來的資料進行分片,以適合於在網路層中傳輸;當接收到網路層傳來的分組時,它要對收到的分組進行確認,還要對丟失的分組設定超時重發等。為此TCP需要增加額外的許多開銷,以便在資料傳輸過程中進行一些必要的控制,確保資料的可靠傳輸。因此,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狀態,完成三次握手。
一旦通訊雙方建立了TCP連線,連線中的任何一方都能向對方傳送資料和接收對方發來的資料。TCP協定負責把使用者資料(位元組流)按一定的格式和長度組成多個資料包進行傳送,並在接收到資料包之後按分解順序重新組裝和恢復使用者資料。
利用TCP傳輸資料時,資料是以位元組流的形式進行傳輸的。
建立一個連線需要三次握手,而終止一個連線要經過四次握手,這是由TCP的半關閉(half-close)造成的。具體過程如圖所示:
TCP最主要的特點如下。
(1) 是面向連線的協定。
(2) 端到端的通訊。每個TCP連線只能有兩個端點,而且只能一對一通訊,不能一點對多點直接通訊。
(3) 高可靠性。通過TCP連線傳送的資料,能保證資料無差錯、不丟失、不重複地準確到達接收方,並且保證各資料到達的順序與其發出的順序相同。
(4) 全雙工方式傳輸。
(5) 資料以位元組流的方式傳輸。
(6) 傳輸的資料無訊息邊界。
同步工作方式是指利用TCP編寫的程式執行到監聽或接收語句時,在未完成工作(偵聽到連線請求或收到對方發來的資料)前不再繼續往下執行,執行緒處於阻塞狀態,直到該語句完成相應的工作後才繼續執行下一條語句。
非同步工作方式是指程式執行到監聽或接收語句時,不論工作是否完成,都會繼續往下執行。
UDP是一種簡單的、面向資料包的無連線的協定,提供的是不一定可靠的傳輸服務。所謂“無連線”是指在正式通訊前不必與對方先建立連線,不管對方狀態如何都直接傳送過去。這與發手機簡訊非常相似,只要知道對方的手機號就可以了,不要考慮對方手機處於什麼狀態。UDP雖然不能保證資料傳輸的可靠性,但資料傳輸的效率較高。
(1) UDP可靠性不如TCP
TCP包含了專門的傳遞保證機制,當資料接收方收到傳送方傳來的資訊時,會自動向傳送方發出確認訊息;傳送方只有在接收到該確認訊息之後才繼續傳送其他資訊,否則將一直等待直到收到確認資訊為止。與TCP不同,UDP並不提供資料傳送的保證機制。如果在從傳送方到接收方的傳遞過程中出現資料包的丟失,協定本身並不能做出任何檢測或提示。因此,通常人們把UDP稱為不可靠的傳輸協定。
(2) UDP不能保證有序傳輸
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對像;
第二步:用指定的埠號和伺服器的ip建立一個EndPoint對像;
第三步:用socket對像的Bind()方法系結EndPoint;
第四步:用socket對像的Listen()方法開始監聽;
第五步:接收到使用者端的連線,用socket對像的Accept()方法建立一個新的用於和使用者端進行通訊的socket對像;
第六步:通訊結束後一定記得關閉socket;
第一步:建立一個Socket對像;
第二步:用指定的埠號和伺服器的ip建立一個EndPoint對像;
第三步:用socket對像的Connect()方法以上面建立的EndPoint對像做為引數,向伺服器發出連線請求;
第四步:如果連線成功,就用socket對像的Send()方法向伺服器傳送資訊;
第五步:用socket對像的Receive()方法接受伺服器發來的資訊 ;
第六步:通訊結束後一定記得關閉socket;
伺服器端介面:
程式碼實現如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Threading; using System.IO; namespace SocketServer { public partial class FrmServer : Form { public FrmServer() { InitializeComponent(); } //定義回撥:解決跨執行緒存取問題 private delegate void SetTextValueCallBack(string strValue); //定義接收使用者端傳送訊息的回撥 private delegate void ReceiveMsgCallBack(string strReceive); //宣告回撥 private SetTextValueCallBack setCallBack; //宣告 private ReceiveMsgCallBack receiveCallBack; //定義回撥:給ComboBox控制元件新增元素 private delegate void SetCmbCallBack(string strItem); //宣告 private SetCmbCallBack setCmbCallBack; //定義傳送檔案的回撥 private delegate void SendFileCallBack(byte[] bf); //宣告 private SendFileCallBack sendCallBack; //用於通訊的Socket Socket socketSend; //用於監聽的SOCKET Socket socketWatch; //將遠端連線的使用者端的IP地址和Socket存入集合中 Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); //建立監聽連線的執行緒 Thread AcceptSocketThread; //接收使用者端傳送訊息的執行緒 Thread threadReceive; /// <summary> /// 開始監聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Start_Click(object sender, EventArgs e) { //當點選開始監聽的時候 在伺服器端建立一個負責監聽IP地址和埠號的Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //獲取ip地址 IPAddress ip=IPAddress.Parse(this.txt_IP.Text.Trim()); //建立埠號 IPEndPoint point=new IPEndPoint(ip,Convert.ToInt32(this.txt_Port.Text.Trim())); //繫結IP地址和埠號 socketWatch.Bind(point); this.txt_Log.AppendText("監聽成功"+" r n"); //開始監聽:設定最大可以同時連線多少個請求 socketWatch.Listen(10); //範例化回撥 setCallBack = new SetTextValueCallBack(SetTextValue); receiveCallBack = new ReceiveMsgCallBack(ReceiveMsg); setCmbCallBack = new SetCmbCallBack(AddCmbItem); sendCallBack = new SendFileCallBack(SendFile); //建立執行緒 AcceptSocketThread = new Thread(new ParameterizedThreadStart(StartListen)); AcceptSocketThread.IsBackground = true; AcceptSocketThread.Start(socketWatch); } /// <summary> /// 等待使用者端的連線,並且建立與之通訊用的Socket /// </summary> /// <param name="obj"></param> private void StartListen(object obj) { Socket socketWatch = obj as Socket; while (true) { //等待使用者端的連線,並且建立一個用於通訊的Socket socketSend = socketWatch.Accept(); //獲取遠端主機的ip地址和埠號 string strIp=socketSend.RemoteEndPoint.ToString(); dicSocket.Add(strIp, socketSend); this.cmb_Socket.Invoke(setCmbCallBack, strIp); string strMsg = "遠端主機:" + socketSend.RemoteEndPoint + "連線成功"; //使用回撥 txt_Log.Invoke(setCallBack, strMsg); //定義接收使用者端訊息的執行緒 Thread threadReceive = new Thread(new ParameterizedThreadStart(Receive)); threadReceive.IsBackground = true; threadReceive.Start(socketSend); } } /// <summary> /// 伺服器端不停的接收使用者端傳送的訊息 /// </summary> /// <param name="obj"></param> private void Receive(object obj) { Socket socketSend = obj as Socket; while (true) { //使用者端連線成功後,伺服器接收使用者端傳送的訊息 byte[] buffer = new byte[2048]; //實際接收到的有效位元組數 int count = socketSend.Receive(buffer); if (count == 0)//count 表示使用者端關閉,要退出迴圈 { break; } else { string str = Encoding.Default.GetString(buffer, 0, count); string strReceiveMsg = "接收:" + socketSend.RemoteEndPoint + "傳送的訊息:" + str; txt_Log.Invoke(receiveCallBack, strReceiveMsg); } } } /// <summary> /// 回撥委託需要執行的方法 /// </summary> /// <param name="strValue"></param> private void SetTextValue(string strValue) { this.txt_Log.AppendText(strValue + " r n"); } private void ReceiveMsg(string strMsg) { this.txt_Log.AppendText(strMsg + " r n"); } private void AddCmbItem(string strItem) { this.cmb_Socket.Items.Add(strItem); } /// <summary> /// 伺服器給使用者端傳送訊息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Send_Click(object sender, EventArgs e) { try { string strMsg = this.txt_Msg.Text.Trim(); byte[] buffer = Encoding.Default.GetBytes(strMsg); List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //將泛型集合轉換為陣列 byte[] newBuffer = list.ToArray(); //獲得使用者選擇的IP地址 string ip = this.cmb_Socket.SelectedItem.ToString(); dicSocket[ip].Send(newBuffer); } catch (Exception ex) { MessageBox.Show("給使用者端傳送訊息出錯:"+ex.Message); } //socketSend.Send(buffer); } /// <summary> /// 選擇要傳送的檔案 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Select_Click(object sender, EventArgs e) { OpenFileDialog dia = new OpenFileDialog(); //設定初始目錄 dia.InitialDirectory = @""; dia.Title = "請選擇要傳送的檔案"; //過濾檔案型別 dia.Filter = "所有檔案|*.*"; dia.ShowDialog(); //將選擇的檔案的全路徑賦值給文字方塊 this.txt_FilePath.Text = dia.FileName; } /// <summary> /// 傳送檔案 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_SendFile_Click(object sender, EventArgs e) { List<byte> list = new List<byte>(); //獲取要傳送的檔案的路徑 string strPath = this.txt_FilePath.Text.Trim(); using (FileStream sw = new FileStream(strPath,FileMode.Open,FileAccess.Read)) { byte[] buffer = new byte[2048]; int r = sw.Read(buffer, 0, buffer.Length); list.Add(1); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); //傳送 //dicSocket[cmb_Socket.SelectedItem.ToString()].Send(newBuffer, 0, r+1, SocketFlags.None); btn_SendFile.Invoke(sendCallBack, newBuffer); } } private void SendFile(byte[] sendBuffer) { try { dicSocket[cmb_Socket.SelectedItem.ToString()].Send(sendBuffer, SocketFlags.None); } catch (Exception ex) { MessageBox.Show("傳送檔案出錯:"+ex.Message); } } private void btn_Shock_Click(object sender, EventArgs e) { byte[] buffer = new byte[1] { 2}; dicSocket[cmb_Socket.SelectedItem.ToString()].Send(buffer); } /// <summary> /// 停止監聽 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_StopListen_Click(object sender, EventArgs e) { socketWatch.Close(); socketSend.Close(); //終止執行緒 AcceptSocketThread.Abort(); threadReceive.Abort(); } } }
使用者端介面
程式碼實現如下:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Net.Sockets; using System.Net; using System.Threading; using System.IO; namespace SocketClient { public partial class FrmClient : Form { public FrmClient() { InitializeComponent(); } //定義回撥 private delegate void SetTextCallBack(string strValue); //宣告 private SetTextCallBack setCallBack; //定義接收伺服器端傳送訊息的回撥 private delegate void ReceiveMsgCallBack(string strMsg); //宣告 private ReceiveMsgCallBack receiveCallBack; //建立連線的Socket Socket socketSend; //建立接收使用者端傳送訊息的執行緒 Thread threadReceive; /// <summary> /// 連線 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Connect_Click(object sender, EventArgs e) { try { socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse(this.txt_IP.Text.Trim()); socketSend.Connect(ip, Convert.ToInt32(this.txt_Port.Text.Trim())); //範例化回撥 setCallBack = new SetTextCallBack(SetValue); receiveCallBack = new ReceiveMsgCallBack(SetValue); this.txt_Log.Invoke(setCallBack, "連線成功"); //開啟一個新的執行緒不停的接收伺服器傳送訊息的執行緒 threadReceive = new Thread(new ThreadStart(Receive)); //設定為後臺執行緒 threadReceive.IsBackground = true; threadReceive.Start(); } catch (Exception ex) { MessageBox.Show("連線伺服器端出錯:" + ex.ToString()); } } /// <summary> /// 介面伺服器傳送的訊息 /// </summary> private void Receive() { try { while (true) { byte[] buffer = new byte[2048]; //實際接收到的位元組數 int r = socketSend.Receive(buffer); if (r == 0) { break; } else { //判斷傳送的資料的型別 if (buffer[0] == 0)//表示傳送的是文字訊息 { string str = Encoding.Default.GetString(buffer, 1, r - 1); this.txt_Log.Invoke(receiveCallBack, "接收遠端伺服器:" + socketSend.RemoteEndPoint + "傳送的訊息:" + str); } //表示傳送的是檔案 if (buffer[0] == 1) { SaveFileDialog sfd = new SaveFileDialog(); sfd.InitialDirectory = @""; sfd.Title = "請選擇要儲存的檔案"; sfd.Filter = "所有檔案|*.*"; sfd.ShowDialog(this); string strPath = sfd.FileName; using (FileStream fsWrite = new FileStream(strPath, FileMode.OpenOrCreate, FileAccess.Write)) { fsWrite.Write(buffer, 1, r - 1); } MessageBox.Show("儲存檔案成功"); } } } } catch (Exception ex) { MessageBox.Show("接收伺服器端傳送的訊息出錯:" + ex.ToString()); } } private void SetValue(string strValue) { this.txt_Log.AppendText(strValue + "r n"); } /// <summary> /// 使用者端給伺服器傳送訊息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_Send_Click(object sender, EventArgs e) { try { string strMsg = this.txt_Msg.Text.Trim(); byte[] buffer = new byte[2048]; buffer = Encoding.Default.GetBytes(strMsg); int receive = socketSend.Send(buffer); } catch (Exception ex) { MessageBox.Show("傳送訊息出錯:" + ex.Message); } } private void FrmClient_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 斷開連線 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_CloseConnect_Click(object sender, EventArgs e) { //關閉socket socketSend.Close(); //終止執行緒 threadReceive.Abort(); } } }
到此這篇關於C#網路程式設計之Socket程式設計的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45