標籤:網路 image 執行個體 ip地址 error: [] 在伺服器 類型 客戶
博主要做一個手機和電腦端(C#)通訊的程式,便覽了網路上關乎socket的東西。但是接收檔案的時候卡住了,怎麼也接收不全。後來做了分區處理,如果分區,發送的時候就會有不同的socket(用戶端開發不是我,故我不能控制人家怎麼發),結果撞山了。
因為發送的時候for迴圈發,導致不是有重幀就是丟失,故進行了深入的研究。
1.Socket
Socket包括Ip地址和連接埠號碼兩部分,程式通過Socket來通訊,Socket相當於作業系統的一個組件。Socket作為進程之間通訊機制,通常也稱作”通訊端”,用於描述IP地址和連接埠號碼,是一個通訊鏈的控制代碼。說白了,就是兩個程式通訊用的。
更深刻理解引用一個同行的博文:http://blog.csdn.net/jiajia4336/article/details/8798421
生活案例對比:
Socket之間的通訊可以類比生活中打電話的案例。任何使用者在通話之前,首先要佔有一部電話機,相當於申請一個Socket,同時要知道對方的號碼,相當於對方有一個固定的Socket,然後向對方撥號呼叫,相當於發出串連請求。假如對方在場並空閑,拿起 電話話筒,雙方就可以進行通話了。雙方的通話過程,是一方向電話機發出訊號和對方從電話機接收訊號的過程,相當於向socket發送資料和從socket接收資料。通話結束後,一方掛起電話機,相當於關閉socket,撤銷串連。
注意:Socket不僅可以在兩台電腦之間通訊,還可以在同一台電腦上的兩個程式間通訊。
2,連接埠進階(深入)
連接埠號碼範圍:0-65535,總共能表示65536個數。
按連接埠號碼可分為3大類
(1)公認連接埠(WellKnownPorts):從0到1023,它們緊密綁定(binding)於一些服務。通常這些連接埠的通訊明確表明了某種服務的協議。例如:80連接埠實際上總是HTTP通訊。
(2)註冊連接埠(RegisteredPorts):從1024到49151。它們鬆散地綁定於一些服務。也就是說有許多服務綁定於這些連接埠,這些連接埠同樣用於許多其它目的。例如:許多系統處理動態連接埠從1024左右開始。
(3)動態和/或私人連接埠(Dynamicand/orPrivatePorts):從49152到65535。理論上,不應為服務分配這些連接埠。實際上,機器通常從1024起分配動態連接埠。
通過IP地址確定了網路中的一台電腦後,該電腦上可能提供很多提供服務的應用,每一個應用都對應一個連接埠。
在Internet上有很多這樣的主機,這些主機一般運行了多個服務軟體 ,同時提供幾種服務,每種服務都開啟一個Socket,並綁定到一個連接埠上,不同的連接埠對應於不同的服務(應用程式)
例如:http 使用80連接埠, ftp使用21連接埠 smtp使用25連接埠
3.Socket分類
Socket主要有兩種類型:
- 流式Socket
是一種連線導向的Socket,針對於連線導向的TCP服務應用,安全,但是效率低
2,資料報式Socket
是一種不需連線的Socket,對應於不需連線的UDP服務應用,不安全,但效率高
4. Socket一般應用模式(伺服器端和用戶端)
伺服器端的Socket(至少需要兩個)
01.一個負責接收用戶端串連請求(但不負責與用戶端通訊)
02.每成功接收到用戶端的串連便在伺服器端產生一個對應的複雜通訊的Socket
021.在接收到用戶端串連時建立
022. 為每個串連成功的用戶端請求在伺服器端都建立一個對應的Socket(負責和用戶端通訊)
用戶端的Socket
- 必須指定要已連線的服務器地址和連接埠
- 通過建立一個Socket對象來初始化一個到伺服器端的TCP串連
通過,我們可以看出,首先伺服器會建立一個負責監聽的socket,然後用戶端通過socket串連到伺服器指定連接埠,最後伺服器端負責監聽的socket,監聽到用戶端有串連過來了,就建立一個負責和用戶端通訊的socket。
下面我們來看下Socket更具體的通訊過程:
Socket的通訊過程
伺服器端:
01,申請一個socket
02,綁定到一個IP地址和一個連接埠上
03,開啟偵聽,等待接收串連
用戶端:
01,申請一個socket
02,串連伺服器(指明IP地址和連接埠號碼)
伺服器端接收到串連請求後,產生一個新的socket(連接埠大於1024)與用戶端建立串連並進行通訊,原監聽socket繼續監聽。
注意:負責通訊的Socket不能無限建立,建立的數量和作業系統有關。
5.Socket的建構函式
Public Socket(AddressFamily addressFamily,SocketType socketType,ProtocolType protocolTYpe)
AddressFamily:指定Socket用來解析地址的定址方案。例如:InterNetWork指示當Socket使用一個IP版本4地址串連
SocketType:定義要開啟的Socket的類型
Socket類使用ProtocolType枚舉向Windows Sockets API通知所請求的協議
注意:
1,連接埠號碼必須在 1 和 65535之間,最好在1024以後。
2,要串連的遠程主機必須正在監聽指定連接埠,也就是說你無法隨意串連遠程主機。
如:
IPAddress addr = IPAddress.Parse("127.0.0.1");
IPEndPoint endp = new IPEndPoint(addr,,9000);
服務端先綁定:serverWelcomeSocket.Bind(endp)
用戶端再串連:clientSocket.Connect(endp)
3,一個Socket一次只能串連一台主機
4,Socket關閉後無法再次使用
5,每個Socket對象只能與一台遠程主機串連。如果你想串連到多台遠程主機,你必須建立多個Socket對象。
6.Socket常用類和方法
相關類:
IPAddress:包含了一個IP地址
IPEndPoint:包含了一對IP地址和連接埠號碼
方法:
Socket():建立一個Socket
Bind():綁定一個本地的IP和連接埠號碼(IPEndPoint)
Listen():讓Socket偵聽傳入的串連吃那個病,並指定偵聽隊列容量
Connect():初始化與另一個Socket的串連
Accept():接收串連並返回一個新的Socket
Send():輸出資料到Socket
Receive():從Socket中讀取資料
Close():關閉Socket,銷毀串連
7、socket接收
博主就是因為網上的一些不良代碼和思路被坑了不少時間,故此特意說明。
socket的接收緩衝區
我們都知道socket的接收方法使receive,receive是從socket緩衝區中讀取,socket緩衝區是一個動態東西,你讀取一個,就相當於處理一個,就減少一個。
此緩衝區的意義:當前接收到的資料,意思是截止到你讀取的時候裡面的資料,這裡面的資料是可以隨著時間增加的。
我們可以理解為一個隊列,發送方往隊列中添加資料,receive取資料。
socket接收資料
windows的此緩衝區限制了大小為8k,故最多我們可以接受8k的資料,如果我們處理的過慢,又超過了發送方的逾時時間,發送方就會顯示逾時。故同步處理就需要我們及時讀取。否則,請非同步處理,或者另起線程處理。
當然,如果我們雙方約定了訊息的大小,比如1k,那麼我們接受的時候就可以每次讀取1024個byte。
重點是,如果我們要讀取一個檔案,發送方使用一個socket的一次send,那麼我們只能一次讀取一個位元組(約定為偶數的可以讀取2個,約定4個或者8個的倍數的,可以讀取4個或者8個),緩衝區的資料是按順序到達的。
讀取後,我們在重新拼裝成訊息,然後解析檔案欄位,還原為檔案。
另:如果檔案過大,切片後,不停的new socket發送是不可取的,可以嘗試同一個socket發送(博主未驗證)
上一段我接收的代碼:
private void ServerStart() { //建立IPEndPoint執行個體 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, Port); //建立socket通訊端 serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //將所建立的通訊端與IPEndPoint綁定 serverSocket.Bind(ipep); //設定socket為監聽Listen模式 serverSocket.Listen(9999); while (true) { try { //在通訊端上接收接入的串連 Socket acceptSocket = serverSocket.Accept(); if (acceptSocket != null) { Thread socketConnectedThread = new Thread(ReceiveData); socketConnectedThread.IsBackground = true; socketConnectedThread.Start(acceptSocket); //ThreadPool.SetMinThreads(100, 100); //ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveData), acceptSocket); Dispatcher.BeginInvoke((Action)(()=> { richTextBox.AppendText("\n有用戶端接入..."); })); } } catch (Exception ex) { MessageBox.Show("listening Error: " + ex.Message); } } } private void ReceiveData(object obj) { Socket s = (Socket)obj; //根據收聽到的用戶端通訊端向用戶端發送資訊 IPEndPoint clientep = (IPEndPoint)s.RemoteEndPoint; try { List<byte> list = new List<byte>(); byte[] buffer = new byte[1]; int readBytes = 0; do { readBytes = s.Receive(buffer, 0, buffer.Length,SocketFlags.None); list.AddRange(buffer); } while (s.Available!=0); byte[] buffers = list.ToArray(); Dispatcher.BeginInvoke((Action)(() => { richTextBox.AppendText("\nIP地址:" + clientep.Address + " 連接埠號碼:" + clientep.Port); })); byte[] messageBuffer = new byte[list.Count-8]; messageBuffer = buffers.Skip(8).Take(list.Count - 8).ToArray(); Message receive = MessageHelp.DeSerialize(messageBuffer); if (receive != null) { switch (receive.messageType) { case "link": ClientIp = receive.sourceIp; ClientPort = Convert.ToInt32(receive.sourcePort); //接到後發送ok byte[] sendData = MessageHelp.Serialize(new Message { messageType = "link", sourceIp = LocalIpAddress, sourcePort = Port.ToString(), content = "ok" }); //s.Send(sendData, sendData.Length, SocketFlags.None); Socket toClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); toClient.Connect(new IPEndPoint(IPAddress.Parse(ClientIp), ClientPort));//與該ip地址進行串連 toClient.Send(sendData, sendData.Length, SocketFlags.None); break; case "command": byte[] responseData = MessageHelp.Serialize(new Message { messageType = "command", sourceIp = LocalIpAddress, sourcePort = Port.ToString(), content = receive.content}); Socket reClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); reClient.Connect(new IPEndPoint(IPAddress.Parse(ClientIp), ClientPort));//與該ip地址進行串連 reClient.Send(responseData, responseData.Length, SocketFlags.None); Dispatcher.BeginInvoke((Action)(() => { richTextBox.AppendText("\n" + receive.content); })); break; case "file": FileName = receive.fileName; byte[] csharpFileByte = new byte[receive.fileTotalLength]; for (int i = 0; i < receive.bytes.Length; i++) { csharpFileByte[i] = Convert.ToByte(receive.bytes[i] & 0xff); } System.IO.File.WriteAllBytes(FileName, csharpFileByte); MessageBox.Show("檔案接收成功"); //接到後發送ok byte[] responseFile = MessageHelp.Serialize(new Message { messageType = "file", sourceIp = LocalIpAddress, sourcePort = Port.ToString(), content = receive.content }); //s.Send(sendData, sendData.Length, SocketFlags.None); Socket fClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); fClient.Connect(new IPEndPoint(IPAddress.Parse(ClientIp), ClientPort));//與該ip地址進行串連 fClient.Send(responseFile, responseFile.Length, SocketFlags.None); break; } } } catch (Exception ex) { s.Close(); } s.Close(); s.Dispose(); }
C#のsocket通訊