一、摘要
總結一下基於C#的TCP傳輸協議的涉及到的常用方法及同步實現。
二、實驗平台
Visual Studio 2010
三、socket編程的一些常用方法(同步實現)
3.1 命名空間
需要添加的命名空間
using System.Net; using System.Net.Socket;
3.2 構造新的socket對象
socket原型:
public socket (AddressFamily addressFamily,SocketType sockettype,ProtocolType protocolType)
(1) AddressFamily 用來指定socket解析地址的尋址方案,Inte.Network標示需要ip版本4的地址,Inte.NetworkV6需要ip版本6的地址;
(2) SocketType 參數指定socket類型,Raw支持基礎傳輸協議訪問,Stream支持可靠,雙向,基於連接的數據流;
(3) ProtocolType 表示socket支持的網絡協議,如常用的TCP和UDP協議。
3.3 定義主機對象
(1) IPEndPoint類
原型:
a)
public IPEndPoint(IPAddress address,int port)
參數address可以直接填寫主機的IP,如"192.168.2.1";
b)
public IPEndPoint(long address,int port)
參數address整型int64如123456,參數port端口int32,如6655。
(2) 利用DNS服務器解析主機,使用Dns.Resolve方法
原型:
public static IPHostEntry Resolve(string hostname)
參數:待解析的主機名稱,返回IPHostEntry類值,IPHostEntry為Inte.Net主機地址信息提供容器,該容器提供存有IP地址列表,主機名稱等。
(3) Dns.GetHostByName獲取本地主機名稱
原型:
public static IPHostEntry GetHostByName(string hostname)
(4) GetHostByAddress
原型:
a)
public static IPHostEntry GetHostByAddress(IPAddress address)
參數:IP地址。
b)
public static IPHostEntry GetHostByAddress(string address)
參數:IP地址格式化字符串。
3.4 端口綁定和監聽
同步套接字服務器主機的綁定和端口監聽,Socket類的Bind(綁定主機),Listen(監聽端口),Accept(接收客戶端的連接請求)。
(1) Bind
原型:
public void Bind(EndPoint LocalEP)
參數為主機對象 IPEndPoint
(2) Listen
原型:
public void Listen(int backlog)
參數整型數值,掛起隊列最大值
(3) accept
原型:
public socket accept()
返回為套接字對象
3.5 socket的發送和接收方法
(1) 發送數據
a)socket類的send方法
原型一:
public int Send(byte[] buffer)
參數:待發送的字節數組;
原型二:
public int Send(byte[],SocketFlags)
SocketFlags成員列表:
DontRoute不使用路由表發送,
MaxIOVectorLength為發送和接收數據的wsabuf結構數量提供標准值,
None 不對次調用使用標志,
OutOfBand消息的部分發送或接收,
Partial消息的部分發送或接收,
Peek查看傳入的消息。
原型三:
public int Send(byte[],int,SocketFlags)
參數二要發送的字節數
原型四:
public int Send(byte[],int,int,SocketFlags)
參數二為Byte[]中開始發送的位置
b) NetWordStream類的Write方法
原型:
public override void write(byte[] buffer,int offset,int size)
參數分別為:字節數組,開始字節位置,總字節數。
(2) 接收數據
a) Socket類Receive方法
原型一:
public int Receive(byte[] buffer)
原型二:
public int Receive(byte[],SocketFlags)
原型三:
public int Receive(byte[],int,SocketFlags)
原型四:
public int Receive(byte[],int,int,SocketFlags)
Socket類Receive方法的相關參數可參看Socket類Send方法中的參數。
b) NetworkStream類的Read方法
public override int Read(int byte[] buffer,int offset,int size)
參數可參看NetworkStream類的Write方法。
四、TCP傳輸協議的同步實現
4.1 服務器端編程的步驟:
(1) 創建套接字;
(2) 綁定套接字到一個IP地址和一個端口上(bind());
(3)將套接字設置為監聽模式等待連接請求(listen());
(4)請求到來后,接受連接請求,返回一個新的對應於此次連接的套接字(accept());
(5)用返回的套接字和客戶端進行通信(send()/recv());
(6)返回,等待另一連接請求;
(7)關閉套接字。
服務器端代碼:
using System; using System.Net; using System.Net.Sockets; using System.Collections.Generic; using System.Text; namespace net { class Program { static void Main(string[] args) { //定義接收數據長度變量 int recv; //定義接收數據的緩存 byte[] data = new byte[1024]; //定義偵聽端口 IPEndPoint ipEnd = new IPEndPoint(IPAddress.Any, 5566); //定義套接字類型 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //連接 socket.Bind(ipEnd); //開始偵聽 socket.Listen(10); //控制台輸出偵聽狀態 Console.Write("Waiting for a client"); //一旦接受連接,創建一個客戶端 Socket client = socket.Accept(); //獲取客戶端的IP和端口 IPEndPoint ipEndClient = (IPEndPoint)client.RemoteEndPoint; //輸出客戶端的IP和端口 Console.Write("Connect with {0} at port {1}", ipEndClient.Address, ipEndClient.Port); //定義待發送字符 string welcome = "Welcome to my server"; //數據類型轉換 data = Encoding.ASCII.GetBytes(welcome); //發送 client.Send(data, data.Length, SocketFlags.None); while (true) { //對data清零 data = new byte[1024]; //獲取收到的數據的長度 recv = client.Receive(data); //如果收到的數據長度為0,則退出 if (recv == 0) break; //輸出接收到的數據 Console.Write(Encoding.ASCII.GetString(data, 0, recv)); //將接收到的數據再發送出去 client.Send(data, recv, SocketFlags.None); } Console.Write("Disconnect form{0}", ipEndClient.Address); client.Close(); socket.Close(); } } }
4.2 客戶端編程的步驟:
(1) 創建套接字;
(2) 向服務器發出連接請求(connect());
(3) 和服務器端進行通信(send()/recv());
(4) 關閉套接字。
客戶端代碼:
using System; using System.Net; using System.Net.Sockets; using System.Collections.Generic; using System.Text; namespace client { class Program { static void Main(string[] args) { //定義發送數據緩存 byte[] data = new byte[1024]; //定義字符串,用於控制台輸出或輸入 string input, stringData; //定義主機的IP及端口 IPAddress ip = IPAddress.Parse("127.0.0.1"); IPEndPoint ipEnd = new IPEndPoint(ip, 5566); //定義套接字類型 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //嘗試連接 try { socket.Connect(ipEnd); } //異常處理 catch (SocketException e) { Console.Write("Fail to connect server"); Console.Write(e.ToString()); return; } //定義接收數據的長度 int recv = socket.Receive(data); //將接收的數據轉換成字符串 stringData = Encoding.ASCII.GetString(data, 0, recv); //控制台輸出接收到的數據 Console.Write(stringData); //定義從鍵盤接收到的字符串 input = Console.ReadLine(); //將從鍵盤獲取的字符串轉換成整型數據並存儲在數組中 data = Encoding.ASCII.GetBytes(input); //發送該數組 socket.Send(data, data.Length, SocketFlags.None); while (true) { // //如果字符串是"exit",退出while循環 if (input == "exit") { break; } //對data清零 data = new byte[1024]; //定義接收到的數據的長度 recv = socket.Receive(data); //將接收到的數據轉換為字符串 stringData = Encoding.ASCII.GetString(data, 0, recv); //控制台輸出字符串 Console.Write(stringData); //發送收到的數據 socket.Send(data, recv, 0); } Console.Write("disconnect from server"); socket.Shutdown(SocketShutdown.Both); socket.Close(); } } }
上述代碼實現了,當連接建立之后,客戶端向服務器端發送鍵盤輸入的字符,服務器端收到字符后,顯示在控制台並發送給客戶端,客戶端收到字符后,顯示在控制台並再次發送給服務器端,如此循環。
五、實驗結果
先后運行服務器端程序和客戶端程序,控制台界面如下:
圖1 服務器端控制台
當連接建立后,服務器端控制台顯示等待客戶端的狀態"Waiting for a client",並打印出連接信息。
圖2 客戶端控制台
當連接建立后,客戶端收到來自服務器端發送的字符串"Welcome to my server"。
之后,客戶端通過鍵盤發送數據,二者循環接收並發送,控制台分別如下:
圖3 服務器控制台
圖4 客戶端控制台
六、幾點說明
6.1 傳輸速度
(1) 增大發送和接收的數組可提升傳輸速度,即增加一次實際發送數據的數量可以提高傳輸速度,但數組中數據的個數也不能一味的增大。需要說明的,由於地層MIT的限制,底層具體實現的時候每次發送的數據仍是不超過1510個的。
(2) 將控制台界面最小化后,速度也會有翻倍的提升。
6.2 MFC的轉換
為了使傳輸協議更有可觀性和使用性,通常做成MFC的樣式,具體的使用已在"基於TCP協議的網絡攝像頭的設計與實現"應用。