C#網絡程序設計(1)網絡編程常識與C#常用特性
網絡程序設計能夠幫我們了解聯網應用的底層通信原理!
(1)網絡編程常識:
1)什么是網絡編程
只有主要實現進程(線程)相互通信和基本的網絡應用原理性(協議)功能的程序,才能算是真正的網絡編程。
2)網絡編程的層次
現實中的互聯網是按照"TCP/IP分層協議棧"的體系結構構建的,因此程序員必須搞清楚自己要做的是哪個層次上的編程工作。
TCP/IP協議體系的實現情況:

其中,網絡接口層已經被大多數計算機生產廠家集成在了主板上,也就是經常所說的網卡(NIC)。windows操作系統內核中就集成了TCP/IP協議的實現。TCP/IP協議的核心部分是傳輸層協議(TCP/UDP)、網際層協議(IP)。應用程序通過編程界面(即程序員界面)與內核打交道。
3)編程界面的兩種形式
一種室內和直接提供的系統調用,在windows下表現為Windows API函數;另一種是以程序庫的方式提供的各種函數和類。MFC就是微軟用C++語言對windows API進行面向對象封裝后形成的功能強大的類庫。前者在核內實現,后者在核外實現。TCP/IP網絡環境下的應用程序是通過網絡應用編程界面(套接字Socket)實現的。用VC一般是使用MFC封裝好的Socket類,而在C#和.NET中可以實現兩種編程界面。
(2)網絡程序的工作機制:
網絡程序與傳統單機程序的區別在於它能夠與網絡上其他計算機(主機)中的程序互通信息。
1)Socket基本介紹:
網絡上計算機之間的通信實際上是計算機上進程之間的通信。為了標志通信的進程,首先要標志進程所在的主機,其次要標志主機上不同的進程。 在互聯網上使用IP地址來標識不同的主機,在網絡協議中使用端口號來識別不同的進程。為了唯一地標志網絡中的一個進程要使用如下的二元組:
(IP地址,端口號)
這個二元組可以看做是網絡進程地址。它是編程界面呈現給騎上應用程序的"插口",可以看成是兩個網絡應用進程在通信時,各自通信連接中的一個端點。當進行進城之間的通信時,其中一個程序將要傳輸的一段信息寫入它所在主機的Socket中,該Socket通過與網絡接口卡(Network Interface Cards,NIC)相連的傳輸介質將這段信息發往另一台主機的Socket中。使這段信息能夠被其他程序使用:

2)套接字的類型
為了滿足不同程序對通信質量和性能的要求,一般的網絡系統都提供了一下3種不同類型的套接字,以供用戶在設計程序時根據不同需要:
流式套接字(SOCKET_STREAM):提供了一種可靠的,面向連接的雙向數據傳輸服務。實現數據無差錯、無重復的發送,內設流量控制,被控制的數據被看成無記錄邊界的字節流。在TCP/IP協議簇中,使用TCP實現字節流的傳輸,當用戶要發送大量數據,
或對數據傳輸的可靠性有較高要求時使用流式套接字。 數據包套接字(SOCKET_DGRAM):提供了一種無連接,不可靠的雙向數據傳輸服務。數據以獨立的包形式被發送,並且保留了記錄邊界,不提供可靠性保證。數據在傳輸過程中可能會被丟失或重復,並且不能保證在接收端數據按發送順序接收。在TCP/IP協議簇中,
使用UDP實現數據報套接字。 原始套接字(SOCKET_RAW):該套接字允許對較低底層協議(如IP/ICMP)進行直接訪問。一般用於對TCP/IP核心協議的網絡編程。
在windows系統下,套接字WinSock屏蔽了下面TCP/IP協議棧的復雜性,使得在網絡編程者看來,兩個程序之間的通信實質就是它們各自綁定的套接字之間的通信。

