c#網路程式設計初探

iDotNetSpace發表於2010-01-08
我們知道C#和C++的差異之一,就是他本身沒有類庫,所使用的類庫是.Net框架中的類庫--.Net FrameWork SDK。在.Net FrameWork SDK中為網路程式設計提供了二個名稱空間:"System.Net"和"System.Net.Sockets"。C#就是通過這二個名稱空間中封裝的類和 方法實現網路通訊的。

首先我們解釋一下在網路程式設計時候,經常遇到的幾個概念:同步(synchronous)、非同步(asynchronous)、阻塞(Block)和非阻塞(Unblock):

所謂同步方式,就是傳送方傳送資料包以後,不等接受方響應,就接著傳送下一個資料包。非同步方式就是當傳送方傳送一個資料包以後,一直等到接受方響應後,才接著傳送下一個資料包。而阻塞套接字是指執行此套接字的網路呼叫時,直到呼叫成功才返回,否則此套節字就一直阻塞在網路呼叫上,比如呼叫 StreamReader 類的Readlin ( )方法讀取網路緩衝區中的資料,如果呼叫的時候沒有資料到達,那麼此Readlin ( )方法將一直掛在呼叫上,直到讀到一些資料,此函式呼叫才返回;而非阻塞套接字是指在執行此套接字的網路呼叫時,不管是否執行成功,都立即返回。同樣呼叫 StreamReader 類的Readlin ( )方法讀取網路緩衝區中資料,不管是否讀到資料都立即返回,而不會一直掛在此函式呼叫上。在Windows網路通訊軟體開發中,最為常用的方法就是非同步非阻塞套接字。平常所說的C/S(客戶端/伺服器)結構的軟體採用的方式就是非同步非阻塞模式的。

其實在用C#進行網路程式設計中,我們並不需要了解什麼同步、非同步、阻塞和非阻塞的原理和工作機制,因為在.Net FrameWrok SDK中已經已經把這些機制給封裝好了。下面我們就用C#開一個具體的網路程式來說明一下問題。

一.本文中介紹的程式設計及執行環境
(1).微軟視窗2000 伺服器版
(2)..Net Framework SDK Beta 2以上版本
二.伺服器端程式設計的關鍵步驟以及解決辦法:
在下面接受的程式中,我們採用的是非同步阻塞的方式。
(1).首先要要在給定的埠上面建立一個"tcpListener"物件偵聽網路上面的請求。當接收到連結請求後通過呼叫 "tcpListener"物件的"AcceptSocket"方法產生一個用於處理接入連線請求的Socket的例項。下面是具體實現程式碼:

//建立一個tcpListener物件,此物件主要是對給定埠進行偵聽
tcpListener = new TcpListener ( 1234 ) ;
//開始偵聽
tcpListener.Start ( ) ;
//返回可以用以處理連線的Socket例項
socketForClient = tcpListener.AcceptSocket ( ) ;

(2).接受和傳送客戶端資料:

此時Socket例項已經產生,如果網路上有請求,在請求通過以後,Socket例項構造一個"NetworkStream"物件, "NetworkStream"物件為網路訪問提供了基礎資料流。我們通過名稱空間"System.IO"中封裝的二個類"StreamReader"和 "StreamWriter"來實現對"NetworkStream" 物件的訪問。其中"StreamReader"類中的ReadLine ( )方法就是從"NetworkStream"物件中讀取一行字元;"StreamWriter"類中的WriteLine ( )方法就是對"NetworkStream"物件中寫入一行字串。從而實現在網路上面傳輸字串,下面是具體的實現程式碼:

try
{
//如果返回值是"true",則產生的套節字已經接受來自遠方的連線請求
if ( socketForClient.Connected )
{
ListBox1.Items.Add ( "已經和客戶端成功連線!" ) ;
while ( true )
{
//建立networkStream物件通過網路套節字來接受和傳送資料
networkStream = new NetworkStream ( socketForClient ) ;
//從當前資料流中讀取一行字元,返回值是字串
streamReader = new StreamReader ( networkStream ) ;
string msg = streamReader.ReadLine ( ) ;
ListBox1.Items.Add ( "收到客戶端資訊:" + msg ) ;
streamWriter = new StreamWriter ( networkStream ) ;
if ( textBox1.Text != "" )
{
ListBox1.Items.Add ( "往客戶端反饋資訊:" + textBox1.Text ) ;
//往當前的資料流中寫入一行字串
streamWriter.WriteLine ( textBox1.Text ) ;
//重新整理當前資料流中的資料
streamWriter.Flush ( ) ;
}
}
}
}
catch ( Exception ey )
{
MessageBox.Show ( ey.ToString ( ) ) ;
}


