概述
c#网络应用编程
1 网络应用编程入门知识
- TCP/IP网络体系结构
- 应用层
- 传输层
- 网际层
- 网络接口层
- IP地址(不能识别到主机上的进程)
- 网络号:识别该地址所属的网络,它由Internet权力机构分配
- 主机号:指明网络内的主机,它由各个网络的管理员统一分配
- IPv4编址方案
- 由4个字节(十进制表示)组成的二进制值进行识别,中间用圆点分开,这种方法叫做点分十进制表示法
- 例如:192.168.1.1,127.0.0.1
- IPv6编址方案
- 每个IP地址有16个字节(128位二进制数),其完整格式用8段16进制表示,各段之间用冒号分隔。多个连续的0可以用::表示
- 例如:0:0:0:0:0:0:0:1 可以简写为::1
- 子网掩码:子网掩码用于屏蔽IP地址的一部分以区别网络标识和主机标识
- 子网掩码把所有的网络位(二进制)用1来标识,主机位用0来标识
- 可以利用子网掩码判断两台计算机是否在同一子网内。具体操作:将其IP地址和子网掩码做按位与运算,如果得到结果相同即在同一个子网内
- 逻辑意义的端口:是为了解决与多个进程同时进行通讯的问题
- 可用端口地址的范围是十进制的0~65535。
- 端口地址用两字节二进制数来表示;
- 1000以内的端口号大多被标准协议所占用;
- 应用程序中可以自由使用的端口号一般都使用大于1000的值
- 套接字: 套接字是支持TCP/IP协议的网络通信的基本操作单元
1.4.2 地址转换相关类
- IPAddress类
- 用IPAddress类提供的静态Parse方法将IP地址字符串转换为IPAddress的实例
利用该实例的AddressFamily属性可以判断该IP地址是IPv6还是IPv4try { IPAddress ip = IPAddress.Parse("143.24.20.36"); } catch { MessageBox.Show("请输入正确的IP地址!"); }
IPAddress ip = IPAddress.Parse("::1"); if (ip.AddressFamily == AddressFamily.InterNetworkV6) { MessageBox.Show("这是IPv6地址"); }
- 用IPAddress类提供的静态Parse方法将IP地址字符串转换为IPAddress的实例
- IPEndPoint类:IPEndPoint描述应用程序连接到主机上的服务所需的主机和端口信息
public IPEndPoint(IPAddress address, int port);//第一个参数指定IP地址,第二个参数指定端口
、//案例 IPAddress localAddress = IPAddress.Parse("192.168.1.1"); IPEndPoint iep = new IPEndPoint(localAddress, 65000); string s1 = "IP地址为:" + iep.Address; string s2 = "IP端口为:" + iep.Port;
- IPHostEntry类:将域名系统 (DNS) 主机名与别名数组和匹配 IP 地址数组相关联。该类一般和Dns类一起使用
-
示例查询 DNS 数据库以获取有关主机 www.contoso.com 的信息,并返回实例中 IPHostEntry 的信息
IPHostEntry hostInfo = Dns.GetHostEntry("
www.contoso.com");
-
AddresList属性:获取或设置与主机关联的IP地址列表(包括IPv4和IPv6)
// 获取搜狐服务器的所有IP地址 IPAddress[] ips = Dns.GetHostEntry("news.sohu.com").AddressList;
-
HostName属性: 包含了指定主机的主机名
// 获取本机所有IPv4和IPv6地址 IPAddress[] ips = Dns.GetHostEntry(Dns.GetHostName( )).AddressList;
-
示例查询 DNS 数据库以获取有关主机 www.contoso.com
的信息,并返回实例中 IPHostEntry 的信息
- IPAddressCollection 类:存放一组IPAddress类型,相当于存放IPAddress类型的数组
1.4.3 域名解析(DNS)
- 域名:方便记忆计算机IP地址的助记字符型地址
- 域名解析:域名解析就是域名到IP地址的转换过程
- GetHostAddresses:返回指定主机的Internet协议IP地址,与该方法对应的还有异步方法
//方法原型 public static IPAddress[] GetHostAddresses(string hostNameOrAddress);//hostNameOrAddress表示要解析的主机名或IP地址 //案例 IPAddress[] ips = Dns.GetHostAddresses(hostNameOrAddress); //若hostNameOrAddress是IP地址,则直接返回此地址; //若hostNameOrAddress是空字符串,则返回本机所有IPv4和IPv6地址。 //例如: IPAddress[] ips = Dns.GetHostAddresses(""); //获取本机的所有IP地址
- GetHostEntry: 将主机名或IP地址解析为IPHostEntry实例,与该方法对应的还有异步方法
//方法原型 public static IPHostEntry GetHostEntry (string hostNameOrAddress) //当参数为空字符串时,返回本地主机的IPHostEntry实例。 //使用案例: IPHostEntry host = Dns.GetHostEntry(""); var ipAddresses = host.AddressList; //获取本机所有IP地址 string name = host.HostName; //获取本机主机名
- GetHostName: 获取本地计算机的主机名
string hostname = Dns.GetHostName( );
- GetHostAddresses:返回指定主机的Internet协议IP地址,与该方法对应的还有异步方法
1.5 网卡信息检测与网络流量监测
- 网络适配器:又称网卡或网络接口卡(NIC),是连接计算机与网络的硬件设备。整理计算机上发往网线上的数据,并将数据分解为适当大小的数据包之后向网络上发送
1.5.1 网卡信息检测相关类
- NetworkInterface类
- NetworkInterface类提供了网络适配器的配置和统计信息。可以利用这个类检测本机有多少个网络适配器、网络适配器型号以及网络适配器的速度等
- 获取实例: 利用NetworkInterface类提供的静态方法得到NetworkInterface类型的数组
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
- 一些属性和方法
- Name属性: 获取网络适配器的名称
- Speed属性:获取网络适配器的速度(bit/秒)
- GetAllNetworkInterfaces方法:返回描述本地计算机上的所有网络适配器对象
- GetIPProperties方法:返回描述此网络适配器配置的对象
- GetIsNetworkAvailable方法:指示是否有任何可用的网络连接
- GetPhysicalAddress方法:返回此适配器的媒体访问控制(MAC)地址
- Supports方法:指示接口是否支持指定的协议 (IPv4或IPv6)
- IPInterfaceProperties类
- 可以利用这个类检测本机所有网络适配器支持的各种地址
- IPInterfaceProperties类是抽象类,不能实例化,可以通过NetworkInterface对象的GetIPProperties()获得其实例
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces(); IPInterfaceProperties adapterProperties = adapters[0].GetIPProperties();
- 常用属性和方法
- AnycastAddresses属性:获取分配给此接口的任意广播IP地址
- DhcpServerAddresses属性:获取此接I的动态主机配置协议(DHCP)服务器的地址
- DnsAddresses属性:获取此接口的域名系统(DNS)服务器的地址
- DnsSuffix属性:获取与此接口关联的域名系统(DNS)后缀
- GatewayAddresses属性:获取此接口的网关地址
- MulticastAddresses属性:获取分配给此接口的多路广播地址
- UnicastAddresses属性:获取分配给此接口的单播地址
- GetIPv4Properties方法:获取此网络接口的Internet协议版本4(IPv4)配置数据
- GetIPv6Properties方法:获取此网络接口的Internet协议版本6(IPv6)配置数据
1.5.2 网络流量检测相关类
- IPGlobalProperties类
- 提供了本地计算机网络连接和通信统计数据的信息
- 例如,接收到的数据包个数、丢弃的数据包个数等
- 检测网络流量时,首先调用IPGlobalProperties类提供的静态方法GetIPGlobalProperties得到IPGlobalProperties的实例,然后通过该实例的相关属性即可得到需要的信息
IPGlobalProperties properties = IPGlobalPropeties.GetIPGlobalProperties( );
- 常用方法
- GetActiveTcpConnections (IPV4:返回有关本地计算机上的 Internet 协议版本 4传输控制协议(TCP) )连接的信息
- GetActiveTcpListeners:返回有关本地计算机上的 Internet 协议版本 4 (IPV4) 传输控制协议 (TCP) 侦听器的终结点信息
- GetActiveUdpListeners:返回有关本地计算机上的 Internet 协议版本 4 (IPv4) 用户数据报协议 (UDP) 侦听器的信息
- GetIPv4GlobalStatistics:提供本地计算机的 Internet 协议版本 4 (IPv4) 统计数据
- GetIPv6GlobalStatistics:提供本地计算机的 Internet 协议版本 6 (IPv6) 统计数据
- GetTcpIPv4Statistics:提供本地计算机的传输控制协议/Internet 协议版本 4 (TCP/IPv4) 统计数据
- GetTcpIPv6Statistics
3.1线程和进程
- c#中多线程修饰符volatile
volatile是C#中用于控制同步的关键字, 其意义是针对程序中一些敏感数据, 不允许多线程同时修改,但是可以被多个同时运行的线程分时刻修改, 保证数据在任何访问时刻, 最多有一个线程访问,以保证数据的完整性,volatile是修饰变量的修饰符。只能声明在类或者结构中,不能声明局部变量。 - volatile 应用场景之一: 修饰需要被多线程访问的同一数据,防止数据出错
start() 线程启动;重载带参数 start(objet); start只是告诉系统当前线程需要启动,但是系统不一定会立即启动
Thread.CurrentThread.Name 获取当前运行线程的名字
- 进程
- p.StartInfo.FileName = “文件名”;
p.StartInfo.Arguments = “参数”;
p.StartInfo.WindowStyle = ProcessWindowStyle.Normal; - 进程是资源调度和分配的基本单位;进程之间相互独立
- 进程创建:
Process p = new Process();
- 进程启动:
- 通过创建的进程对象的StartInfo属性指定要运行的应用程序名称以及传递的参数,然后调用该对象的Start方法启动该进程
Process p = new Process(); p.StartInfo.FileName = "文件名"; p.StartInfo.Arguments = "参数"; p.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
- 直接使用Process类的静态Start方法:
Process.Start(“Notpad.exe”);
- 通过创建的进程对象的StartInfo属性指定要运行的应用程序名称以及传递的参数,然后调用该对象的Start方法启动该进程
- 进程停止:
- CloseMainWindow + Close(释放资源): 有图形界面
- Kill + WaitForExit(等待退出): 所有程序
- 最好使用try/catch语句包含进程
-
Process类提供的相关方法和属性
- Kill方法和CloseMainWindow方法
前者用于强行终止进程,后者只是“请求”终止进程。 - HasExited属性HasExited属性用于判断启动的进程是否已停止运行。
- WaitForInputIdle方法仅适用于具有用户界面的进程,它可以使Process等待关联进程进入空闲状态。
- WaitForExit方法设置等待关联进程退出的时间
- Kill方法和CloseMainWindow方法
-
其他一些属性
ExitCode属性用于获取关联进程终止时指定的值
ExitTime属性用于获取关联进程退出的时间
EnableRaisingEvents属性用于获取或设置在进程终止时是否应引发Exited事件
- 获取进程信息
-
获取本地计算机上指定名称的进程:
Process[] myProcesses = Process.GetProcessesByName("进程名称");
-
获取远程计算机上指定名称的进程:
Process[] myProcesses =Process.GetProcessesByName("远程进程名称",remoteMachineName);
注意:(a)进程名称不带扩展名
(b)可以是任何一个可执行文件
-
- p.StartInfo.FileName = “文件名”;
- 线程
- 线程的创建
- 无参线程的创建 使用委托(ThreadStart)或省略委托(直接创建时传入方法名)
- 有参线程的创建 使用委托(ParameterizedThreadStart)或省略委托
- 一个参数(object类)
Thread t = new Thread(Method(Object obj));t.start(obj);
- 多个参数(封装class,将参数类型定义为该class)
- 一个参数(object类)
- 利用sleep() 方法可实现线程休眠 参数单位ms
- 前台线程后台线程
- isBackground属性 设置线程为后台线程 (值为true时为后台线程);默认为前台线程
- 线程的优先级
- Priority属性 设置优先级 值可以为ThreadPriority枚举类的值
- 优先级高的只是尽量在运行的时候多分配cpu资源,并不是一定先执行, 相对比较先
- 使用最高优先级的时候要特别小心
- 线程的终止和取消
- 调用方法Abort() 属于强制停止运行;称为取消线程的执行
- 随线程中所有方法运行完毕而停止,属于自然停止运行;称为终止线程
- 线程的创建
3.1.4 线程池(ThreadPool类)
- 线程池的基本特征
- 托管线程池中的线程都是后台线程
- 添加到线程池中的任务不一定会立即执行
- 完成任务的线程会自动返回线程池等待重用,而不是销毁
- 线程池可设置最大线程数
- 向线程池中添加工作项
- 欲执行的方法无参数 ThreadPool.QueueUserWorkItem(new WaitCallback(Method));
- 欲执行的方法有参数 ThreadPool.QueueUserWorkItem(new WaitCallback(Method),object); (多个参数时还用封装类)
- ThreadPool只提供了一些静态方法,不能通过创建该类的实例来使用线程池,使用的时候直接使用静态方法,不能new
3.1.5 线程池多线程编程中的资源同步
- 同步执行:按顺序执行,前面不完成,后面不进行
- 异步执行:不管某语句是否执行完,后面的也执行
- 并行:在不同cpu上同时执行
- 并发:在同一cpu上伪同时执行
- 死锁:同时想访问同一资源,互相等待而使两个线程都停止响应
- 争用:由于线程执行先后而导致程序结果不一样的现象
Join()方法:
- 字符串Join() 方法:用于连接数组的元素,在每个元素之间使用指定的分隔符。它返回一个修改后的字符串
- 多线程中的Join()方法:作用就是让主线程等待子线程执行结束之后再运行主线程,相当于临时用同步执行保同步执行(通俗的说就是创建一个子线程,给它加了这个方法,其它线程就会暂停执行,直到这个线程执行完为止才去执行其他线程)
Lock语句:
//lock语句格式
private object o = new object();//创建一个对象
public void Work()
{
lock(o)//锁住这个对象
{
//做一些必须按照顺序做的事情
}
}
3.1.6 wpf中的多线程编程
- 默认情况下,.NET框架下不允许在一个线程中直接访问另一个线程中的控件。要在后台线程中与用户界面交互,可以通过向WPF控件的Dispatcher注册工作项来完成,也就是说需要新的方法动作,就把这个方法动作添加到该控件的Dispatcher中
- WPF调度器(Dispatcher属性) wpf中每个元素都有的属性
- Invoke方法 同步调用,即直到在线程池中实际执行完该委托它才返回
- InvokeAsync是异步调用
- 注册时使用委托注册添加方法:Dispatcher.Invoke(method) ;当method的方法体很小时,采用匿名方法:Dispatcher.Invoke(() => {方法体})
4.1数据编码和解码
- c#中的字符和字符串默认采用的都是Unicode编码
- Unicode 2个字节,世界通用
- UTF-8 变长字符编码 1~4个字节 英文1个字节,汉字4个字节
- Encoding类
- Encoding类在System.Text命名空间
- 获取指定编码格式
- Encoding直接获取:
Encoding ascii = Encoding.ASCII;
- Encoding的GetEncoding()方法获取:
Encoding gb2312 = Encoding.GetEncoding("GB2312");
- HeadName属性获取编码名称:
string s1 = "GB2312的编码名称为:" + gb2312.HeaderName;
- EncodingName属性获取编码描述:
string s2 = "GB2312的编码描述为:" + gb2312.EncodingName;
- HeadName属性获取编码名称:
- Encoding直接获取:
- 编码之间的转换:
-
方法一Convert:
byte[] b = Encoding.Convert(unicode,utf8,unicode.GetBytes(s)) ;
三个参数: 原编码,目标编码,原编码转换的字节序列
最后用目标编码格式对象的GetString()方法将字节序列b转换成目标
-
方法二Encoding类:
- 编码:利用获取的编码格式对象的GetBytes()方法 将需编码内容转换为字节序列,可以用**
BitConverter.ToString(byte[] b)
** 来查看编码后的字节序列 - 解码:利用获取的编码格式对象的GetString()方法 将 转换后的字节序列解码
- 编码:利用获取的编码格式对象的GetBytes()方法 将需编码内容转换为字节序列,可以用**
-
4.2 数据流
- 都是由Stream基类派生的
- 当希望通过网络逐字节串行传输数据,或者对文件逐字节进行操作时,首先需要将数据转化为数据流
- 文件流:
- 获取当前工作目录(路径)
string path = Path.Combine(Environment.CurrentDirectory),"file.txt");
string path = Path.Combine(System.IO.Directory.GetCurrentDirectory(),"file.txt")
string path = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory,"file.txt")
- FileStream创建
- 使用FileStream构造函数创建:
-
FileStream(string path, FileMode mode, FileAccess access)
path :文件路径
mode :文件操作方式 ,取值:CreateNew ,Create ,Open ,OpenOrCreate ,Truncate ,Append
FileAccess :权限,取值:Read ,Write ,ReadWrite
-
- 使用File类创建
FileStream file = File.OpenRead(path)
FileStream file = File.OpenWrite(path)
- 使用FileStream构造函数创建:
- 文件流的读写
fileStream.Position = fileStream.Length
** 位置设置到文件流尾部**- 操作对象是数据流
- 读:
-
fileStream.Read(byte[] ,int ,int )
文件流转换成的字节数组
向字节数组中写入数据的起始位置,一般为0
希望从**文件流中读取的字节数,实际上读取的字节数是Read 的返回值(某个真实值)
-
- 写:
-
fileStream.Write(byte[] ,int ,int )
要写入文件流中的数据
从字节数组中读取的起始位置
写入到流中的字节数
-
- 结尾一定要释放和关闭
- 手动
fileStream.Flush(); fileStream.Close();
- 自动 using(){ } 圆括号内创建对象,大括号内执行方法体,当方法体执行完之后括号内的对象会自动释放
//使用using自动释放 using(FileStream fs2 = new FileStream (string path, FileMode mode, FileAccess access) { fs2.Position = fs2.Length;//从末尾进行写入 fs2.Write(byts,0,byts.Length); Console.WriteLine("写入完毕"); }
- 手动
- 获取当前工作目录(路径)
- 内存流:
- **MemoryStream **构造函数无参 ,方法和文件流很像
- Position 设置内存流当前位置
- Write
- Read
- **MemoryStream **构造函数无参 ,方法和文件流很像
- 网络流:
- **NetworkStream **(仅支持面向连接的套接字 即面向TCP)
- 获取NetworkStream对象
- 利用TcpClient对象的GetStream方法:
TcpClient tcpClient = new TcpClient(); tcpClient.Connect("www.abcd.com".51888); NetworkSTream networkStream = tcpClient.GetStream();
- 利用Socket:
NetworkStream myNetworkStream = new NetworkStream(Socket)
- 利用TcpClient对象的GetStream方法:
- 无 Position属性 不支持查找和随机访问
- CanWrite、CanRead属性 可判断网络流对象是否可写入、读出
- 通过DataAvailable属性可查看缓冲区是否有数据等待读出
- 写入 从来源端内存缓冲区到网络上的数据传输 (本机进程缓冲区到TCP发送缓冲区,全部在本地)
- 读取 从网络上到接收端内存缓冲区(如果字节数组)的数据传输(TCP接收缓冲区到本机进程缓冲区,接受收时从网络接收)
- 文本读写流:
- 操作文本数据时,不用先转换成字节序列,相对方便:可以直接用Console.WriteLine()读出,用Console.ReadLine()读出:
- 创建StreamReader和StreamWriter实列
- 如果数据来源是文件流、内存流或者网络流 直接
StreamReader sr = new StreamReader(networkStream)
写入也一样,参数也可换成其他流 - 对文件流:直接利用文件路径创建StreamWriter对象:
StreamWriter sw = new StreamWriter("C:file.txt");
或者StreamWriter sw = File.CreateText("C:file.txt");
- 如果数据来源是文件流、内存流或者网络流 直接
- 也是需要关闭! colse()方法
- 加密流
- 使用CryptoStream对象时,一般还要借助其他流进行处理
- 解密时,使用和加密时相同的密钥创建CryptoStream实例,并在创建该实例时将构造函数的mode参数改为读模式,再将StreamWriter替换成StreamReader,即可将解密后的数据读取出来。
- 二进制读写流:
- BinaryStream和BinaryWriter
- 创建方式和FileStream类似 但是没有路径和参数
- 写入会自动根据数据类型调用不同重载
- 读取需要用相应的数据类型进行读取
br.ReadInt32(); br.ReadDouble(); ...
5.1.1 任务
任务(Task)表示一个异步操作。任务运运行的时候需要使用线程,通用语言运行时(CLR)会创建必要的线程来支持任务执行的需求
- Task类:表示一个没有返回值的异步操作
Task t = Task.Run(() => { Console.WriteLine("this is a task"); });
- Task<TResult>:表示一个可以返回值的异步操作
var t = Task<int>.Run(() => { int num = 5; num = DateTime.Now.Year+5; return num; });
- Task.Run方法:可以将一段代码以任务的方式运行
- Task.Delay 方法:
- 常常搭配await使用:await Task.Delay(1000)//参数单位为毫秒,有时候为了符合语法,参数值设为0,此时不起延时效果
- **Delay(Int32) ** //延时指定的毫秒数
- Delay(TimeSpan) //延时指定的时间(年、月、日、时、分、秒、毫秒等)
- Delay(Int32, CancellationToken) //延时指定的毫秒数后取消任务操作
- Delay(TimeSpan, CancellationToken) //延时指定的时间后取消任务操作
5.1.2 Lambda表达式
- 可用于创建委托或表达式树类型的匿名函数
- 基本用法
- (输入参数列表) => {表达式或语句块}
- 列表的一些LinQ语句可以结合lambda表达式使用:如Where的查询条件可以是Lambda表达式:var q = list.Where(i => i<4); 或者 var q = list.Select((n) => string[n]);
x => x*x //返回传入参数的平方 (x,y) => x==y //返回传入两个参数是否相等(bool值) (int x, string s) => s.Length > x // () => SomeMethod() //
- Action委托:
- 定义:Action<[T1,T2,…,T16]>
- 封装不带返回值的方法
- T1~T16表示输入参数的类型,参数个数可以是0~16个
- 例如:Action<T1,T2> 等价于Public delegate void Action<in T1,in T2>(T1 a,T2 b);
- 基本用法
- 普通方法实现
- 匿名方法实现
Action a = () => Console.WriteLine("OK"); a(); Action<string>b = (s)=> Console.WriteLine(s); b("OK"); Console.ReadKey();
- 定义:Action<[T1,T2,…,T16]>
- Func委托:
- 定义:Func<[T1,T2,…,T16],TReasult>
- 封装带返回值的方法
- 返回值类型为TReasult
- 定义:Func<[T1,T2,…,T16],TReasult>
5.1.4 元组(Tuple类)
元组是一种数据结构,其中的元素具有固定的数目和序列
- 元组表示一组数据,提供对数据集的轻松访问和操作
- 元组内数据类型可以不一样,甚至可以是元组类型
- 由于元组内数据类型可以不一样,可以用Var 类型接受返回值
- 创建方法:
- 直接使用构造函数实例化对象
Tuple<int, string, string> person = new Tuple <int, string, string>(1, "Steve", "Jobs");
- Tuple.Create(Item1,Item2,…) 方法可以直接创建具有1个到7个元素的元组,如果容量不够,可以嵌套元组,用var接受这个对象
- 直接使用构造函数实例化对象
- 通过Tuple对象的ItemN引用(N=1~7)得到相应元素 t.Item1,t.Item2…
- 整个元组的输出可以用t.ToString()
5.2 异步编程基本技术
5.2.1异步编程的实现方式和异步操作关键字
- 异步编程的实现方式
- APM 传统的异步编程模型 已淘汰不用
- EAP 基于事件的异步编程设计模式 已淘汰不用
- TAP 基于任务的异步模式
- 改进的而基于人物的异步模式(async、await、TAsk.Run和TAP)
- TAP和c#关键字的而结合使用
- 目前建议采用的异步编程技术
- 异步操作关键字
- 异步方法和异步事件处理程序:
- 带async修饰的方法成为异步方法
- 带async修饰的事件处理程序称为异步事件处理程序
- 以上二者也可以统称为异步方法
- async修饰符:
- 普通方法:
- 配合Task.Run()来创建和执行
- 异步方法:
- 如果方法没有返回值,则用async和Task共同签名
- 如果方法有返回值,则用async和Task<T>共同签名
- 普通方法:
- 异步事件处理程序(如button处理事件):
- 用async和void共同签名
- await运算符:
- await运算符表示等待异步执行的结果。实质上对方法的返回值进行操作
- await可以获得有返回值的任务的返回值
- 使用await异步等待任务完成时,不会执行其后面的代码,但也不会影响用户对UI的操作
- await运算符必须放在异步方法内部
- 异步方法的命名约定:
- 方法后面以为”Async“作为后缀
- 仅包含async和await关键字的异步方法与用Task.Run调用的异步方法不同点:
- async和await关键字是C# 5.0提供的功能,仅包含async和await关键字的异步方法不会创建新线程,它只是表示在当前线程中异步执行指定的任务。而Task.Run方法是.NET框架4.5提供的功能,它会在线程池中用单独的线程执行某个任务,通用语言运行时(CLR)会创建必要的线程来支持任务执行的需求
- 异步方法和异步事件处理程序:
5.2.2 创建任务
- 定义任务执行的方法:
- 用普通方法定义任务
public void Method1() {…} private async void btnStart_Click(…) { await Task.Run(()=>Method1()); }
- 用异步方法定义任务
public async Task Method1Async() {…. } private async void btnStart_Click(…) { await Method1Async(); }
- 用匿名方法定义任务(Lambda表达式)
private async void btnStart_Click(...) { await Task.Run(()=> { Console.WriteLine(“aaa”); }); }
- 用普通方法定义任务
- 四种创建和执行任务的方法
-
利用Task.Run方法隐式创建和执行任务(开启新的线程)
- Task.Run方法是.NET框架4.5提供的功能,它会在线程池中用单独的线程执行某个任务
- Run方法的几种形式:
- **Run(Func<Task>) **
- 用默认调度程序在线程池中执行不带返回值的任务
- **Run<TResult>(Func<Task<TResult>>) **
- 用默认调度程序在线程池中执行带返回值的任务
- **Run(Func<Task>, CancellationToken) **
- 执行任务过程中可侦听取消通知
- Run<TResult>(Func<Task<TResult>>, CancellationToken)
- 执行任务过程中可侦听取消通知
- **Run(Func<Task>) **
-
async隐式创建异步方法(不开启新的线程)参考定义任务的方法
- 如果方法没有返回值,则用async和Task共同签名
private async Task FunAsync() { await Task.Delay(1000);//使得任务延时一秒 } public async void btn_Click(...) { await FunAsync(); }
- 如果方法有返回值,则用async和Task<T>共同签名
private async Task<bool> FunAsync(int a,int b) { return a>b; } public async void btn_Click() { bool b = await FunAsync();//await可以返回返回值 }
- 如果方法没有返回值,则用async和Task共同签名
-
WPF中Dispatcher.InvokeAsync()隐式创建和执行
- 类似于线程注册:Invoke注册线程;InvokeAsync注册任务();
this.Dispatcher.InvokeAsync(() => FunAsync());
- 类似于线程注册:Invoke注册线程;InvokeAsync注册任务();
-
利用Task和Task<TResult>显示创建任务
类似线程创建和运行
Task t = new Task(Fun);//传入方法名加入到任务中 t.Start();//运行任务
-
5.2.3 取消或终止任务的执行
- 实现原理:将有可能取消的任务添加到一个群内,在群内发通知实现所有群内的收到通知后取消任务
- CancellationTokenSource类和CancellationToken结构
- 二者位于System.Threading命名空间
- CancellationTokenSource用于创建取消通知,称为取消源
- 创建:
CancellationTokenSource cts = new CancellationTokenSource();
- 取消过程:
- CancellationTokenSource对象(短信群)的Cancel方法发出取消通知,然后将CancellationToken对象(群发的通知)的IsCancellationRequested属性设置为true
- 执行任务的方法接收到取消通知后,可以用以下方式之一种操作:
- 在任务代码中,简单地从委托中返回,任务状态值为RanToCompletion(正常完成)
- 可以通过任务的Status属性获得任务状态
- 类似线程终止时设定的布尔变量方式
- 在任务代码中,因为取消的话会引发OperationcanceledException异常,并将其传递到在其上请求了取消的标记
- 调用CancellationToken对象的ThrowIfCancellationRequested方法
ct.ThrowIfCancellationRequested(); //捕获异常,方便以后处理
- 调用CancellationToken对象的ThrowIfCancellationRequested方法
- 在任务代码中,简单地从委托中返回,任务状态值为RanToCompletion(正常完成)
- 创建:
- CancellationToken结构用于传播应取消操作的通知,称为取消令牌
CancellationToken ct=cts.Token
5.2.4 获取任务执行的状态
- 冷任务:
- 用Task类或者Task<TResult>类的构造函数显式创建的任务称为冷任务(Cold Task),冷任务必须通过Start方法来启动
- 热状态:
- 任务启动后(显式创建是在调用Start方法后启动,隐式创建是创建后默认启动),才开始其生命周期,此周期一直持续到释放任务占用的资源为止。任务在生命周期内的执行情况称为热状态
- 获取任务执行的状态:
- Status属性: 利用任务实例的Status属性获取任务执行的状态。任务执行的状态用TaskStatus枚举表示
- TaskStatus枚举:可取值:
- Created: 该任务已初始化,但尚未进入调度计划。
- WaitingForActivation: 该任务已进入调度计划,正在等待被调度程序激活。
- WaitingToRun: 该任务已被调度程序激活,但尚未开始执行。
- Running: 该任务正在运行,但尚未完成。
- RanToCompletion: 该任务已成功完成。
- Canceled: 该任务由于被取消而完成(任务自身引发OperationCanceledException异常,或者在该任务执行之前调用方已向该任务的CancellationToken发出了信号)。
- Faulted: 该任务因为出现未经处理的异常而完成。
- WaitingForChildrenToComplete: 该任务本身已完成,正等待附加的子任务完成。
- 任务完成情况的相关属性:
- IsCompleted:
- 表示任务是否完成
- 任务装填为RanToCompletion时,任务完成成功
- IsCanceled:
- 表示任务是否因为取消而完成
- IsFaulted:
- 表示任务是否因为出现未处理的异常而完成
- 取消和完成之间的关系
- 取消是向任务传递一种信号,希望任务尽快结束
- 完成是任务执行结束了
- IsCompleted:
6 并行编程
- 不同cpu真正同时
- 任务并行库(TPL)基于任务的并行编程模型,主要借助System.Threading.Tasks.Parallel类实现
- TPL的核心是Parallel类和PLINQ,编写并行程序的首选方案
- TPL的分类:
- 数据并行
- 对源集合或者数组中的元素同时执行相同操作
- 借助Parallel类的For或Foreach方法来实现
- 任务并行
- 借助Parallel类提供的静态方法Invoke实现任务并行
- 并行查询
- 并行实现LINQ to Objects查询,即PLINQ
- 数据并行
并行编程是尽力充分使用cpu资源的编程,并不是所有的编程都适合并行编程
Parallel类:用于并行操作
- Parallel类提供的并行方法
- Parallel.For方法用于并行执行for循环:如果不需要取消或总段迭代,或者不需要保持线程本地状态,用此方法最简单;Parallel.For和Parallel.Foreach方法都是用于数据并行
- For(Int32,Int32,Action<Int32>)
//一般形式: //Parallel.For(<开始索引>,<结束索引>,<每次迭代执行的委托>) //计算两个数组的和 int[] a = Enumerable.Range(1, n). ToArry(); int[] b = Enumerable.Range(1,n).ToArry(); int[] c = new int[n] ; Action<int> action = (i) => { c[i] = a[i] + b[i]; }
- For(Int32,Int32,Parallel0ptions,Action<Int32>) //带并行选项的Parallel.For循环
//完整语法 public static ParallelLoopResult For( int fromInclusive, //开始索引(包含) int toExclusive, //结束索引(不包含) ParallelOptions parallelOptions, //并行选项 Action<int> body //每个迭代调用的委托 ) //使用案例 private void btnStart(object sender,RoutedEventArgs e) { Action<int> action2= NewAction(); ParallelOptions option = new ParallelOptions(); option.MaxDegreeOfParallelism= 4 * Environment.ProcessorCout;//自定义最大并行度 sw. Restart();//开始计时 Parallel.For(0,n,option,action2);//使用有并行度的重载 sw.Stop();//结束计时 AddInfo("自定义并行选项,用时: {0]ms,最大并行度:{1} ", sw.ElapsedMilliseconds,option.MaxDegree0fParallelism); } private Action<int> NewAction(){ //委托体 return action; }
- For(Int32,Int32,Action<Int32,ParallelLoopState>)//带并行循环状态的Parallel.For循环,可以监视或控制并行循环的状态
//完整语法 public static ParallelLoopResult For( int fromInclusive, //开始索引(包含) int toExclusive, //结束索引(不包含) Action<int, ParallelLoopState> body //每个迭代调用的委托 ) //使用案例 private void btnStart(object sender,RoutedEventArgs e) { Action<int,ParallelLoopState> action = (i, loopState) => { Data data = new Data() { Name =“A” + i.ToString(), Number = i } if (i==10) loopState.Break()//给委托添加循环状态,此出用于设置跳出循环的条件 }; try { var result = Parallel.For (0, n,action);//调用有循环状态的重载,其中循环状态调用相应委托 } catch (Exception ex) { MeazageBox.show (ex.ToString() }; } public class Data{ string Name; int Number; }
- For(Int32,Int32,Parallel0ptions,Action<Int32,ParallelLoopState>)// 既带有并行选项,还带有循环状态
- For<TLocal>(Int32,Int32,Func<TLocal>,Func<Int32,ParallelLoopState,TLocal,TLocal>,Action<TLocal>)//带有线程局部变量的Parallel.For重载;线程局部变量保存的数据称为线程本地数据
带有线程局部变量的Parallel.For重载//方法原型 public static ParallelLoopResult For<TLocal>( int fromInclusive, //开始索引(包含) int toExclusive, //结束索引(不包含) Func<TLocal> localInit, //返回每个任务初始化的状态 Func<int, ParallelLoopState, TLocal, TLocal> body, //每个迭代调用一次 Action<TLocal> localFinally //对每个任务执行一个最终操作 )
- For<TLocal>(Int32,Int32,Parallel0ptions,Func<TLocal>,Func<Int32,ParallelLoopState,TLocal,TLocal>,Action<TLocal>)
- For(Int32,Int32,Action<Int32>)
- Parallel.ForEach方法用于并行执行foreach循环
- 简单的Parallel.ForEach循环
ForEach<TSource>(IEnumerable<TSource>, Action<TSource>)
- 通过按范围分区加快小型循环体的速度
- 可以使用Partitioner的静态Create方法对源元素创建按范围分区的IEnumerable<T>,然后将此范围集合传递给由常规for循环组成的ForEach方法的循环体
public partial class ParallelForEachExample2 : Page { public ParallelForEachExample2() { InitializeComponent(); } private void btnStart_Click(object sender, RoutedEventArgs e) { textBlock1.Text = "通过分区加快小型循环体的速度示例,结果:n"; var source = Enumerable.Range(0, 100).ToArray(); var rangePartitioner = Partitioner.Create(0, source.Length); double[] results = new double[source.Length]; Parallel.ForEach(rangePartitioner, (range, loopState) => { for (int i = range.Item1; i < range.Item2; i++) { results[i] = source[i] * source[i]; } }); textBlock1.Text += string.Join(", ", results); } }
- 可以使用Partitioner的静态Create方法对源元素创建按范围分区的IEnumerable<T>,然后将此范围集合传递给由常规for循环组成的ForEach方法的循环体
- 简单的Parallel.ForEach循环
- Parallel.Invoke方法用于任务并行
- 语法形式
public static void Invoke(Action[] actions ) public static void Invoke(ParallelOptions parallelOptions, Action[] actions )//参数可以添加并行选项和绑定多个方法或委托
- 使用案例:
public partial class ParallelInvokeExample1 : Page { CancellationTokenSource cts; public ParallelInvokeExample1() { InitializeComponent(); btnHelps.ChangeState(btnStart, true, btnStop, false); } private void btnStart_Click(object sender, RoutedEventArgs e) { textBlock1.Text = ""; btnHelps.ChangeState(btnStart, false, btnStop, true); Action a1 = () => MyMethod("a"); Action a2 = () => MyMethod("b"); Action a3 = () => MyMethod("c"); //在这个例子中,没有使用默认的任务调度程序,而是通过设置并行选项,将任务调度程序与WPF当前同步上下文关联起来,相关代码如下 ParallelOptions options = new ParallelOptions();//如果不将其与WPF当前同步上下文关联在一起,这些用Action定义的多个并行执行的任务在执行期间将无法和WPF界面交互 options.TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); cts = new CancellationTokenSource(); options.CancellationToken = cts.Token; Parallel.Invoke(options, a1, a1, a2, a2, a2, a3); } private void btnStop_Click(object sender, RoutedEventArgs e) { cts.Cancel(); textBlock1.Text += "n任务被取消"; btnHelps.ChangeState(btnStart, true, btnStop, false); } private async void MyMethod(string s) { while (cts.IsCancellationRequested == false) { textBlock1.Text += s; await Task.Delay(100); } } }
- 使用案例:
- 语法形式
- Parallel.For方法用于并行执行for循环:如果不需要取消或总段迭代,或者不需要保持线程本地状态,用此方法最简单;Parallel.For和Parallel.Foreach方法都是用于数据并行
- Parallel帮助器类
- ParallelOptions类:为并行方法提供操作选项
- CancellationToken:获取或设置取消标志
- TaskScheduler:默认值为null
- MaxDegreeOfParallelism:获取或设置允许的最大并行度
- ParallelLoopState类:将Parallel循环的迭代与其他迭代交互(循环状态)
- Break方法:告知Parallel循环尽早停止执行当前迭代之外的迭代
- Stop方法:告知Parallel循环尽早停止执行
- ParallelLoopResult:提供Parallel循环的完成状态
- IsCompleted:获取该循环是否已经完成
- ParallelOptions类:为并行方法提供操作选项
- 用于线程全局变量的数据结构
- 基本概念:线程全局变量,线程局部变量
- 全局变量同步和冲突的解决:用volatile修饰变量,或者使用原子操作(Interlocked类提供的静态方法)
- 并发集合类:常用的并发集合类有ConcurrentBag<T>
** Tips:对程序段进行计时:**
Stopwatch sw = Stopwatch.StartNew();
{
//待计时的代码块
}
sw.Stop();//计时结束
int time = sw.ElapsedMilliseconds;//用Stopwatch对象的ElapsedMilliseconds属性得到毫秒数
sw.Restart();//可以利用Stopwatch对象的Restart()方法可以重新开始即使
{
//待计时的代码块
}
sw.Stop();//再次停止得到计时结果
7 WCF
7.1 预备知识(非重点)
7.1.1
- XML
- 是文本编码,可以在任何网络中正常传输,不受所选用的操作系统、对象模型和编程语言的影响
- 编辑都是自定义的,通过这些自定义的标记,可描述某种数据的不同部分及其嵌套的层次结构
- XML规定所有标记都必须有开始标志和结束标志
Web Service
- 分类:按照数据交换格式的不同
- XML Web Service:
- 一种以XML作为数据交换格式。不论什么操作系统,也不论什么设备,只要能连接到Internet,就能调用服务商(有偿或无偿)提供的服务
- JSON Web Service:
- 以JSON作为数据交换格式的Web服务称为JSON Web Service。一般在Web应用程序通过HTML和JavaScript调用这种服务
JSON格式 “firstname”:“John”
- XML Web Service:
- web服务体系结构
- 服务请求者
- 服务提供者
- 服务注册中心
- web服务涉及:
- SOAP:定义客户端与Web服务交换数据的格式
- WSDL:描述Web服务提供的方法以及调用这些方法的各种方式
- web服务底层基本流程
7.1.3 远程处理(RPC)
- RPC:远程过程调用: 不同计算机上的进程相互访问的一种具体实现
7.1.4 消息队列(MSMQ)
- MQ:多个不同应用程序之间实现相互通信的一种基于队列和事务处理的异步传输模式
- MQ的两种发送方式:快速模式和可恢复模式
- MSMQ是微软实现的MQ
7.1.5 面向服务的体系结构
- SOA:面向服务的体系结构
7.2 WCF(重点)
- WCF基础介绍(非重点):
- WCF是微软公司推出的符合SOA思想的分布式应用程序技术框架和编程模型
- 目标:是实现以下两个实体之间的通信:WCF服务和WCF客户端
- WCF特点:
- 以服务为中心
- 支持多种消息交换模式
- 支持多种传输协议和编码方式
- 支持工作流、事务以及持久性的消息处理统一性、安全性和可扩展性
- 终结点(EndPoint)
- 用于确定网络通信目标,用EndPoint类来实现,在配置文件中用<endpoint>配置节来指定
- 地址(Adress)
- 用于公开服务的位置,即确定终结点的位置
- 地址可以是URL、FTP、网络路径或本地路径
- wcf规定的地址格式:
下面的代码演示了客户端通过tcp访问wcf服务的地址格式//[传输协议]://[位置][:端口][/服务路径] //例如: http://www.mytest.com:50001/MyService http://localhost:8733/Design_Time_Addresses/MyService http://localhost:8080/MyService
net.tcp://localhost:50001/MyService
- 协定(Contract)
- 用于公开提供的是哪种具体服务
- 协定表示客户端和服务端之间的信息交换规则。如果不指定协定,就无法在客户端和服务端之间进行通信
- 服务协定
- 数据协定
- 消息协定
- 协定在接口中用Contract特性来声明,内部用ContractAttribute类来实现,在配置文件中用contract指定
- 绑定(Binding)
- 用于定义客户端和服务端的通信方式
WCF提供了多种绑定方式(BasicHttpBinding、WSHttpBinding、NetTcpBinding、NetNamedPipeBinding、NetMsmqBinding)//完整使用案例 <endpoint address="http://localhost:2338/Service1.svc" binding="basicHttpBinding" contract="WcfService.IService1" />
- 不论是服务端还是客户端,一般都是在单独的配置文件(Web.config、App.config)中配置绑定
- 用于定义客户端和服务端的通信方式
7.2.2 wcf体系结构
- 协定层
- 服务运行时层
- 消息传递层
- 激活和承载层
7.2.3承载wcf的方式(重点)
- WCF本身不能运行,只能将其“宿主”在某个可执行程序中(.dll或者.exe)才能运行
- 承载WCF的方式有三种:
- 1.利用IIS或者WAS承载WCF服务
最常用的承载方式- IIS和IIS Express
- IIS是微软推出的Web应用服务器。
- WCF应用程序开发完成后,将其部署在服务器操作系统(例如Windows Server 2008、Windows Server 2012)的IIS中即可
- 2.利用Windows服务承载WCF服务
- 利用Windows服务进程激活的方式
- Windows服务是Windows操作系统提供的功能,操作系统利用进程控制块来管理它。
- Windows服务一般都是开机自启动的。
- 编写WCF服务程序时,可以利用【WCF服务库】模板将WCF服务制作成单独的DLL文件,调试程序时系统会自动将其宿主到WCF服务主机中来承载WCF服务,并利用Windows进程去自动激活它
- 3.自承载方式
- 比较灵活的承载方式
- 自承载WCF是指开发人员自己编写代码实现承载WCF的工作。
- 本质是利用Windows进程激活服务来承载WCF的,但不是直接用WCF模板来实现承载工作,而是利用.NET框架公开的相关类去实现承载WCF的工作。
- 自承载的优点
- 实现灵活。开发人员可在程序中随时启动、关闭和通过代码配置WCF服务,或者通过程序提供的界面,让用户根据需要随时启动和停止服务。
- 可以通过代码选择多种基础传输协议(例如HTTP、TCP、UDP等),也可以通过代码来配置服务。
部署自承载程序时,需要的环境支持要求最小。
- 自承载的缺点
- 所有承载的实现代码都需要程序员自己去编写。
- 该方案不是面向服务的企业级分布式解决方案。
- 1.利用IIS或者WAS承载WCF服务
7.3 wcf服务端和客户端编程基础
7.3.1 ** WCF服务器端编程模型**
- 编写wcf服务端程序有4个主要步骤:
- 选择承载方式
- 编写HTTP应用程序时,一般选择【WCF服务应用程序】模板。
- 编写TCP应用程序时,既可以选择自承载方式,也可以选择【WCF服务应用程序】模板。
- 编写UDP应用程序时,既可以用自承载方式,也可以用标准绑定
- 设计和实现协定的方式
- 方式一:
- 用一个接口公开多个方法,再用一个类实现接口中声明的所有方法。(建议)
- 可以实现多继承
- 代码改动量小
- 版本升级简单
- 用一个接口公开多个方法,再用一个类实现接口中声明的所有方法。(建议)
- 方式二:
- 全部用类来实现,不使用接口。
- 优点:简单、直观
- 缺点:多继承无法实现、版本升级困难
- 全部用类来实现,不使用接口。
- 方式一:
- 配置服务
- 方式1:通过修改配置文件(Web.config或者App.config)(建议的方式)
- 优点:部署服务端应用程序时,不需要修改源程序,只需要修改配置文件即可。
- 方式2:开发人员自己编写代码
- 方式1:通过修改配置文件(Web.config或者App.config)(建议的方式)
- 承载服务
- 服务端设计完成后,运行(承载)服务即可,此时客户端就可以和服务端交互
- 选择承载方式
7.3.2 wcf客户端编程模型
推荐使用 wpf应用程序
- 编写wcf程序的主要步骤如下:
- 1、利用服务端配置生成客户端代理类和客户端配置
- 运行WCF服务后,客户端可通过【添加服务引用】的办法,让系统自动生成客户端代理类,此时它会根据服务端配置(Web.config或者App.config)自动修改客户端配置(App.config)
- 2、编写客户端代码
- 客户端添加服务引用后,即可利用自动生成的客户端代理类,编写代码与WCF交互。
- 3、更新客户端配置,关闭客户端代理类
- 如果服务端配置文件(Web.config或者App.config)发生了改变,或者接口发生了改变,此时需要在客户端更新服务引用,以便让系统重新生成新的客户端配置(App.config);最后要记得关闭客户端代理类。
- 1、利用服务端配置生成客户端代理类和客户端配置
7.3.3 整体wcf服务端和客户端程序的基本步骤:
- **编写WCF程序的基本步骤如下:
**- 创建服务端项目和客户端项目
- 编写服务端代码
- 修改服务端配置
- 测试服务(可选)
- 在客户端添加服务引用
- 编写客户端调用代码
- 更新服务引用(可选)
- 创建服务端项目和客户端项目
7.4 设计和实现协定
7.4.1 特性
wcf中最常用的协定是服务协定和数据协定,通过特性声明
- 特性:用来声明服务端定义的所有协定
- 在C#中,所有特性类都是从Attribute类继承而来的,而且其名称都有Attribute后缀。
- 用C#编写代码时,一律用中括号来声明特性类,声明时省略Attribute后缀,这是建议的用法。
- 特性类的用途是为紧跟在它后面的目标元素提供设计行为。比如对某个字段声明了某个特性,则该特性的目标元素就是这个字段。
7.4.2 服务协定
服务协定:指wcf对客户端公开哪些服务
- 包括以下内容:
- 操作方法
- 消息交换模式
- 采用的通信协定以及序列化格式
服务协定
- ServiceContract:用于在应用程序中定义服务协定
- 常用属性
- CallbackContract:获取或设置双工通信的回调协定类型,默认为null。
- Name和Namespace:获取或设置Web服务描述语言(WSDL)中<portType>元素的名称和命名空间。
- HasProtectionLevel:获取一个bool类型的值,该值指示是否对成员分配了保护级别。如果分配了保护级别(非None)则为true,否则为false。
- ProtectionLevel:设置绑定支持的保护级别,默认值为ProtectionLevel.None。可选择的值有:
- EncryptAndSign(对数据进行加密和签名确保所传输数据的保密性和完整性)
- None(仅身份验证)
- Sign(对数据签名确保所传输数据的完整性)
- SessionMode:获取或设置采用的会话模式
- OperationContract:用于在应用程序中定义操作协定
- 常用属性
- IsOneWay:获取或设置是否不应答消息,默认为false(返回应答的消息)
- IsInitiating:获取或设置一个布尔值,该值指示接口中的方法是否在服务端启动会话时就可以实现操作,默认为true
- 基本用法:
- 在接口的前面用ServiceContract特性声明服务协定,在接口的内部用操作协定公开操作方法;在对应类中实现接口声明的方法:
//接口内部用操作协定公开操作方法 [ServiceContract(Namespace = "WcfServiceExamples")] public interface IService1 { [OperationContract] double Add(double n1, double n2); [OperationContract] double Divide(double n1, double n2); } //在对应类中实现接口声明的方法 public class Service1 : IService1 { public double Add(double d1, double d2) { return d1 + d2; } public double Divide(double d1, double d2) { return d1 / d2; } }
- 在接口的前面用ServiceContract特性声明服务协定,在接口的内部用操作协定公开操作方法;在对应类中实现接口声明的方法:
- 常用属性
7.4.3 数据协定
数据协定:数据协定是服务端与客户端之间交换数据的约定,即用某种抽象方式描述要交换的数据并将其传输到客户端
- 数据协定默认采用XML格式
- 通过数据协定,客户端和服务端不必共享相同的类型,而只需共享相同的数据协定即可
- 数据协定的特性声明:
- DataContract特性定义哪些类可以被序列化
- DataMember特性用于声明类中的哪些成员可被序列化
[DataContract] public class MyData1{ [DataMember] public int Age; }
- 数据协定的基本用法:
- 显示声明:利用特性声明
- 隐式声明:
- WCF会自动对具有public修饰符的类、结构、枚举等应用数据协定,对具有public修饰符的字段和同时具有get和set的属性应用成员协定
- 注意问题:
- 属性的限制
- 将DataMember特性应用于属性时,该属性必须同时具有get和set
用隐式声明时,凡是具有public修饰符的字段,都应该用属性来表示。(即用get set封装的字段) - 如果直接用public修饰符的字段来表示,必须使用显式声明
- 将DataMember特性应用于属性时,该属性必须同时具有get和set
- 构造函数的处理
- 不要在客户端直接创建服务端提供的类的实例,而是通过客户端代理类来调用
- 静态成员的处理
- 只能将DataMember特性应用于字段和属性,应用在静态成员上将被忽略。
- 属性的限制
7.4.4 消息协定
在有些情况下,需要用单个类型来表示整个消息。使用消息协定可以避免在XML序列化时产生不必要的包装
特性声明:
-
MessageHeader
通过MessageHeader特性(MessageHeaderAttribute类)指定消息头
-
MessageMember
-
通过MessageBodyMember特性(MessageBodyMemberAttribute类)指定消息体
- 可以对所有字段、属性和事件应用MessageHeader特性和MessageBodyMember特性,而与这些字段、属性和事件的访问修饰符无关,即不论是public、private、protected还是internal,都能使用这两个特性
- 如果类型中既包含消息协定又包含数据协定,则只处理消息协定
wcf项目实例演示
8 wcf和http应用编程
- 位于应用层
- HTTP的特点
- HTTP以TCP方式工作。
- HTTP客户端首先与服务器建立TCP连接,然后客户端通过套接字发送HTTP请求,并通过套接字接收HTTP响应。
- HTTP是无状态的
- “无状态”的含义是,客户端发送一次请求后,服务器并没有存储关于该客户端的任何状态信息。即使客户端再次请求同一个对象,服务器仍会重新发送这个对象,而不管原来是否已经向该客户端发送过这个对象。
- HTTP使用元信息作为标头
- HTTP通过添加标头(Header)的方式向服务器提供本次HTTP请求的相关信息,即在主要数据前添加一部分信息,称为元信息(Metainformation)
- HTTP以TCP方式工作。
8.1.2 http的请求与响应
http的通信包括请求和响应
- http请求:
- 最基本的请求类型:
- GET:
- 请求获取特定的资源,例如,请求一个Web页面,除了页面位置作参数之外,这种请求还可以跟随协议的版本如HTTP/1.0等作为参数,以发送给服务器更多的信息
- POST:
- 请求向指定资源提交数据进行处理(例如,提交表单或者上传文件),请求的数据被包含在请求体中
- POST请求一般用于客户端填写包含在Web表单(Form)中的内容后,将这些填入的数据以POST请求的方式发送给服务器
- 当用户通过客户端浏览器在web页面中填入数据,然后点击提交按钮时,客户端向服务器发送的就是post请求
- HEAD:
- HEAD请求在客户端程序和服务器端之间进行交流,而不会返回具体的文档。
- 因此HEAD方法通常不单独使用,而是和其他的请求方法一起起到辅助作用。
- GET:
- 设置请求方式:
- 可以用HttpWebRequest的Method属性设置请求的方法。如果不设置,系统默认请求方法为GET
string uri = "http://www.google.cn"; HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri); request.Method = "POST";
- 可以用HttpWebRequest的Method属性设置请求的方法。如果不设置,系统默认请求方法为GET
- 请求格式:
- <request-line> :第1行必须是一个请求行(request line),说明请求的类型、要访问的资源及使用的HTTP版本
- <headers> :标头(header)部分,说明服务器要使用的附加信息,这部分一般由多行组成。
- <blank line> :标头之后是一个空行(blank line)
- [<request-body>] :空行之后是请求的主体(request-body),主题中可以包含任意的数据
- http响应的一般格式为:
- <status-line>
- <headers>
- <blank line>
- [<response-body>]
- 所有HTTP响应的第一行都是状态行,该行内容依次是当前HTTP版本号、3位数字组成的状态码以及描述状态的短语,各项之间用空格分隔。例如:
HTTP/1.1 200 OK ;//状态行
状态码的第一个数字代表当前响应的类型,具体规定如下:- 1xx 消息——请求已被服务器接收,继续处理。
- 2xx 成功——请求已成功被服务器接收、理解、并接受
- 3xx 重定向——需要后续操作才能完成这一请求。
- 4xx 请求错误—请求含有词法错误或者无法被执行。
- 5xx 服务器错误—服务器在处理某个正确请求时发生错误
- 最基本的请求类型:
8.1.3 http编程技术选择
- WebRequest类和WebReaponse类
- HttpWebRequest类和HttpWebResponse类
- 使用wcf来实现http应用编程(建议用法):
- 程序员只需要处理业务逻辑即可,其他工作细节让WCF内部去完成就行了
8.2 http绑定
- 基本绑定:BasicHttpBinding类:
- 在配置文件中用basicHttpBinding元素来配置
- 安全HTTP绑定(WSHttpBinding类):
- WSHttpBinding
- 双工安全HTTP绑定(WSDualHttpBinding类):
- WSDualHttpBinding
8.3 wcf 服务端和客户端的消息交换模式
- 请求应答模式(Action/Reply):
- 客户端发出请求,要等待服务端应答,所以可能会阻塞
- 步骤:
- 创建解决方案
- 定义数据协定:隐式定义数据协定时,注意什么样的类会被应用数据协定,什么字段和属性会被应用成员协定
- 定义服务协定
- 配置服务:Web.config中要修改和添加哪些信息
- 客户端添加引用
- 编写客户端代码:怎么添加代理类命名空间的引用
- 单向模式(IsOneWay)(和请求应答模式区别):
- 客户端发出请求,服务端不向客户端返回结果,即使服务端错误,也不会返回结果,有优缺点:
- 优点:速度快界面不卡顿
- 缺点:不接受返回消息,无法发现服务端错误
- 步骤:
- 定义服务协定
- 该模式是通过在操作协定的参数中将IsOneWay属性设置为true来实现的。
例如:[OperationContract(IsOneWay=true)] //单向模式
- 该模式是通过在操作协定的参数中将IsOneWay属性设置为true来实现的。
- 编写客户端代码
- 定义服务协定
- 客户端发出请求,服务端不向客户端返回结果,即使服务端错误,也不会返回结果,有优缺点:
- 双工通信(重点)
- 双工(duplex)是指客户端和服务端都可以主动呼叫对方。在这种通信模式中,WCF利用双向绑定实现服务端和客户端相互公开终结点的信息。
- 双工通信主要设计思想和实现步骤:
-
修改服务端配置,选择合适的绑定方式
在Web.config中的<ProtocolMapping>节点添加支持
<protocolMapping> <add binding="wsDualHttpBinding" scheme="http" /> </protocolMapping>
-
在服务端声明和实现接口
-
双工通信由两个接口组成
- 服务接口用于服务(客户端调用、在服务端实现)
- 回调接口用于回调(服务端调用、在客户端实现)
例如:
[ServiceContract(SessionMode=SessionMode.Required, CallbackContract=typeof(IService1DuplexCallback))] public interface IService1Duplex //服务接口,服务端向客户端公开的方法 { ...... } public interface IService1DuplexCallback //回调接口,客户端向服务端公开的方法 { ...... }
服务协定的两个参数要注意:
-
会话模式SessionMode :
SessionMode = SessionMode.Required
-
双工通信回调类型 CallbackContract:
CallbackContract = typeof(IService1DuplexCallback)
服务接口在服务端实现
如果在服务端要调用回调接口向客户端发送消息,先要创建回调通道。之后通过回调通道调用客户端接口方法
例如:
public class Service1 : IService1Duplex { ...... IStudentsDuplexCallback Callback=OperationContext.Current.GetCallbackChannel<IStudentsDuplexCallback>(); } }
-
-
在客户端实现回调接口
-
在客户端实现中,必须有一个类实现服务端定义的双工协定回调接口,以便服务端利用它主动向该客户端发送信息
-
由于服务端是通过客户端对象调用客户端的回调操作来实现与客户端的通信的,因此,在客户端编写和服务端通信的代码时,首先需要创建InstanceContext类的一个实例,以便让服务端通过该实例知道通信的是哪个客户端对象
例如:
InstanceContext site = new InstanceContext(this); Service1DuplexClient client = new Service1DuplexClient(site);
-
-
9 tcp编程
双工、可靠
- 特点:
- 一对一通信
- 安全顺序传输
- 通过字节流收发数据
- 传输的数据无消息边界
- TCP是将数据组装为多个数据报以字节流的形式进行传输,因此可能会出现发送方单次发送的消息与接收方单次接收的消息不一致的现象
- 实现技术:(高版本推荐wcf和tcpclient、tcplistener)
- 用Socket类实现
- TCP通信过程中的所有细节全部通过自己编写的程序来控制。
- 特点:方式灵活,但需要程序员编写的代码多。
- 建议:定义一些新的协议或者对底层的细节进行更灵活的控制时使用此技术
- 用TcpClient和TcpListener以及多线程实现
- TcpClient和TcpListener类对Socket进一步封装后的类,在一定程度上简化了用Socket编写TCP程序的难度,但灵活性也受到一定的限制。
- 特点:TCP数据传输过程中的监听和通信细节(比如消息边界问题)仍然需要程序员自己通过代码去解决。
- 用TcpClient和TcpListener以及多任务实现
- 编写TCP应用程序时,不需要开发人员考虑多线程创建、管理以及负载平衡等实现细节,只需要将多线程看作是多个任务来实现即可
- 用WCF实现
- 监听和无消息边界等问题均有WCF内部自动完成。
- 程序员只需要考虑传输过程中的业务逻辑即可。另外,利用WCF还可以实现自定义的协议
- 用Socket类实现
9.2.1 TcpClient类和TcpListener类
- TcpClient类用于提供本地主机和远程主机的连接信息(用于客户端和服务端)
- 位于System.Net.Sockets命名空间下。
- 提供的构造函数主要用于客户端编程,而服务器端程序是通过TcpListener对象的AcceptTcpClient方法得到TcpClient对象的,不需要在服务端创建对象。
- 构造函数(多个重载)
- TcpClient( )
- 用不带参数的构造函数创建TcpClient对象时,系统会自动分配IP地址和端口号,例如:
TcpClient tcpClient=new TcpClient( ); tcpClient.Connect("
www.abcd.com", 51888);
- 用不带参数的构造函数创建TcpClient对象时,系统会自动分配IP地址和端口号,例如:
- TcpClient(string hostname,int port)
- 自动为客户端分配IP地址和端口号,并自动与远程主机建立连接。
例如:
TcpClient tcpClient = new TcpClient("
www.abcd.com", 51888);
- 自动为客户端分配IP地址和端口号,并自动与远程主机建立连接。
- TcpClient(AddressFamily family)
- 这种构造函数创建的TcpClient对象也能自动分配本地IP地址和端口号,但是它使用AddressFamily枚举指定使用哪种网络协议(IPv4或者IPv6)。
例如:
TcpClient tcpClient = new TcpClient(AddressFamily.InterNetwork); tcpClient.Connect("
www.abcd.com", 51888);
- 这种构造函数创建的TcpClient对象也能自动分配本地IP地址和端口号,但是它使用AddressFamily枚举指定使用哪种网络协议(IPv4或者IPv6)。
- TcpClient(IPEndPoint iep)
- 该构造函数的参数iep用于指定本机(客户端)IP地址与端口号。当客户端有一个以上的IP地址时,如果程序员希望指定IP地址和端口号,可以使用这种方式。
例如:
IPAddress[] address = Dns.GetHostAddresses(Dns.GetHostName( )); IPEndPoint iep = new IPEndPoint(address[0], 51888); TcpClient tcpClient = new TcpClient(iep); tcpClient.Connect("
www.abcd.com", 51888);
- 该构造函数的参数iep用于指定本机(客户端)IP地址与端口号。当客户端有一个以上的IP地址时,如果程序员希望指定IP地址和端口号,可以使用这种方式。
- TcpClient( )
- TcpListener类用于监听客户端连接请求(服务端)
- TcpListener类用于在服务端监听和接收客户端传入的连接请求
- 构造函数:
- TcpListener(IPEndPoint iep)
- 通过IPEndPoint类型的对象在指定的IP地址与端口监听客户端连接请求,iep包含了本机的IP地址与端口号。
- TcpListener(IPAddress localAddr, int port)
- 直接指定本机IP地址和端口,并通过指定的本机IP地址和端口监听客户端传入的连接请求。
- TcpListener(IPEndPoint iep)
- 在同步工作方式下,对应有Start方法、Stop方法、AcceptSocket方法和AcceptTcpClient方法。另外,与这些同步方法对应的异步方法都有Async后缀。
- Start方法
- TcpListener对象的Start方法用于启动监听。
public void Start( ) public void Start(int backlog)
- TcpListener对象的Start方法用于启动监听。
- Stop方法
- TcpListener对象的Stop方法用于关闭TcpListener并停止监听请求,语法如下。
public void Stop( )
- TcpListener对象的Stop方法用于关闭TcpListener并停止监听请求,语法如下。
- AcceptTcpClient方法
- 用于在同步阻塞方式下获取并返回一个封装了Socket的TcpClient对象,同时从传入的连接队列中移除该客户端的连接请求。得到该对象后,就可以通过该对象的GetStream方法生成NetworkStream对象,再利用NetworkStream对象与客户端通信。
- AcceptSocket方法
- 用于在同步阻塞方式下获取并返回一个用来接收和发送数据的Socket对象,同时从传入的连接队列中移除该客户端的连接请求。
- 简单应用只需要调用AcceptTcpClient,如果需要更细化的行为控制,则用AcceptSocket来实现。
- Start方法
- 用TcpListener和TcpClient编写TCP应用程序的一般步骤
- 服务端:
- 1)创建一个TcpListener对象,然后调用该对象的Start方法在指定的端口进行监听。
- 2)在单独的线程中,循环调用TcpListener对象的AcceptTcpClient方法接收客户端连接请求,并根据该方法返回的结果得到与该客户端对应的TcpClient对象。
- 3)每得到一个新的TcpClient对象,就创建一个与该客户端对应的线程,然后通过该线程与对应的客户端通信。
- 4)根据传送信息的情况确定是否关闭与客户端的连接。
- 客户端:
-
1)利用TcpClient的构造函数创建一个TcpClient对象,并利用该对象与服务端建立连接。
-
2)利用TcpClient对象的GetStream方法得到网络流,然后利用该网络流与服务端进行数据传输。
-
3)创建一个线程监听指定的端口,循环接收并处理服务端发送过来的信息。
-
4)完成通信工作后,向服务端发送关闭信息,并关闭与服务器的连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oE4ge6ST-1670832672770)(image/image_gKy-Lpa85Y.png)]
-
- 服务端:
- 解决tcp无消息边界问题的办法
- (1)发送固定长度的消息
- 这种办法适用于消息长度固定的场合。
- (2)将消息长度与消息一起发送
- 一般在每次发送的消息前面用4个字节表明本次消息的长度,然后将其和消息一起发送到对方;对方接收到消息后,首先从前4个字节获取实际的消息长度,再根据消息长度值依次接收发送方发送的数据。
- 这种办法适用于任何场合。
- (3)使用特殊标记分隔消息
- 使用特殊分隔符对消息进行分隔。
- 这种办法主要用于消息本身不包含特殊标记的场合。
- (1)发送固定长度的消息
9.3.1 wcf与tcp相关的绑定
利用WCF编写TCP应用程序时,只需要在服务端配置文件中设置相关的绑定,就可以轻松实现相应的功能,而且不容易出错
- 1.NetTcpBinding
- NetTcpBinding类用于将WCF和TCP绑定在一起,并以服务的形式提供TCP服务端和客户端之间的通信。
- 在服务端配置文件中,用<netTcpBinding>元素来配置
- 在服务端配置文件中,<netTcpBinding>元素默认配置如下:
- 安全模式:Transport(保证传输安全)。
- 消息编码方式:Binary(采用二进制消息编码器)。
- 传输协议:TCP。
- 会话:提供传输会话(也可以配置为可靠对话)。
- 事务:无事务处理功能(也可以配置为支持事务处理)。
- 双工:支持。
- 2.其他与TCP相关的绑定
- 除了NetTcpBinding以外,WCF与TCP相关的绑定还有MexTcpBinding、NetTcpContextBinding。
- WCF有一个约定:凡是具有Net前缀的绑定默认都使用二进制编码器对消息进行编码,而不带Net前缀的绑定则默认使用文本消息编码。
最后
以上就是不安盼望为你收集整理的c#网络应用编程知识点c#网络应用编程的全部内容,希望文章能够帮你解决c#网络应用编程知识点c#网络应用编程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复