(2)C#網絡編程常用特性:
在C#諸多優秀的特性中,委托,多線程和跨線程回調在網絡應用中用得最多。
1)委托
C#的委托相當於C/C++中的函數指針。函數指針用於獲得一個函數的入口地址,實現對函數的操作。委托與C/C++中函數指針的不同之處是:委托是面向對象的,類型安全的和保險的,是引用類型,因此對委托的使用要"先定義,后聲明,接着實例化,最后作為參數傳遞給方法,最后才能使用"。定義委托使用關鍵字delegate。
1.定義
delegate void MyDelegate(type1 para1,type2 para2 ......);
2.聲明
MyDelegate mydelegate;
3.實例化
mydelegate=new MyDelegate(obj.InstanceMethod);
4.作為參數傳遞
SomeMethod(mydelegate);
方法InstanceMethod的定義
private void InstanceMethod(type1 para1,type2 para2......)
{
//方法體,操作參數
}
5.在代碼中使用
private void SomeMethod(MyDelegate mydelgate)
{
mydelegate(arg1,arg2......);
}
注意:委托機制實際上是通過委托實現對方法的動態調用。但調用還必須有一個前提條件:方法InstanceMethod有參數且和MyDelegate的參數一致,並且返回類型相同。委托的實例化中的參數既可以是實例方法,也可以是靜態方法。
若實例化委托的語句與作為參數的方法位於一個類中,則可以省略對象名引用,直接用方法名實例化委托。
為什么要使用委托:
在接下來的討論中,均用文字抄寫員程序講解這三個特性:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace text01_Delegate
{
public partial class TraditionalForm : Form
{
public TraditionalForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (checkBox1.Checked == true)
{
Process1.Text = "運行中......";
Process1.Refresh();//強制該控件重繪自己及其子控件
textBox1.Clear();
textBox1.Refresh();
this.WriteTextToBox1();
Process1.Text = "任務1";
}
if (checkBox2.Checked == true)
{
Process2.Text = "運行中......";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
this.WriteTextToBox2();
Process2.Text = "任務2";
}
}
private void WriteTextToBox1()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++ )
{
textBox1.AppendText(strData[i]+"\r");
DateTime now = DateTime.Now;
while(now.AddSeconds(1)>DateTime.Now){}
}
}
private void WriteTextToBox2()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++ )
{
textBox2.AppendText(strData[i]+"\r");
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
}
}

