目錄
異步原理
套接字編程原理:延續文件作用思想,打開-讀寫-關閉的模式。
C/S編程模式如下:
Ø 服務器端:
打開通信通道,告訴本地機器,願意在該通道上接受客戶請求——監聽,等待客戶請求——接受請求,創建專用鏈接進行讀寫——處理完畢,關閉專用鏈接——關閉通信通道(當然其中監聽到關閉專用鏈接可以重復循環)
Ø 客戶端:打開通信通道,連接服務器——數據交互——關閉信道。
Socket通信方式:
Ø 同步:客戶端在發送請求之后必須等到服務器回應之后才可以發送下一條請求。串行運行
Ø 異步:客戶端請求之后,不必等到服務器回應之后就可以發送下一條請求。並行運行
套接字模式:
Ø 阻塞:執行此套接字調用時,所有調用函數只有在得到返回結果之后才會返回。在調用結果返回之前,當前進程會被掛起。即此套接字一直被阻塞在網絡調用上。
Ø 非阻塞:執行此套接字調用時,調用函數即使得不到得到返回結果也會返回。
套接字工作步驟:
Ø 服務器監聽:監聽時服務器端套接字並不定位具體客戶端套接字,而是處於等待鏈接的狀態,實時監控網絡狀態
Ø 客戶端鏈接:客戶端發出鏈接請求,要連接的目標是服務器端的套接字。為此客戶端套接字必須描述服務器端套接字的服務器地址與端口號。
Ø 鏈接確認:是指服務器端套接字監聽到客戶端套接字的鏈接請求時,它響應客戶端鏈接請求,建立一個新的線程,把服務器端套接字的描述發送給客戶端,一旦客戶端確認此描述,則鏈接建立好。而服務器端的套接字繼續處於監聽狀態,繼續接受其他客戶端套接字請求。
主要方法
源碼
Server源碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class Conn
{ //定義數據最大長度
public const int data = 1024;
//Socket
public Socket socket;
//是否使用
public bool isUse = false;
//Buff
public byte[] readBuff = new byte[data];
public int buffCount = 0;
//構造函數
public Conn()
{
readBuff = new byte[data];
}
//初始化
public void Init(Socket socket)
{
this.socket = socket;
isUse = true;
buffCount = 0;
}
//緩沖區剩余的字節數
public int BuffRemain()
{
return data - buffCount;
}
//獲取客戶端地址
public string GetAdress()
{
if (!isUse)
return "無法獲取地址";
return socket.RemoteEndPoint.ToString();
}
//關閉
public void Close()
{
if (!isUse)
return;
Console.WriteLine("[斷開鏈接]" + GetAdress());
socket.Close();
isUse = false;
}
}
public class Program
{
/// <summary>
/// 創建多個Conn管理客戶端的連接
/// </summary>
public static Conn[] conns;
/// <summary>
/// 最大連接數
/// </summary>
public static int maxConn = 50;
/// <summary>
/// 將Socket定義為全局變量
/// </summary>
private static Socket serverSocket;
/// <summary>
/// 獲取鏈接池索引,返回負數表示獲取失敗
/// </summary>
/// <returns></returns>
public static int NewIndex()
{
if (conns == null)
return -1;
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
{
conns[i] = new Conn();
return i;
}
else if (conns[i].isUse == false)
{
return i;
}
}
return -1;
}
static void Main(string[] args)
{
//創建多個鏈接池,表示創建maxConn最大客戶端
conns = new Conn[maxConn];
for (int i = 0; i < maxConn; i++)
{
conns[i] = new Conn();
}
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
serverSocket.Bind(ipEndPoint);//綁定IP和端口號
serverSocket.Listen(maxConn);//開始監聽端口,0為監聽無限個客戶端
Console.WriteLine("[服務器]啟動成功");
//開始調用異步連接
serverSocket.BeginAccept(AcceptCb, null);
//按下quit退出程序
while (true)
{
if (Console.ReadLine() == "quit") return;
}
}
/// <summary>
/// Accept回調
/// </summary>
/// <param name="ar"></param>
static void AcceptCb(IAsyncResult ar)
{
try
{
Socket socket = serverSocket.EndAccept(ar);//嘗試進行異步連接
int index = NewIndex();
if (index < 0)
{
socket.Close();
Console.Write("[警告]鏈接已滿");
}
else
{
Conn conn = conns[index];
conn.Init(socket);
string adr = conn.GetAdress();
Console.WriteLine("客戶端連接 [" + adr + "] conn池ID:" + index);
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(), SocketFlags.None, ReceiveCb, conn);
}
serverSocket.BeginAccept(AcceptCb, null);
}
catch (Exception e)
{
Console.WriteLine("AcceptCb失敗:" + e.Message);
}
}
/// <summary>
/// 接收回調
/// </summary>
/// <param name="ar"></param>
static void ReceiveCb(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
try
{
int count = conn.socket.EndReceive(ar);
//關閉信號
if (count <= 0)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 斷開鏈接");
conn.Close();
return;
}
//數據處理
string str = Encoding.UTF8.GetString(conn.readBuff, 0, count);
Console.WriteLine("收到 [" + conn.GetAdress() + "] 數據:" + str);
str = conn.GetAdress() + "發送的:" + str;
byte[] bytes = System.Text.Encoding.UTF8.GetBytes("接收到" + str);
//廣播
/*
for (int i = 0; i < conns.Length; i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("將消息轉播給 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}*/
//點播
for (int i = 0; i<=0;i++)
{
if (conns[i] == null)
continue;
if (!conns[i].isUse)
continue;
Console.WriteLine("將消息轉播給 " + conns[i].GetAdress());
conns[i].socket.Send(bytes);
}
//繼續接收
conn.socket.BeginReceive(conn.readBuff, conn.buffCount, conn.BuffRemain(),SocketFlags.None, ReceiveCb, conn);
}
catch (Exception e)
{
Console.WriteLine("收到 [" + conn.GetAdress() + "] 斷開鏈接");
conn.Close();
}
}
}
}
Client源碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public class Program
{
private static int datacount = 1024;//設置數組數據長度
private static Socket socket;
private static byte[] dataBuff = new byte[datacount];
private static string recvStr;
static void Main(string[] args)
{
//獲取到Socket協議
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//綁定IP與端口
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
socket.Connect(ipEndPoint);//與服務端進行連接
Console.WriteLine("客戶端地址 " + socket.LocalEndPoint.ToString());//獲取客戶端地址
//開啟異步接收模式
socket.BeginReceive(dataBuff, 0, datacount, SocketFlags.None, ReceiveCb, null);
socket.Send(Encoding.UTF8.GetBytes("123")); //封裝好的一個Send方法,能夠在方法中操作,比如說:釋放資源
Send(socket, "456"); //簡單的發送
while (true)
{
//向服務器發送數據
byte[] data = Encoding.UTF8.GetBytes(Console.ReadLine());//輸入字符
//按下回車鍵發送數據
ConsoleKey inpt = Console.ReadKey().Key;
if (inpt == ConsoleKey.Enter)
{
socket.Send(data);
Console.WriteLine("發送成功");
}
}
}
private static void Send(Socket handler, String data)
{
byte[] byteData = Encoding.ASCII.GetBytes(data);
handler.BeginSend(byteData, 0, byteData.Length, 0,
new AsyncCallback(SendCallback), handler);
}
private static void SendCallback(IAsyncResult ar)
{
try
{
Socket handler = (Socket)ar.AsyncState;
int bytesSent = handler.EndSend(ar);
Console.WriteLine("Sent {0} bytes to client.", bytesSent);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
private static void ReceiveCb(IAsyncResult ar)
{
//接收數據的長度
//數據處理
//將dataBuffer數組轉碼成字符的形式輸出
//dataBuffer:需要轉碼的數組
//0:從數組的長度,0開始讀取。
//count :讀取數組的最大長度
int count = socket.EndReceive(ar);
if(count>0) //當接受數據長度大於0時處理
{
string str = System.Text.Encoding.UTF8.GetString(dataBuff, 0, count);
//獲取服務器端的數據
Console.WriteLine("獲取服務端的數據:" + str);
//繼續接收
socket.BeginReceive(dataBuff, 0, datacount, SocketFlags.None, ReceiveCb, null);
}
}
}
}
實驗效果(廣播為例)
參考博客
C# Socket編程 同步及異步通信:https://blog.csdn.net/bemodesty/article/details/84396658
C# Socket之異步連接(一):https://blog.csdn.net/u010511043/article/details/86435701
C# Socket之異步連接(二):https://blog.csdn.net/u010511043/article/details/86437192
C# Socket之客戶端異步連接:https://blog.csdn.net/u010511043/article/details/86441724