一、背景與概念
1.標準以太網
以太網是美國Xerox(施樂)公司的Palo Alto研究中心于1975年研制成功的,其核心技術起源于ALOHA網。目前以太網是指符合IEEE 802.3標準的局域網(LAN)產品組,其中IEEE 802.3是一組電氣與電子工程師協會(IEEE)標準,用于定義有線以太網媒體訪問控制的物理層和數據鏈路層,說明了配置以太網網絡的規則,以及各種網絡元件如何彼此協作。以太網分為:
1.標準以太網:早期的10Mbps以太網稱之為標準以太網,是一種總線型局域網,由同軸電纜、網卡(網絡適配器)組成。。
發送和接收數據通過CSMA/CD協議完成,流程可以簡單概括為四點:先聽后發,邊聽邊發,沖突停止,延遲重發。
2.總線以太網通過中繼器、網橋進行擴展。
中繼器(Repeater)也叫轉發器,將在傳輸介質中衰減的信號放大,以增加線纜的傳輸距離。
網橋(Bridge)又稱橋接器,是一種存儲轉發設備。它工作在數據鏈路層,當它收到一個幀時,先檢查此幀的目的MAC地址,然后再確定將該幀轉發到哪一個端口。
以太網種類繁多,包括基于集線器的以太網,基于交換機的以太網等,目前已發展到十萬兆以太網。。
2.工業以太網
工業以太網一般來講是指技術上與商用以太網(即IEEE802.3標準)兼容,但在產品設計時,在材質的選用、產品的強度、適用性以及實時性等方面能滿足工業現場的需要。
目前包括4種主要協議:HSE、Modbus TCP/IP、ProfINet、Ethernet/IP。
基金會現場總線FF于2000年發布Ethernet規范,稱HSE(High Speed Ethernet)。
Modbus TCP/IP協議由施耐德公司推出,以一種非常簡單的方式將Modbus幀嵌入到TCP幀中,使Modbus與以太網和TCP/IP結合。
德國西門子于2001年發布了ProfiNet網絡方案,它是將原有的Profibus與互聯網技術結合。ProfiNet采用標準TCP/IP協議加上應用層的RPC/DCOM來完成節點間的通信和網絡尋址。
Ethernet/IP是適合工業環境應用的協議體系,基于CIP(Controland Information Proto-Col)協議。它是一種是面向對象的協議,能夠保證網絡上隱式(控制)的實時I/O信息和顯式信息(包括用于組態、參數設置、診斷等) 的有效傳輸。
工業以太網設備包括以下幾個重要部分:
1.工業以太網集線器
2.工業以太網非管理型交換機
3.工業以太網管理型交換機
4.工業以太網管理型冗余交換機
3.TCP/IP協議
上表為網絡協議基本功能與協議,TCP(Transmission Control Protocol)協議在傳輸層,IP(Internet Protocol)協議在網絡層。
1.TCP協議:是一種面向連接的、可靠的、基于字節流的傳輸層通信協議,是為了在不可靠的互聯網絡上提供可靠的端到端字節流而專門設計的一個傳輸協議。互聯網絡與單個網絡有很大的不同,因為互聯網絡的不同部分可能有截然不同的拓撲結構、帶寬、延遲、數據包大小和其他參數。TCP的設計目標是能夠動態地適應互聯網絡的這些特性,而且具備面對各種故障時的健壯性。
2.IP協議:是網絡之間信息傳送的協議,可將IP信息包從源設備(例如用戶的計算機)傳送到目的設備(例如某部門的www服務器)。其目的一是解決互聯網問題,實現大規模、異構網絡的互聯互通;二是分割頂層網絡應用和底層網絡技術之間的耦合關系,以利于兩者的獨立發展。
3.三次握手,四次分手
其中,SYN:同步序列編號(Synchronize Sequence Numbers),是TCP/IP建立連接時使用的握手信號。ACK:ACK (Acknowledge character)即是確認字符,在數據通信中,接收站發給發送站的一種傳輸類控制字符。表示發來的數據已確認接收無誤。
FIN_WAIT:FIN_WAIT_1和FIN_WAIT_2都是表示等待對方的FIN報文。
4. 套接字
應用層和傳輸層之間,是使用套接字來進行分離。套接字包含兩個信息:連接至遠程的本地的端口信息(本機地址和端口號),連接到的遠程的端口信息(遠程地址和端口號)。
5.TcpClient和TcpListener
.NET提供了這兩個類將對套接字的編程進行了一個封裝。
一般將發起連接的一方稱為客戶端,另一端稱為服務端,則現在可以得出:總是服務端在使用TcpListener類,因為它需要建立起一個初始的連接。
IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); // 開始偵聽Console.WriteLine("Start Listening ...");
8500端口:提供獲取服務列表、注冊服務、注銷服務等HTTP接口;提供UI服務,使用cmd:netstat -a;
每創建一個新的TcpClient便相當于創建了一個新的套接字Socket去與服務端通信,.Net會自動為這個套接字分配一個端口號.
創建TcpClient類型實例時,可以在構造函數中指定遠程服務器的地址和端口號。這樣在創建的同時,就會向遠程服務端發送一個連接請求(“握手”),一旦成功,則兩者間的連接就建立起來了。也可以使用重載的無參數構造函數創建對象,然后再調用Connect()方法,在Connect()方法中傳入遠程服務器地址和端口號,來與服務器建立連接,不管是使用有參數的構造函數與服務器連接,或者是通過Connect()方法與服務器建立連接,都是同步方法(或者說是阻塞的,英文叫block)。
6.端口通訊
在與服務端的連接建立以后,我們就可以通過此連接來發送和接收數據。端口與端口之間以流(Stream)的形式傳輸數據,因為幾乎任何對象都可以保存到流中,所以實際上可以在客戶端與服務端之間傳輸任何類型的數據。對客戶端來說,往流中寫入數據,即為向服務器傳送數據;從流中讀取數據,即為從服務端接收數據。對服務端來說,往流中寫入數據,即為向客戶端發送數據;從流中讀取數據,即為從客戶端接收數據。
二、示例
1.客戶端和服務端建立連接
分別建立兩個控制臺程序。
class Client{static void Main(string[] args){TcpClient client = new TcpClient();try{client.Connect("local", 8500); }catch (Exception ex){Console.WriteLine(ex.Message);return;}Console.WriteLine("Server Connected!{0} --> {1}",client.Client.LocalEndPoint, client.Client.RemoteEndPoint);}}
static void Main(string[] args){Console.WriteLine("Server is running ... ");IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); Console.WriteLine("Start Listening ...");TcpClient remoteClient = listener.AcceptTcpClient();Console.WriteLine("Client Connected!{0} <-- {1}",remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); }
結果就不顯示了,反正都成功了,兩個應用程序建立了連接。
2.一個客戶端發出多個請求
static void Main(string[] args){const int BufferSize = 8192; Console.WriteLine("Server is running ... ");IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); Console.WriteLine("Start Listening ...");TcpClient remoteClient = listener.AcceptTcpClient();Console.WriteLine("Client Connected!{0} <-- {1}",remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);NetworkStream streamToClient = remoteClient.GetStream();do{byte[] buffer = new byte[BufferSize];int bytesRead = streamToClient.Read(buffer, 0, BufferSize);Console.WriteLine("Reading data, {0} bytes ...", bytesRead);string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);Console.WriteLine("Received: {0}", msg);} while (true);}
class Client{static void Main(string[] args){Console.WriteLine("Client Running ...");TcpClient client;try{client = new TcpClient();client.Connect("localhost", 8500); catch (Exception ex){Console.WriteLine(ex.Message);return;}Console.WriteLine("Server Connected!{0} --> {1}",client.Client.LocalEndPoint, client.Client.RemoteEndPoint);NetworkStream streamToServer = client.GetStream();ConsoleKey key;Console.WriteLine("Menu: M - Send, G - Exit");do{key = Console.ReadKey(true).Key;if (key == ConsoleKey.M){Console.Write("Input the message: ");string msg = Console.ReadLine();byte[] buffer = Encoding.Unicode.GetBytes(msg); streamToServer.Write(buffer, 0, buffer.Length); Console.WriteLine("Sent: {0}", msg);}} while (key != ConsoleKey.G);//client.Close();}}
###3.服務端回傳處理過的字符
服務端截取客戶端傳輸的字符串,回傳給客戶端。
static void Main(string[] args){const int BufferSize = 8192; ConsoleKey key;Console.WriteLine("Server is running ... ");IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });TcpListener listener = new TcpListener(ip, 8500);listener.Start(); Console.WriteLine("Start Listening ...");TcpClient remoteClient = listener.AcceptTcpClient();Console.WriteLine("Client Connected!{0} <-- {1}",remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);NetworkStream streamToClient = remoteClient.GetStream();do{byte[] buffer = new byte[BufferSize];int bytesRead;try{lock (streamToClient){bytesRead = streamToClient.Read(buffer, 0, BufferSize);}if (bytesRead == 0) throw new Exception("讀取到0字節");Console.WriteLine("Reading data, {0} bytes ...", bytesRead);string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);Console.WriteLine("Received: {0}", msg);msg = msg.Substring(2);buffer = Encoding.Unicode.GetBytes(msg);lock (streamToClient){streamToClient.Write(buffer, 0, buffer.Length);}Console.WriteLine("Sent: {0}", msg);}catch (Exception ex){Console.WriteLine(ex.Message);break;}} while (true);streamToClient.Dispose();remoteClient.Close();Console.WriteLine("\n\n輸入\"Q\"鍵退出。");do{key = Console.ReadKey(true).Key;} while (key != ConsoleKey.Q);}
class Client{static void Main(string[] args){Console.WriteLine("Client Running ...");TcpClient client;ConsoleKey key;const int BufferSize = 8192;try{client = new TcpClient();client.Connect("localhost", 8500); }catch (Exception ex){Console.WriteLine(ex.Message);return;}Console.WriteLine("Server Connected!{0} --> {1}",client.Client.LocalEndPoint, client.Client.RemoteEndPoint); NetworkStream streamToServer = client.GetStream();Console.WriteLine("Menu: S - Send, X - Exit");do{key = Console.ReadKey(true).Key;if (key == ConsoleKey.S){Console.Write("Input the message: ");string msg = Console.ReadLine();byte[] buffer = Encoding.Unicode.GetBytes(msg); try{lock (streamToServer){streamToServer.Write(buffer, 0, buffer.Length);}Console.WriteLine("Sent: {0}", msg);int bytesRead;buffer = new byte[BufferSize];lock (streamToServer){bytesRead = streamToServer.Read(buffer, 0, BufferSize);}msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);Console.WriteLine("Received: {0}", msg);}catch (Exception ex){Console.WriteLine(ex.Message);break;}}} while (key != ConsoleKey.X);streamToServer.Dispose();client.Close();Console.WriteLine("\n\n輸入\"Q\"鍵退出。");do{key = Console.ReadKey(true).Key;} while (key != ConsoleKey.Q); }}
三、TCP端口狀態說明
1.LISTENING狀態
FTP (文件傳輸協議)服務器啟動時后處于這一狀態,直到和客戶端連接成功。
2.ESTABLISHED狀態
連接成功就處于這一狀態,表示兩端正在通訊。
3.CLOSE_WAIT狀態
對方主動關閉連接或者網絡異常導致連接中斷均會變成此狀態。
4.TIME_WAIT狀態
主動調用close()斷開連接,收到對方確認后狀態變為TIME_WAIT。TCP協議規定TIME_WAIT狀態會一直持續2MSL(兩倍的分段最大生存期),以此來確保舊的連接狀態不會對新連接產生影響。
5.SYN_SENT狀態
SYN_SENT狀態表示請求連接,當你要訪問其它的計算機的服務時首先要發個同步信號給該端口,此時狀態為SYN_SENT,如果連接成功了就變為 ESTABLISHED,此時SYN_SENT狀態非常短暫。如果發現SYN_SENT非常多且在向不同的機器發出,機器可能中病毒了。
四、總結
總結下Socket:
1,建立服務端和客戶端的連接。
2,客戶端發送消息給服務端。
3,服務端接收到客戶端的連接。
4,服務端對消息進行處理再發給客戶端。
5. 客戶端收到消息。
6. 結束。
參考文章:
以太網
七層網絡協議–tcp/ip協議
網絡編程