(3).最後別忘了要關閉所以流,停止偵聽網路,關閉套節字,具體如下:

//關閉執行緒和流
networkStream.Close ( ) ;
streamReader.Close ( ) ;
streamWriter.Close ( ) ;
_thread1.Abort ( ) ;
tcpListener.Stop ( ) ;
socketForClient.Shutdown ( SocketShutdown.Both ) ;
socketForClient.Close ( ) ;

三.C#網路程式設計伺服器端程式的部分原始碼(server.cs):

由於在此次程式中我們採用的結構是非同步阻塞方式,所以在實際的程式中,為了不影響伺服器端程式的執行速度,我們在程式中設計了一個執行緒,使得對網路請求偵聽,接受和傳送資料都線上程中處理,請在下面的程式碼中注意這一點,下面是server.cs的完整程式碼:

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.Net.Sockets ;
using System.IO ;
using System.Threading ;
using System.Net ;
//匯入程式中使用到的名字空間
public class Form1 : Form
{
private ListBox ListBox1 ;
private Button button2 ;
private Label label1 ;
private TextBox textBox1 ;
private Button button1 ;
private Socket socketForClient ;
private NetworkStream networkStream ;
private TcpListener tcpListener ;
private StreamWriter streamWriter ;
private StreamReader streamReader ;
private Thread _thread1 ;
private System.ComponentModel.Container components = null ;
public Form1 ( )
{
InitializeComponent ( ) ;
}
//清除程式中使用的各種資源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose ( disposing ) ;
}
private void InitializeComponent ( )
{
label1 = new Label ( ) ;
button2 = new Button ( ) ;
button1 = new Button ( ) ;
ListBox1 = new ListBox ( ) ;
textBox1 = new TextBox ( ) ;
SuspendLayout ( ) ;
label1.Location = new Point ( 8 , 168 ) ;
label1.Name = "label1" ;
label1.Size = new Size ( 120 , 23 ) ;
label1.TabIndex = 3 ;
label1.Text = "往客戶端反饋資訊:" ;
//同樣的方式設定其他控制元件,這裡略去

this.Controls.Add ( button1 ) ;
this.Controls.Add ( textBox1 ) ;
this.Controls.Add ( label1 ) ;
this.Controls.Add ( button2 ) ;
this.Controls.Add ( ListBox1 ) ;
this.MaximizeBox = false ;
this.MinimizeBox = false ;
this.Name = "Form1" ;
this.Text = "C#的網路程式設計伺服器端!" ;
this.Closed += new System.EventHandler ( this.Form1_Closed ) ;
this.ResumeLayout ( false ) ;

}
private void Listen ( )
{
//建立一個tcpListener物件,此物件主要是對給定埠進行偵聽
tcpListener = new TcpListener ( 1234 ) ;
//開始偵聽
tcpListener.Start ( ) ;
//返回可以用以處理連線的Socket例項
socketForClient = tcpListener.AcceptSocket ( ) ;
try
{
//如果返回值是"true",則產生的套節字已經接受來自遠方的連線請求
if ( socketForClient.Connected )
{
ListBox1.Items.Add ( "已經和客戶端成功連線!" ) ;
while ( true )
{
//建立networkStream物件通過網路套節字來接受和傳送資料
networkStream = new NetworkStream ( socketForClient ) ;
//從當前資料流中讀取一行字元,返回值是字串
streamReader = new StreamReader ( networkStream ) ;
string msg = streamReader.ReadLine ( ) ;
ListBox1.Items.Add ( "收到客戶端資訊:" + msg ) ;
streamWriter = new StreamWriter ( networkStream ) ;
if ( textBox1.Text != "" )
{
ListBox1.Items.Add ( "往客戶端反饋資訊:" + textBox1.Text ) ;
//往當前的資料流中寫入一行字串
streamWriter.WriteLine ( textBox1.Text ) ;
//重新整理當前資料流中的資料
streamWriter.Flush ( ) ;
}
}
}
}
catch ( Exception ey )
{
MessageBox.Show ( ey.ToString ( ) ) ;
}
}
static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}