這是一個用傳統方法編寫的程序,當勾選兩個復選框時實現將文本框中的文字依次填到文本區1和文本區2。但此程序存在兩個問題:無法實現兩個文本區域內容的同時書寫;由於書寫到文本框1和2內的代碼基本相同,從而導致代碼冗余。這兩個問題可以分別通過多線程和委托解決。
產生問題的根源:
先今程序語言普遍支持的方法(函數)調用機制是結構化編程時代的產物,而結構化是適應面向過程發展起來的編程方式,故程序中的方法(函數)體也是對操作過程的封裝,但是如今的面向對象的程序設計方法要求我們設計一種不同於傳統方法的全新的代碼封裝機制(將程序的方法作為一個函數傳遞)。
用委托實現文字抄寫員程序:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace text01_Delegate
{
public partial class DelegateForm : Form
{
private delegate void WriteTextBox(char ch);
private WriteTextBox writeTextBox;
public DelegateForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if(checkBox1.Checked==true)
{
Process1.Text = "運行中";
Process1.Refresh();
textBox1.Clear();
textBox1.Refresh();
writeTextBox = new WriteTextBox(WriTextToBox1);
wriTextForBoxAndBox2(writeTextBox);
Process1.Text = "任務1";
}
if(checkBox2.Checked==true)
{
Process2.Text = "運行中";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
writeTextBox = new WriteTextBox(WriTextToBox2);
wriTextForBoxAndBox2(writeTextBox);
Process2.Text = "任務2";
}
}
private void wriTextForBoxAndBox2(WriteTextBox writeMethod)
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++)
{
writeMethod(strData[i]);
DateTime now = DateTime.Now;
while(now.AddSeconds(1)>DateTime.Now){}
}
}
private void WriTextToBox1(char ch)
{
textBox1.AppendText(ch+"\r");
}
private void WriTextToBox2(char ch)
{
textBox2.AppendText(ch+"\r");
}
}
}
由於運行效果與不使用委托相同,只是減少了代碼冗余,因此不再演示。
委托的意義:
使用委托使程序員可以講方法引用封裝在委托對象內。然后可以將該委托對象傳遞給可調用所引用方法的代碼,而不必在編譯時知道將調用哪個方法。如此一來,C#語言憑借自身獨特的委托機制,完美地實現了方法聲明與方法實現的分離,從而徹底貫徹了面向對象的編程思想。
委托是一個特殊的類,它定義了方法的類型,可以講方法當做另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程序中大量使用if-else(switch)語句,同時也使得程序具有更好的擴展性。
2)多線程
線程概述:
一個正在運行的應用程序在操作系統中被視為一個進程,進程可以包括一個或多個線程。線程是操作系統分配處理器時間的基本單元,在進程中可以有多個線程同時執行代碼。線程上下文包括為使線程在其宿主進程地址空間中無縫地繼續執行所需的所有信息,包括CPU寄存器組和堆棧。每個應用程序域都是使用單個線程啟動的,但該應用程序域中的代碼可以創建附加應用程序域和附加線程。
多線程可以提高程序運行的效率,但也會帶來很多問題:
1.線程越多,占用內存也越多; 2.多線程需要協調和管理,所以需要占用CPU時間以便跟蹤堆棧; 3.線程之間對共享資源的訪問會互相影響; 4.線程太多會導致控制復雜。
線程的創建:
一個進程可以創建一個或多個線程來執行與改進程關聯的部分程序代碼。在C#中,線程是使用Thread類處理的,在System.Threading命名空間內。
創建線程有兩種方法,一種帶參數,一種不帶參數。這兩種方法都通過委托來實現。
1.不帶參數(ThreadStart委托) Thread thread=new Thread(new ThreadStart(method)); thread.start(); 2.帶參數(ParameterizedThreadStart) Thread thread=new Thread(ParameterizedThreadStart(method)); thread.start(args1,args2......);
現代應用程序普遍使用線程機制實現,除了增強程序工作的並發性、提高執行效率,更重要的目的是實現用戶界面的實時響應已改善軟件的交互性能。
單線程實現文字抄寫員:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace text01_Delegate
{
public partial class SingleThreadForm : Form
{
public SingleThreadForm()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;//線程操作無效,允許從不是創建該控件的線程調用
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart doTask = new ThreadStart(DoTask);
Thread taskThread = new Thread(doTask);
taskThread.Start();
}
private void DoTask()
{
if (checkBox1.Checked == true)
{
Process1.Text = "運行中......";
Process1.Refresh();//強制該控件重繪自己及其子控件
textBox1.Clear();
textBox1.Refresh();
this.WriteTextToBox1();
Process1.Text = "任務1";
textBox3.Focus();
textBox3.SelectAll();
}
if (checkBox2.Checked == true)
{
Process2.Text = "運行中......";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
this.WriteTextToBox2();
Process2.Text = "任務2";
textBox3.Focus();
textBox3.SelectAll();
}
}
private void WriteTextToBox1()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length; i++)
{
textBox1.AppendText(strData[i] + "\r");
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
private void WriteTextToBox2()
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length; i++)
{
textBox2.AppendText(strData[i] + "\r");
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
}
}
本程序的運行效果與傳統方式的相同,因此不再顯示。區別是在傳統程序中主程序執行將文本寫入文本框的操作,但在此單線程程序中主線程分配一個子線程,讓子線程代替執行此操作,主線程繼續監聽用戶操作(實時GUI響應)。
ThreadStart與ParameterizedThreadStart實際上就是兩個.Net預定義的委托。
多線程並發執行:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace text01_Delegate
{
public partial class AssociateForm : Form
{
private delegate void WriteTextBox(char ch);
private WriteTextBox writeTextBox;
public AssociateForm()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
ThreadStart doTask1 = new ThreadStart(DoTsk1);
Thread tsk1Thread = new Thread(doTask1);
tsk1Thread.Start();
ThreadStart doTask2 = new ThreadStart(DoTsk2);
Thread tsk2Thread = new Thread(doTask2);
tsk2Thread.Start();
}
private void DoTsk1()
{
if(checkBox1.Checked==true)
{
Process1.Text = "運行中......";
Process1.Refresh();
textBox1.Clear();
textBox1.Refresh();
//用委托來實現寫文本到TextBox1
writeTextBox = new WriteTextBox(writeTextToBox1);
writeText(writeTextBox);
Process1.Text = "任務1";
textBox3.Focus();
textBox3.SelectAll();
}
}
private void DoTsk2()
{
if (checkBox2.Checked == true)
{
Process2.Text = "運行中......";
Process2.Refresh();
textBox2.Clear();
textBox2.Refresh();
writeTextBox = new WriteTextBox(writeTextToBox2);
writeText(writeTextBox);
Process2.Text = "任務2";
textBox3.Focus();
textBox3.SelectAll();
}
}
private void writeText(WriteTextBox wMthod)
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++)
{
wMthod(strData[i]);
DateTime now = DateTime.Now;
while (now.AddSeconds(1) > DateTime.Now) { }
}
}
private void writeTextToBox1(char ch)
{
textBox1.AppendText(ch+"");
}
private void writeTextToBox2(char ch)
{
textBox2.AppendText(ch+"");
}
}
}

3)跨線程回調
在上述的單線程與多線程並發程序中,窗體的實例化方法中都有如下一句:
CheckForIllegalCrossThreadCalls = false;
.Net對它的解釋是:
獲取或設置一個值,該值指示是否捕獲對錯誤線程的調用,這些調用在調試應用程序時訪問控件的 System.Windows.Forms.Control.Handle 屬性。
如果在上述程序中注釋掉這一句會出現如下錯誤:

