C#網路程式設計 tcpclient (阻塞非同步)
阿新 • • 發佈:2019-02-01
C# 高效的語言,既然選擇了它來寫網路伺服器,就應該使用最簡單的方式。
MS提供了SOCKET和TCPCLIENT兩種方式給我們使用,TCPCLIENT封裝了socket,似乎應該用tcpclient來搞。。。
關於阻塞:
該概念出現的場景是伺服器端,在接受客戶端連線的場景。我們在伺服器端用TcpListener開啟一個監聽,等待客戶端的連線,需要使用AcceptTcpClient()方法。
這個AcceptTcpClient()方法一旦使用,整個執行緒就停住(阻塞)了。 直到有客戶端連線上來後,AcceptTcpClient()方法才返回客戶端的連線。
關於非同步:
該概念出現在網路連線已經建立好後的場景,伺服器程式或客戶端程式,從連線的資料流裡面讀取或寫入資料,如果資料流中有資料才讀取,程式程式碼跟著執行就是同步,
或者不管有無資料,都去試做讀資料流中的資料,而使用回撥函式來處理這攤子事情,就是非同步。
基於TCPCLIENT,下面使用簡單的程式碼來實現一個阻塞,非同步的網路伺服器。
伺服器開始,使用一個執行緒來處理監聽操作:
Thread lsThread = new Thread(new ThreadStart(server_listen));
lsThread.IsBackground = true;
lsThread.Start();
////////////////// /////網路監聽//// ///////////////// private void server_listen() { listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 3000); listener.Start(); while (true) { Thread.Sleep(1000); while (listener.Pending()) //跳開阻塞,用這個在關閉伺服器程式時不會出現異常 { tcpuser one = new tcpuser(); one.tcp = listener.AcceptTcpClient(); Thread subThread = new Thread(new ParameterizedThreadStart(f1)); //執行緒 subThread.Start(one); gv.tcpuserlist.Add(one); } } }
關於listener.Pending,這個方法可以測試listener中有沒有客戶端連線上來,如果有,再去接收客戶端的連線。
這樣的寫法,就可以避免程式被直接使用AcceptTcpClient()方法阻塞住。
//多客戶端的處理執行緒 private void f1(object xxx) { tcpuser tp = (tcpuser)xxx; NetworkStream stream = tp.tcp.GetStream(); while (true) { var begint = DateTime.Now; rwl.EnterReadLock(); //無論花費多長時間,執行緒都會一直等到獲得鎖為止 try { //對話邏輯處理 client_business(tp); //網路寫 if (!String.IsNullOrWhiteSpace(tp.txtout)) { tp.writebuffer = Encoding.UTF8.GetBytes(tp.txtout + "."); //加一次訊息結束符號. if (tp.tcp != null && tp.tcp.Connected) { if (stream.CanWrite) { stream.BeginWrite(tp.writebuffer, 0, tp.writebuffer.Length, new AsyncCallback(WriteCallBack), tp); } } else { tp.islogon = 0; rwl.ExitReadLock(); break; } } //網路讀 if (tp.tcp != null && tp.tcp.Connected) { stream.BeginRead(tp.readbuffer, 0, tp.readbuffer.Length, new AsyncCallback(ReadCallBack), tp); } else { tp.islogon = 0; rwl.ExitReadLock(); break; } } catch { listBox2.Items.Insert(0, "對話模組 ERROR"); tp.islogon = 0; rwl.ExitReadLock(); break; } Thread.Sleep(100); rwl.ExitReadLock(); listBox1.Items.Insert(0, tp.playername + " client:" + Convert.ToInt32(DateTime.Now.Subtract(begint).TotalMilliseconds).ToString()); } }
上面的程式碼使用BeginRead來讀取流中的資料,BeginRead方法是一個回撥函式,WIN系統會處理函式中的程式碼,並且它自己結束時候,又呼叫了自己,就形成了一個迴圈,可以不停的處理資料。
下面是BeginRead函式的程式碼內容:
//網路讀子函式
public void ReadCallBack(IAsyncResult ar)
{
try
{
tcpuser one = (tcpuser)ar.AsyncState;
if (one.tcp != null && one.tcp.Connected)
{
NetworkStream stream = (NetworkStream)one.tcp.GetStream();
int numberOfBytesRead = stream.EndRead(ar);
one.txtin = Encoding.UTF8.GetString(one.readbuffer, 0, numberOfBytesRead);
if (!String.IsNullOrWhiteSpace(one.txtin))
{ one.txtlonglong = one.txtlonglong + one.txtin; one.txtin = ""; } //每次收到的,都放longlong
}
}
catch { listBox2.Items.Insert(0, "ReadCallBack ERROR"); }
}
//網路寫子函式
public void WriteCallBack(IAsyncResult ar)
{
try
{
tcpuser one = (tcpuser)ar.AsyncState;
if (one.tcp != null && one.tcp.Connected)
{
NetworkStream stream = (NetworkStream)one.tcp.GetStream();
stream.EndWrite(ar);
one.txtout = "";
}
}
catch { listBox2.Items.Insert(0, "WriteCallBack ERROR"); }
}