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){} } } } }
效果不再演示!
实验文档:https://files.cnblogs.com/files/MenAngel/NetProgram.zip