搬運整合三個使用C#實現Socket編程的例子,包含服務器端和客戶端。
原文鏈接:
C# socket監(jiān)聽
C#-Socket監(jiān)聽消息處理
基于C#的socket編程的TCP同步實現
按照鏈接順序貼上原文。
例子一:
網絡通訊流程如上
服務器:
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.IO;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace _06Server{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } PRivate void btnStart_Click(object sender, EventArgs e) { try { //當點擊開始監(jiān)聽的時候 在服務器端創(chuàng)建一個負責監(jiān)ip地址跟端口號的Socket Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Any;//IPAddress.Parse(txtServer.Text); //創(chuàng)建端口號對象 IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //監(jiān)聽 socketWatch.Bind(point); ShowMsg("監(jiān)聽成功"); socketWatch.Listen(10); Thread th = new Thread(Listen); th.IsBackground = true; th.Start(socketWatch); } catch { } } /// <summary> /// 等待客戶端的連接 并且創(chuàng)建與之通信用的Socket /// </summary> /// Socket socketSend; void Listen(object o) { Socket socketWatch = o as Socket; //等待客戶端的連接 并且創(chuàng)建一個負責通信的Socket while (true) { try { //負責跟客戶端通信的Socket socketSend = socketWatch.Accept(); //將遠程連接的客戶端的IP地址和Socket存入集合中 dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend); //將遠程連接的客戶端的IP地址和端口號存儲下拉框中 cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString()); //192.168.11.78:連接成功 ShowMsg(socketSend.RemoteEndPoint.ToString() + ":" + "連接成功"); //開啟 一個新線程不停的接受客戶端發(fā)送過來的消息 Thread th = new Thread(Recive); th.IsBackground = true; th.Start(socketSend); } catch { } } } //將遠程連接的客戶端的IP地址和Socket存入集合中 Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>(); /// <summary> /// 服務器端不停的接受客戶端發(fā)送過來的消息 /// </summary> /// <param name="o"></param> void Recive(object o) { Socket socketSend = o as Socket; while (true) { try { //客戶端連接成功后,服務器應該接受客戶端發(fā)來的消息 byte[] buffer = new byte[1024 * 1024 * 2]; //實際接受到的有效字節(jié)數 int r = socketSend.Receive(buffer); if (r == 0) { break; } string str = Encoding.UTF8.GetString(buffer, 0, r); ShowMsg(socketSend.RemoteEndPoint + ":" + str); } catch { } } } void ShowMsg(string str) { txtLog.AppendText(str + "/r/n"); } private void Form1_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } /// <summary> /// 服務器給客戶端發(fā)送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { string str = txtMsg.Text; byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str); List<byte> list = new List<byte>(); list.Add(0); list.AddRange(buffer); //將泛型集合轉換為數組 byte[] newBuffer = list.ToArray(); //buffer = list.ToArray();不可能 //獲得用戶在下拉框中選中的IP地址 string ip = cboUsers.SelectedItem.ToString(); dicSocket[ip].Send(newBuffer); // socketSend.Send(buffer); } /// <summary> /// 選擇要發(fā)送的文件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSelect_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.InitialDirectory = @"C:/Users/SpringRain/Desktop"; ofd.Title = "請選擇要發(fā)送的文件"; ofd.Filter = "所有文件|*.*"; ofd.ShowDialog(); txtPath.Text = ofd.FileName; } private void btnSendFile_Click(object sender, EventArgs e) { //獲得要發(fā)送文件的路徑 string path = txtPath.Text; using (FileStream fsRead = new FileStream(path, FileMode.Open, Fileaccess.Read)) { byte[] buffer = new byte[1024 * 1024 * 5]; int r = fsRead.Read(buffer, 0, buffer.Length); List<byte> list = new List<byte>(); list.Add(1); list.AddRange(buffer); byte[] newBuffer = list.ToArray(); dicSocket[cboUsers.SelectedItem.ToString()].Send(newBuffer, 0, r+1, SocketFlags.None); } } /// <summary> /// 發(fā)送震動 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnZD_Click(object sender, EventArgs e) { byte[] buffer = new byte[1]; buffer[0] = 2; dicSocket[cboUsers.SelectedItem.ToString()].Send(buffer); } }}客戶端:using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.IO;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;namespace _07Client{ public partial class Form1 : Form { public Form1() { InitializeComponent(); } Socket socketSend; private void btnStart_Click(object sender, EventArgs e) { try { //創(chuàng)建負責通信的Socket socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress ip = IPAddress.Parse(txtServer.Text); IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text)); //獲得要連接的遠程服務器應用程序的IP地址和端口號 socketSend.Connect(point); ShowMsg("連接成功"); //開啟一個新的線程不停的接收服務端發(fā)來的消息 Thread th = new Thread(Recive); th.IsBackground = true; th.Start(); } catch { } } /// <summary> /// 不停的接受服務器發(fā)來的消息 /// </summary> void Recive() { while (true) { try { byte[] buffer = new byte[1024 * 1024 * 3]; int r = socketSend.Receive(buffer); //實際接收到的有效字節(jié)數 if (r == 0) { break; } //表示發(fā)送的文字消息 if (buffer[0] == 0) { string s = Encoding.UTF8.GetString(buffer, 1, r-1); ShowMsg(socketSend.RemoteEndPoint + ":" + s); } else if (buffer[0] == 1) { SaveFileDialog sfd = new SaveFileDialog(); sfd.InitialDirectory = @"C:/Users/SpringRain/Desktop"; sfd.Title = "請選擇要保存的文件"; sfd.Filter = "所有文件|*.*"; sfd.ShowDialog(this); string path = sfd.FileName; using (FileStream fsWrite = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) { fsWrite.Write(buffer, 1, r - 1); } MessageBox.Show("保存成功"); } else if (buffer[0] == 2) { ZD(); } } catch { } } } /// <summary> /// 震動 /// </summary> void ZD() { for (int i = 0; i < 500; i++) { this.Location = new Point(200, 200); this.Location = new Point(280, 280); } } void ShowMsg(string str) { txtLog.AppendText(str + "/r/n"); } /// <summary> /// 客戶端給服務器發(fā)送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, EventArgs e) { string str = txtMsg.Text.Trim(); byte[] buffer = System.Text.Encoding.UTF8.GetBytes(str); socketSend.Send(buffer); } private void Form1_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; } private void txtServer_TextChanged(object sender, EventArgs e) { } }}例子二:TCP/IP:Transmission Control Protocol/Internet Protocol,傳輸控制協議/因特網互聯協議,又名網絡通訊協議。簡單來說:TCP控制傳輸數據,負責發(fā)現傳輸的問題,一旦有問題就發(fā)出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地,而IP是負責給因特網中的每一臺電腦定義一個地址,以便傳輸。TCP協議在許多分布式應用程序中進行消息命令傳遞是必不可少的部分。
TCP通信的三次握手:三次握手(Three-way Handshake),是指建立一個TCP連接時,需要客戶端和服務器總共發(fā)送3個包。
第一次握手:客戶端發(fā)送一個TCP的SYN標志位置1的包指明客戶打算連接的服務器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段里。第二次握手:服務器發(fā)回確認包(ACK)應答。即SYN標志位和ACK標志位均為1同時,將確認序號(Acknowledgement Number)設置為客戶的I S N加1以.即X+1。第三次握手:客戶端再次發(fā)送確認包(ACK) SYN標志位為0,ACK標志位為1.并且把服務器發(fā)來ACK的序號字段+1,放在確定字段中發(fā)送給對方.并且在數據段放寫ISN的+1先看下服務端Socket監(jiān)聽代碼:
using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;namespace SocketDome{ /// <summary> /// 處理Socket監(jiān)聽邏輯 /// </summary> public class SocketProvider { private static Socket serviceSocketListener; //Socke監(jiān)聽處理請求 /// <summary> /// 開啟Socket監(jiān)聽 /// </summary> public static void Init() { serviceSocketListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serviceSocketListener.Bind(new IPEndPoint(IPAddress.Parse("10.0.0.217"), 20000)); //IP和端口應該是可配置 serviceSocketListener.Listen(1024); Thread handleSocket = new Thread(new ThreadStart(HandleSocket)); handleSocket.Start(); } /// <summary> /// 監(jiān)聽鏈接 /// </summary> private static void HandleSocket() { while (true) { try { Socket currSocket = serviceSocketListener.Accept(); //為新建連接創(chuàng)建新的 System.Net.Sockets.Socket Thread processThread = new Thread(new ParameterizedThreadStart(ProcessSocket)); processThread.Start(currSocket); } catch { } } } /// <summary> /// 處理Socket信息 /// </summary> /// <param name="obj">新建連接創(chuàng)建新Socket對象</param> private static void ProcessSocket(object obj) { Socket currSocket = (Socket)obj; try { byte[] recvBytess = new byte[1048576]; int recbytes; recbytes = currSocket.Receive(recvBytess, recvBytess.Length, 0); if (recbytes > 0) { var contentStr = Encoding.UTF8.GetString(recvBytess, 0, recbytes); var _order = contentStr.Split('~'); byte[] sendPass = Encoding.UTF8.GetBytes(_order[0].ToUpper() + "#SUCCESS"); //先相應對話,然后去異步處理 currSocket.Send(sendPass, sendPass.Length, SocketFlags.None); switch (_order[0].ToUpper()) { case"ADDCACHE": Console.WriteLine("添加緩存消息" + _order[1]); //處理ADDCACHE邏輯 Console.WriteLine("寫Log日志"); break; default : Console.WriteLine("命令錯誤"); Console.WriteLine("寫Log日志"); break; } } } catch (Exception ex) { Console.WriteLine("寫Error日志" + ex.Message); } } }}這個服務端,監(jiān)聽著客戶端發(fā)來的命令,格式定義為:命令~參數,在服務端接受到客戶端的命令消息后立即回傳接到命令并開始處理,進行異步處理避免客戶端等待。
下面看下客戶端的Socket客戶端主動請求服務端代碼:
using System;using System.Collections.Generic;using System.Linq;using System.Net;using System.Net.Sockets;using System.Text;namespace Consoleapplication7{ /// <summary> /// Socket Helper /// </summary> public class SocketHelper { private string ip; private IPEndPoint ex; private Socket socket; public SocketHelper(string ip, int port) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this.ip = ip; this.ex = new IPEndPoint(IPAddress.Parse(ip), port); } /// <summary> /// Socket 進行連接 /// </summary> /// <returns>連接失敗OR成功</returns> public bool Socketlink() { try { socket.Connect(ex); return true; } catch (Exception ex) { return false; } } /// <summary> /// Socket 發(fā)送消息 /// </summary> /// <param name="strmsg">消息</param> public void SendVarMessage(string strmsg) { try { byte[] msg = System.Text.Encoding.UTF8.GetBytes(strmsg); this.socket.Send(msg); } catch (Exception ex) { this.socket.Close(); } } /// <summary> /// Socket 消息回傳 /// </summary> /// <returns></returns> public string ReceiveMessage() { try { byte[] msg = new byte[1048576]; int recv = socket.Receive(msg); this.socket.Close(); return System.Text.Encoding.UTF8.GetString(msg, 0, recv); } catch (Exception ex) { this.socket.Close(); return "ERROR"; } } }}using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleApplication7{ class Program { static void Main(string[] args) { SocketHelper socket = new SocketHelper("10.0.0.217",20000); if(socket.Socketlink()) { Console.WriteLine("連接成功"); socket.SendVarMessage("ADDCACHE~張三"); string strReposon = socket.ReceiveMessage(); Console.WriteLine(strReposon); } Console.Read(); } }}首先以管理園身份開啟服務端查詢,然后客戶端主動請求服務端進行消息請求。
例子三:
一、摘要
總結一下基于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 端口綁定和監(jiān)聽
同步套接字服務器主機的綁定和端口監(jiān)聽,Socket類的Bind(綁定主機),Listen(監(jiān)聽端口),Accept(接收客戶端的連接請求)。
(1) Bind
原型:
public void Bind(EndPoint LocalEP)參數為主機對象 IPEndPoint
(2) Listen
原型:
public void Listen(int backlog)參數整型數值,掛起隊列最大值
(3) accept
原型:
public socket accept()返回為套接字對象
3.5 socket的發(fā)送和接收方法
(1) 發(fā)送數據
a)socket類的send方法
原型一:
public int Send(byte[] buffer)參數:待發(fā)送的字節(jié)數組;
原型二:
public int Send(byte[],SocketFlags)SocketFlags成員列表:
DontRoute不使用路由表發(fā)送,
MaxIOVectorLength為發(fā)送和接收數據的wsabuf結構數量提供標準值,
None 不對次調用使用標志,
OutOfBand消息的部分發(fā)送或接收,
Partial消息的部分發(fā)送或接收,
Peek查看傳入的消息。
原型三:
public int Send(byte[],int,SocketFlags)參數二要發(fā)送的字節(jié)數
原型四:
public int Send(byte[],int,int,SocketFlags)參數二為Byte[]中開始發(fā)送的位置
b) NetWordStream類的Write方法
原型:
public override void write(byte[] buffer,int offset,int size)參數分別為:字節(jié)數組,開始字節(jié)位置,總字節(jié)數。
(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) 創(chuàng)建套接字;
(2) 綁定套接字到一個IP地址和一個端口上(bind());
(3)將套接字設置為監(jiān)聽模式等待連接請求(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); //控制臺輸出偵聽狀態(tài) Console.Write("Waiting for a client"); //一旦接受連接,創(chuàng)建一個客戶端 Socket client = socket.Accept(); //獲取客戶端的IP和端口 IPEndPoint ipEndClient = (IPEndPoint)client.RemoteEndPoint; //輸出客戶端的IP和端口 Console.Write("Connect with {0} at port {1}", ipEndClient.Address, ipEndClient.Port); //定義待發(fā)送字符 string welcome = "Welcome to my server"; //數據類型轉換 data = Encoding.ASCII.GetBytes(welcome); //發(fā)送 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)); //將接收到的數據再發(fā)送出去 client.Send(data, recv, SocketFlags.None); } Console.Write("Disconnect form{0}", ipEndClient.Address); client.Close(); socket.Close(); } }}4.2 客戶端編程的步驟:
(1) 創(chuàng)建套接字;
(2) 向服務器發(fā)出連接請求(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) { //定義發(fā)送數據緩存 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); //發(fā)送該數組 socket.Send(data, data.Length, SocketFlags.None); while (true) { // //如果字符串是"exit",退出while循環(huán) if (input == "exit") { break; } //對data清零 data = new byte[1024]; //定義接收到的數據的長度 recv = socket.Receive(data); //將接收到的數據轉換為字符串 stringData = Encoding.ASCII.GetString(data, 0, recv); //控制臺輸出字符串 Console.Write(stringData); //發(fā)送收到的數據 socket.Send(data, recv, 0); } Console.Write("disconnect from server"); socket.Shutdown(SocketShutdown.Both); socket.Close(); } }}上述代碼實現了,當連接建立之后,客戶端向服務器端發(fā)送鍵盤輸入的字符,服務器端收到字符后,顯示在控制臺并發(fā)送給客戶端,客戶端收到字符后,顯示在控制臺并再次發(fā)送給服務器端,如此循環(huán)。五、實驗結果
先后運行服務器端程序和客戶端程序,控制臺界面如下:
圖1 服務器端控制臺
當連接建立后,服務器端控制臺顯示等待客戶端的狀態(tài)"Waiting for a client",并打印出連接信息。
圖2 客戶端控制臺
當連接建立后,客戶端收到來自服務器端發(fā)送的字符串"Welcome to my server"。
之后,客戶端通過鍵盤發(fā)送數據,二者循環(huán)接收并發(fā)送,控制臺分別如下:
圖3 服務器控制臺
圖4 客戶端控制臺
六、幾點說明
6.1 傳輸速度
(1) 增大發(fā)送和接收的數組可提升傳輸速度,即增加一次實際發(fā)送數據的數量可以提高傳輸速度,但數組中數據的個數也不能一味的增大。需要說明的,由于地層MIT的限制,底層具體實現的時候每次發(fā)送的數據仍是不超過1510個的。
(2) 將控制臺界面最小化后,速度也會有翻倍的提升。
6.2 MFC的轉換
為了使傳輸協議更有可觀性和使用性,通常做成MFC的樣式,具體的使用已在基于TCP協議的網絡攝像頭的設計與實現應用。
|
新聞熱點
疑難解答