這是因為在.Net上執行的是托管代碼,C#強制要求這些代碼必須是線程安全的,即不允許跨線程訪問Windows窗體的控件。在前面的例子中,為了使程序可以正常執行,將C#內置控件(Control)類的CheckForIllegalCrossThreadCalls屬性人為地設置為false,已屏蔽掉編譯器對跨線程調用的檢查。如此做法是不安全的,而要在遵守.Net的安全規范前提下,從一個線程成功地訪問另一個線程創建的控件就要用到C#的方法回調機制。
回調實現的過程:
1.定義聲明回調: delegate void DoSomeCallBack(type para); DoSomeCallBack doSomeCallBack 2.初始化回調方法: doSomeCallBack=new DoSomeCallBack(DoSomeMethod); 實例化剛剛定義的委托,這里作為參數的DoSomeMethod稱為"回調方法",它封裝了對另一個線程中目標對象(窗體對象或其他類)的操作代碼。 3.觸發對象動作: Opt obj.invoke(doSomeCallBack,arg); 其中Opt obj為目標操作對象,在此假設它是某控件,故調用其INvoke方法。 Invoke方法的方法簽名: Object Control.Invoke(Delegate method,params object[] args); C#的方法回調機制實際上是委托的一種應用。
方法回調與委托與線程的綜合應用:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace text01_Delegate
{
public partial class IntergrationForm : Form
{
private delegate void WriteTextBox(char ch);
private WriteTextBox writeTextBox;
//聲明委托以實現回調機制
private delegate void WriteTextBox1CallBack(char ch);
private WriteTextBox1CallBack writeTextBox1CallBack;
private delegate void WriteTextBox2CallBack(char ch);
private WriteTextBox2CallBack writeTextBox2CallBack;
private delegate void ShowProcess1CallBack(string str);
private ShowProcess1CallBack showProcess1CallBack;
private delegate void ShowProcess2CallBack(string str);
private ShowProcess2CallBack showProcess2CallBack;
public IntergrationForm()
{
InitializeComponent();
CheckForIllegalCrossThreadCalls = true;
writeTextBox1CallBack = new WriteTextBox1CallBack(writeTextBox1ByItself);
writeTextBox2CallBack = new WriteTextBox2CallBack(writeTextBox2ByItself);
showProcess1CallBack = new ShowProcess1CallBack(showProcess1ByItself);
showProcess2CallBack = new ShowProcess2CallBack(showProcess2ByItself);
}
private void writeTextBox1ByItself(char ch)
{
textBox1.AppendText(ch+"");
}
private void writeTextBox2ByItself(char ch)
{
//textBox1.AppendText(ch+"");這樣寫程序也是運行正確的
textBox2.AppendText(ch+"");
}
private void showProcess1ByItself(string str)
{
Process1.Text = str;
Process1.Refresh();
}
private void showProcess2ByItself(string str)
{
Process2.Text = str;
Process2.Refresh();
}
private void button1_Click(object sender, EventArgs e)
{
if (checkBox1.Checked == true)
{
//Process1.Text = "運行中";
//Process1.Refresh();
Process1.Invoke(showProcess1CallBack, "運行中......");
ThreadStart doTask1 = new ThreadStart(writeTextToBox1);
Thread tsk1Thread = new Thread(doTask1);
tsk1Thread.Start();
//Process1.Text = "任務1";
//textBox3.Focus();
//textBox3.SelectAll();
Process1.Invoke(showProcess1CallBack, "任務1");
}
if (checkBox2.Checked == true)
{
//Process2.Text = "運行中";
//Process2.Refresh();
Process2.Invoke(showProcess2CallBack, "運行中......");
ThreadStart doTask2 = new ThreadStart(writeTextToBox2);
Thread tsk2Thread = new Thread(doTask2);
tsk2Thread.Start();
//Process2.Text = "任務2";
Process2.Invoke(showProcess2CallBack, "任務2");
}
}
private void writeTextToBox1()
{
writeTextBox = new WriteTextBox(wriTextBox1);
wriText(writeTextBox);
}
private void writeTextToBox2()
{
writeTextBox = new WriteTextBox(wriTextBox2);
wriText(writeTextBox);
}
private void wriTextBox1(char ch)
{
textBox1.Invoke(writeTextBox1CallBack,ch);
}
private void wriTextBox2(char ch)
{
textBox2.Invoke(writeTextBox2CallBack,ch);
}
private void wriText(WriteTextBox wMethod)
{
string strData = textBox3.Text;
for (int i = 0; i < strData.Length;i++ )
{
wMethod(strData[i]);
DateTime now=DateTime.Now;
while(now.AddSeconds(1)>DateTime.Now){}
}
}
}
}
效果不再演示!
實驗文檔:http://files.cnblogs.com/files/MenAngel/NetProgram.zip

