本文主要講述了基於套接字(Socket)進行網絡編程的基本概念,其中包括TCP協議、套接字,以及兩個基本操作:偵聽端口、連接遠程服務端。
TCP是面向連接的,它的意思是說兩個遠程主機(或者叫進程,因為實際上遠程通信是進程之間的通信,而進程則是運行中的程序),必須首先進行一個握手過程,確認連接成功,之后才能傳輸實際的數據。比如說進程A想將字符串“It's a rainy world”發給進程B,它首先要建立連接。在這一過程中,它首先需要知道進程B的位置(主機地址和端口號)。隨后發送一個不包含實際數據的請求報文,我們可以將這個報文稱之為“hello”。如果進程B接收到了這個“hello”,就向進程A回復一個“hello”,進程A隨后才發送實際的數據“It's a rainy world”。
關於TCP第二個需要了解的,就是它是全雙工的。意思是說如果兩個主機上的進程(比如進程A、進程B),一旦建立好連接,那么數據就既可以由A流向B,也可以由B流向A。除此以外,它還是點對點的,意思是說一個TCP連接總是兩者之間的,在發送中,通過一個連接將數據發給多個接收方是不可能的。TCP還有一個特性,就是稱為可靠的數據傳輸,意思是連接建立后,數據的發送一定能夠到達,並且是有序的,就是說發的時候你發了ABC,那么收的一方收到的也一定是ABC,而不會是BCA或者別的什么。
編程中與TCP相關的最重要的一個概念就是套接字。我們應該知道網絡七層協議,如果我們將上面的應用程、表示層、會話層籠統地算作一層(有的教材便是如此划分的),那么我們編寫的網絡應用程序就位於應用層,而大家知道TCP是屬於傳輸層的協議,那么我們在應用層如何使用傳輸層的服務呢(消息發送或者文件上傳下載)?大家知道在應用程序中我們用接口來分離實現,在應用層和傳輸層之間,則是使用套接字來進行分離。它就像是傳輸層為應用層開的一個小口,應用程序通過這個小口向遠程發送數據,或者接收遠程發來的數據;而這個小口以內,也就是數據進入這個口之后,或者數據從這個口出來之前,我們是不知道也不需要知道的,我們也不會關心它如何傳輸,這屬於網絡其它層次的工作。 (舉個例子,如果你想寫封郵件發給遠方的朋友,那么你如何寫信、將信打包,屬於應用層,信怎么寫,怎么打包完全由我們做主;而當我們將信投入郵筒時,郵筒的那個口就是套接字,在進入套接字之后,就是傳輸層、網絡層等(郵局、公路交管或者航線等)其它層次的工作了。我們從來不會去關心信是如何從西安發往北京的,我們只知道寫好了投入郵筒就OK了。 )
接下來介紹下,在C#中如何進行網絡編程
服務端
第一步就是開啟對本地機器上某一端口的偵聽。首先創建一個控制台應用程序,將項目名稱命名為ServerConsole,它代表我們的服務端。如果想要與外界進行通信,第一件要做的事情就是開啟對端口的偵聽,這就像為計算機打開了一個“門”,所有向這個“門”發送的請求(“敲門”)都會被系統接收到。在C#中可以通過下面幾個步驟完成,首先使用本機Ip地址和端口號創建一個System.Net.Sockets.TcpListener類型的實例,然后在該實例上調用Start()方法,從而開啟對指定端口的偵聽。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace SocksTest
{
class Program
{
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.ToString(),remoteClient.Client.RemoteEndPoint.ToString());
Console.Read();
}
}
}
面的代碼中,我們開啟了對8500端口的偵聽。在運行了上面的程序之后,然后打開“命令提示符”,輸入“netstat-a”,可以看到計算機器中所有打開的端口的狀態。可以從中找到8500端口,看到它的狀態是LISTENING,這說明它已經開始了偵聽:
客戶端:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; namespace SocksClient { class Program { static void Main(string[] args) { Console.WriteLine("Client is running......"); TcpClient tcpClient = new TcpClient(); try { tcpClient.Connect("localhost", 8500); } catch (Exception e) { Console.WriteLine(e.ToString()); Console.Read(); return; } Console.WriteLine("Server Connected!{0} --> {1}", tcpClient.Client.LocalEndPoint.ToString(),tcpClient.Client.RemoteEndPoint.ToString()); Console.Read(); } } }
當開啟了server,然后開啟client,server上會顯示連接上的狀態。
接下來做的是獲取多客戶端連接
修改后的客戶端代碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; namespace SocksClient { class Program { static void Main(string[] args) { Console.WriteLine("Client is running......"); TcpClient tcpClient; for (int i = 0; i < 5; i++) { try { tcpClient = new TcpClient(); tcpClient.Connect("localhost", 8500); Console.WriteLine("Server Connected!{0} --> {1}", tcpClient.Client.LocalEndPoint.ToString(), tcpClient.Client.RemoteEndPoint.ToString()); } catch (Exception e) { Console.WriteLine(e.ToString()); Console.Read(); return; } } Console.Read(); } } }
修改后的服務端代碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; namespace SocksTest { class Program { 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 ..."); while (true) { TcpClient remoteClient = listener.AcceptTcpClient();//接受掛起的連接請求 Console.WriteLine("Client Connected!{0} <-- {1}", remoteClient.Client.LocalEndPoint.ToString(), remoteClient.Client.RemoteEndPoint.ToString()); } Console.Read(); } } }
運行后的效果
微軟對以上做了很好的封裝, 這里用起來很簡單.