在開始前先明確幾個基本概念:
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編譯):點擊下載