private void button1_Click ( object sender , System.EventArgs e )
{
ListBox1.Items .Add ( "服務已經啟動!" ) ;
_thread1 = new Thread ( new ThreadStart ( Listen ) ) ;
_thread1.Start ( ) ;

}

private void button2_Click ( object sender , System.EventArgs e )
{
//關閉執行緒和流
networkStream.Close ( ) ;
streamReader.Close ( ) ;
streamWriter.Close ( ) ;
_thread1.Abort ( ) ;
tcpListener.Stop ( ) ;
socketForClient.Shutdown ( SocketShutdown.Both ) ;
socketForClient.Close ( ) ;
}
private void Form1_Closed ( object sender , System.EventArgs e )
{
//關閉執行緒和流
networkStream.Close ( ) ;
streamReader.Close ( ) ;
streamWriter.Close ( ) ;
_thread1.Abort ( ) ;
tcpListener.Stop ( ) ;
socketForClient.Shutdown ( SocketShutdown.Both ) ;
socketForClient.Close ( ) ;
}
}

四.客戶端程式設計的關鍵步驟以及解決辦法: 

(1).連線到伺服器端的指定埠:

我們採用的本地機既做伺服器也做客戶機,你可以通過修改IP地址來確定自己想要連線的伺服器。我們在連線的時候採用了"TcpClient"類,此類是在較高的抽象級別(高於Socket類)上面提供TCP服務。下面程式碼就是連線到本地機(埠為1234),並獲取響應流:

//連線到伺服器埠,在這裡是選用本地機器作為伺服器,你可以通過修改IP地址來改變伺服器
try
{
myclient = new TcpClient ( "localhost" , 1234 ) ;
}
catch
{
MessageBox.Show ( "沒有連線到伺服器!" ) ;
return ;
}
//建立networkStream物件通過網路套節字來接受和傳送資料
networkStream = myclient.GetStream ( ) ;
streamReader = new StreamReader ( networkStream ) ;
streamWriter = new StreamWriter ( networkStream ) ;

(2).實現接受和傳送資料:

在接受和傳送資料上面,我們依然採用了"NetworkStream"類,因為對他進行操作比較簡單,具體實現傳送和接受還是通過名稱空間 "System.IO"中"StreamReader"類ReadLine ( )方法和"StreamWriter"類的WriteLine ( )方法。具體的實現方法如下:

if ( textBox1.Text == "" )
{
MessageBox.Show ( "請確定文字框為非空!" ) ;
textBox1.Focus ( ) ;
return ;
}
try
{
string s ;
//往當前的資料流中寫入一行字串
streamWriter.WriteLine ( textBox1.Text ) ;
//重新整理當前資料流中的資料
streamWriter.Flush ( ) ;
//從當前資料流中讀取一行字元,返回值是字串
s = streamReader.ReadLine ( ) ;
ListBox1.Items.Add ( "讀取伺服器端傳送內容:" + s ) ;
}
catch ( Exception ee )
{
MessageBox.Show ( "從伺服器端讀取資料出現錯誤,型別為:" + ee.ToString ( ) ) ;
}


(3).最後一步和伺服器端是一樣的,就是要關閉程式中建立的流,具體如下:

streamReader.Close ( ) ;
streamWriter.Close ( ) ;
networkStream.Close ( ) ;

五.客戶端的部分程式碼:

由於在客戶端不需要偵聽網路,所以在呼叫上面沒有程式阻塞情況,所以在下面的程式碼中,我們沒有使用到執行緒,這是和伺服器端程式的一個區別的地方。總結上面的這些關鍵步驟,可以得到一個用C#網路程式設計 完整的客戶端程式(client.cs),具體如下:

