基於C#的網絡通信(Socket編程,遊戲開發中的通信),含同步和異步兩種方式

在開始前先明確幾個基本概念:

1、什麼是網絡編程(通信)?

回答:   這個問題的答案非常簡單,就是要實現客戶端和服務器端數據的交互(簡單說就是用戶和服務器的通信),而實現這一通信的手段呢比較常用的就是Socket套接字來實現的。

2、Socket實現通信的基本流程?

回答:其實又可以劃分爲比較常見的TCP和UDP兩種,本文主要以TCP爲例進行介紹,UDP的方式也是類似的,只是創建Socket和發送接收函數略有不同。基本的流程受篇幅限制,大家可以跳轉到之前的一篇博文查看:構建TCP套接字(socket)的概念及具體步驟

3、什麼是同步和異步?

回答:這裏的同步和異步其實並非是指消息是否能同時到達,而是通信模式的不同,舉一個形象的例子:大學體測1000米跑,如果體育老師讓全班一個一個跑就是一種同步方式(一個學生跑完下一個學生再跑)。而我們更多采用的是每個學生給一個編號,起點處每隔一定時間起跑一個(實際上就是一定時間會發出一條消息),而在終點處有人專門記錄每個人的時間就可以了(其實就是接收消息),這個過程其實就是異步。

 同步異步在網絡編程中更多指的是通信模式:

通信的同步,主要是指客戶端在發送請求後,必須得在服務端有迴應後才發送下一個請求。所以這個時候的所有請求將會在服務端得到同步 。

通信的異步,指客戶端在發送請求後,不必等待服務端的迴應就可以發送下一個請求,這樣對於所有的請求動作來說將會在服務端得到異步,這條請求的鏈路就象是一個請求隊列,所有的動作在這裏不會得到同步的。

4、什麼是同步阻塞,什麼是異步非阻塞?

這裏還是需要先明確兩個概念,什麼是阻塞和非阻塞,其實很好理解,阻塞其實你打電話找一個人,對方不在,你就一直通話等着,很顯然,上面的例子中的同步模式顯然是阻塞的(one by one)。而非阻塞模式就是,我打電話找一個人,對方不在,你就說我一會再打過來看,很顯然,上面的大家同時跑的方式是異步非阻塞的。

而阻塞和非阻塞只是應用在請求的讀取和發送。 

5、客戶端和服務器端同步or異步有什麼要求麼?

這個是沒有必然要求的,只是不同的方式的效率不同而已,顯然客戶端和服務器都是同步模式的話那麼效率是最低,而客戶端和服務器端都是異步模式的話效率是最高的。也可以客戶端和服務器端任意一方是同步的而另一方是異步的。

6、異步效率更高,有缺點麼?

還是舉上面跑步的例子,雖然把很多同學同時安排在跑到上跑步了,但是實際上大家跑步的速度是不一樣的,可能會先出發後到達,這個時候還需要去記錄和核對學生的姓名,很顯然這樣的過程非常複雜,這也是異步方式的一個缺點。那有沒有又不復雜,效率又高的方法呢?有,一人一個秒錶,跑的時候計時,到終點給老師看,顯然這樣不會出現計時上的錯誤,效率也很高,但是資源消耗大(需要一人一個表),其實這種方式就是多線程同步模式。

有了上面的例子之後呢,就開始上代碼了,主要是兩個例子:

1、爲了演示方便,我們兩個例子的客戶端都是一樣的功能,定時向服務器發送數據

2、兩個例子的服務器端分別是同步的和異步的

首先先放上客戶端的代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Threading;

class client
{
    //聲明socket
    Socket socket;
    //聲明一個地址類
    IPEndPoint iep;
    public void connect()
    {
        try
        {
            //設定Socket的模式
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //指定服務端的地址和端口
            iep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 30000);
            //建立連接
            socket.Connect(iep);
            Console.WriteLine("服務器連接成功");
            
			//定義向服務器發送的數據
            string content = "你好,我是客戶端!";
            //將字符串轉換成字符數組
            byte[] bytes_string = Encoding.UTF8.GetBytes(content);
            
            while(true)
            {
				//定時向服務器發送數據
                Thread.Sleep(1000);
                socket.Send(bytes_string);
                Console.WriteLine("向服務器發送了:" + bytes_string.Length + "個字節");
            }
        }
        catch(Exception e)
        {
			//捕獲異常
            Console.WriteLine("服務器連接失敗");
            Console.WriteLine(e.ToString());
        }
    }
}