using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using System.Net.Sockets ;
using System.IO ;
using System.Threading ;
//匯入程式中使用到的名字空間
public class Form1 : Form
{
private ListBox ListBox1 ;
private Label label1 ;
private TextBox textBox1 ;
private Button button3 ;
private NetworkStream networkStream ;
private StreamReader streamReader ;
private StreamWriter streamWriter ;
TcpClient myclient ;
private Label label2 ;

private System.ComponentModel.Container components = null ;

public Form1 ( )
{
InitializeComponent ( ) ;
}
//清除程式中使用的各種資源
protected override void Dispose ( bool disposing )
{
if ( disposing )
{
if ( components != null )
{
components.Dispose ( ) ;
}
}
base.Dispose ( disposing ) ;
}
private void InitializeComponent ( )
{
label1 = new Label ( ) ;
button3 = new Button ( ) ;
ListBox1 = new ListBox ( ) ;
textBox1 = new TextBox ( ) ;
label2 = new Label ( ) ;
SuspendLayout ( ) ;
label1.Location = new Point ( 8 , 168 ) ;
label1.Name = "label1" ;
label1.Size = new Size ( 56 , 23 ) ;
label1.TabIndex = 3 ;
label1.Text = "資訊:" ;
//同樣方法設定其他控制元件
AutoScaleBaseSize = new Size ( 6 , 14 ) ;
ClientSize = new Size ( 424 , 205 ) ;
this.Controls.Add ( button3 ) ;
this.Controls.Add ( textBox1 ) ;
this.Controls.Add ( label1 ) ;
this.Controls.Add ( label2 ) ;
this.Controls.Add ( ListBox1 ) ;
this.MaximizeBox = false ;
this.MinimizeBox = false ;
this.Name = "Form1" ;
this.Text = "C#的網路程式設計客戶器端!" ;
this.Closed += new System.EventHandler ( this.Form1_Closed ) ;
this.ResumeLayout ( false ) ;
//連線到伺服器埠,在這裡是選用本地機器作為伺服器,你可以通過修改IP地址來改變伺服器
try
{
myclient = new TcpClient ( "localhost" , 1234 ) ;
}
catch
{
MessageBox.Show ( "沒有連線到伺服器!" ) ;
return ;
}
//建立networkStream物件通過網路套節字來接受和傳送資料
networkStream = myclient.GetStream ( ) ;
streamReader = new StreamReader ( networkStream ) ;
streamWriter = new StreamWriter ( networkStream ) ;
}
static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}

private void button3_Click ( object sender , System.EventArgs e )
{

if ( textBox1.Text == "" )
{
MessageBox.Show ( "請確定文字框為非空!" ) ;
textBox1.Focus ( ) ;
return ;
}
try
{
string s ;
//往當前的資料流中寫入一行字串
streamWriter.WriteLine ( textBox1.Text ) ;
//重新整理當前資料流中的資料
streamWriter.Flush ( ) ;
//從當前資料流中讀取一行字元,返回值是字串
s = streamReader.ReadLine ( ) ;
ListBox1.Items.Add ( "讀取伺服器端傳送內容:" + s ) ;
}
catch ( Exception ee )
{
MessageBox.Show ( "從伺服器端讀取資料出現錯誤,型別為:" + ee.ToString ( ) ) ;
}
}

private void Form1_Closed ( object sender , System.EventArgs e )
{
streamReader.Close ( ) ;
streamWriter.Close ( ) ;
networkStream.Close ( ) ;

}
}

下圖是編譯上面二個程式後執行的介面:


圖01:C#編寫網路程式執行介面


七.總結:

雖然在.Net FrameWrok SDK 中只為網路程式設計提供了二個名稱空間,但這二個名稱空間中的內容卻是十分豐富的,C#利用這二個名稱空間既可以實現同步和非同步,也可以實現阻塞和非阻塞。本 文通過用C#編寫一個網路上資訊傳輸的程式,展現了其豐富的內容,由於篇幅所限,更深,更強大的功能還需要讀者去實踐、探索。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-624692/,如需轉載,請註明出處,否則將追究法律責任。

相關文章