然後就是同步模式的服務器端代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;

public class demo
{
    //需要添加System.Net.Sockets的支持
    Socket socket;
    private string ip;
    private int port;
    //定義每次接收的最大字節數和接收數組
    private int maxBuffer = 1024;
    private byte[] buffer;
    //定義一個套接字代表客戶端
    private Socket client;
    public demo(string _ip,int _port)
    {
        this.ip = _ip;
        this.port = _port;
    }
    //服務器連接函數
    public void StartConnect()
    {
        try
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ip), port);
            //綁定地址
            socket.Bind(iep);
            //設置客戶端連接數量
            socket.Listen(1000);
            Console.WriteLine("服務器開啓成功!");

            //監聽客戶端連接
            Console.WriteLine("監控客戶端連接!");
            client = socket.Accept();

            Console.WriteLine("有客戶端連接了!");
            Receive();
        }
        catch(Exception e)
        {
			//捕獲異常
            Console.WriteLine("服務器開啓失敗!");
            Console.WriteLine(e.ToString());
        }
        
    }
    //接收客戶端信息
    public void Receive()
    {
        //初始化接收的數組
        buffer = new byte[maxBuffer];
        try
        {
            //阻塞式同步接收
            while (true)
            {
                int length = client.Receive(buffer);   //同步模式的核心部分!
                string content = Encoding.UTF8.GetString(buffer, 0, length);
                Console.WriteLine("收到了客戶端發來的信息:"+content+" 字節的長度爲:" + length); 
            } 
            
        }
        catch (Exception e)
        {
            Console.WriteLine("接收異常!");
            Console.WriteLine(e.ToString());
        }
    }
}

同步的服務器端的核心其實就是接收方式,可以看得到這裏用的接收函數時C#中封裝的Socket函數,這個函數的方法感興趣的同學可以在C#的官方文檔中具體查看,這裏不再贅述了:

接下來實現異步的服務器模式的代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;

public class demo
{
    //需要添加System.Net.Sockets的支持
    Socket socket;
    private string ip;
    private int port;
    //定義每次接收的最大字節數和接收數組
    private int maxBuffer = 1024;
    private byte[] buffer;
    //定義一個套接字代表客戶端
    private Socket client;
    public demo(string _ip,int _port)
    {
        this.ip = _ip;
        this.port = _port;
    }
    //服務器連接函數
    public void StartConnect()
    {
        try
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ip), port);
            //綁定地址
            socket.Bind(iep);
            //設置客戶端連接數量
            socket.Listen(1000);
            Console.WriteLine("服務器開啓成功!");

            //監聽客戶端連接
            Console.WriteLine("監控客戶端連接!");
            client = socket.Accept();

            Console.WriteLine("有客戶端連接了!");
            Receive();
        }
        catch(Exception e)
        {
            Console.WriteLine("服務器開啓失敗!");
            //顯示出異常詳情
            Console.WriteLine(e.ToString());
        }
        
    }
    //接收客戶端信息
    public void Receive()
    {
        //初始化接收的數組
        buffer = new byte[maxBuffer];
        try
        {
            //異步接收,接收的數據放到回調函數BegRece裏面去處理,無論客戶端是否發送消息都會執行這一步
            client.BeginReceive(buffer, 0, 1024, 0,new AsyncCallback(BegRece),client);    //異步的核心
        }
        catch (Exception e)
        {
            Console.WriteLine("接收異常!");
            Console.WriteLine(e.ToString());
        }
    }
//異步回調函數
    private void BegRece(IAsyncResult ar)
    {
        Socket so = (Socket)ar.AsyncState;
        //接收字節的數據長度
        int length = so.EndReceive(ar);
        string content = Encoding.UTF8.GetString(buffer,0,length);
        Console.WriteLine("收到了客戶端發來的消息:" + content);
        //爲了實現不停地接收數據,再次調用並對buffer重新賦值即可
        buffer = new byte[maxBuffer];
        client.BeginReceive(buffer, 0, 1024, 0, new AsyncCallback(BegRece), client);

    }
}

 可以發現異步和同步的不同有兩點,一個是接收函數不同,異步方法採用的是BeginReceive(),這個函數的詳細也可以在文檔中查看:

 而關於回調函數的說明也可以在文檔中查看,回調函數的本質可以理解爲BeginReceive()完成接收,回調函數完成處理。

最後來看一下程序的運行結果:

源程序代碼點擊這裏下載(VS2013編譯):點擊下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章