重庆熊猫 Loading

.NET教程 - 网络(Network)

更新记录
转载请注明出处:
2022年10月4日 发布。
2022年10月1日 从笔记迁移到博客。

基础概念

网络协议

网络协议是网络上所有设备(服务器、计算机及交换机、路由器等)间通信规则集合

它定义了通信时信息必须采用的格式以及这些格式的含义

网络协议通常被分为几个层次,每层都完成独立的功能

TCP/IP(Transmission Control Protocol/Internet Protocol, 传输控制协议/网际协议)

是通信层上一组网络协议的总称,在网络中使用最广泛

通信模式

分散式、集中式与分布式

计算机网络中为了有效利用计算机资源,一般将数据通信模型分为:

分散式( Decentralized)、集中式 (Centralized)、分布式(Distributed)

分散式(Decentralized)

在分散式系统中,用户只负责管理自己的计算机系统

各自独立的系统之间没有资源或信息的交换或共享

分散式优点

分散式系统由于各计算机相互独立,即使某一台出现故障,也不会导致整个系统瘫痪

分散式缺点

存在大量共享数据的重复存储

容易导致一个企业组织内各部门数据的不一致性

造成硬件、支持和运营维护等成本的大量增加

集中式 (Centralized)

在集中式环境中,用一台主计算机保存一个企业组织的全部数据

而用户则通过终端连接到这台主计算机系统并与之通信,从而达到访问数据的目的

集中式系统的优点

是所有运作和管理处于单个部门的主持与控制之下,硬件成本低

另外,由于资源集中,既促进和方便了用户间的数据共享

又减小或消除了数据的冗余与不一致性

集中式系统的缺点

可靠性不如分散式

一旦主机群出现故障,所有系统就全部瘫痪

集中式环境不能充分满足各个部门或用户的计算需要

无法满足特殊部门的编程需求

系统响应较慢

分布式(Distributed)

分布式系统是分散式系统和集中式系统的混合

由一个又一个连接起来的独立计算机组成

分布式系统与计算机网络的主要区别是,分布式环境中资源以透明的方式供用户使用。例如:用户打开并编辑一个文件,在分布式系统中,用户不必理会该文件来自哪台计算机,用起来就好像使用单机系统一样。而在网络环境中,用户必须先知道哪台远程主机保存该文件,然后与该远程主机连接,再传送该文件到本地主机进行编辑

形象比喻

分散式系统、集中式系统和分布式系统的主要区别可以用一个形象的例子来描述,假如一座大楼有好几个部门,如果希望统一解决这座大楼的制暖问题,这就存在多种选择方案。

第 1种方式是分散式,即每个房间根据自己情况配备空调、加热器或者什么都不配,这种方式办公室人员自由度大,但大楼管理员不高兴,因为他们无法统一控制

第2 种方式是集中式,即整个大楼只装备一个恒温制暖的中央空调,所有房间均由这个中央空调来控制,这种方式大楼管理员高兴,但办公室人员不高兴,因为办公室无法单独控制房间的温度

第 3 种方式是分布式,即每个部门各装备一个中央空调,每个中央空调只控制一个部门的房间温度,而由大楼管理员控制中央空调,由于这种方式下各个部门可以根据情况调在自己的温度,大楼管理员又能在一定程度上进行管理,因此大家都高兴。

再举个例子,企业管理系统就是集中式和分布式综合的一种表现,所有数据用专用的数据库(SQLServer, Oracle或者DB2 )集中存储,属于集中式,而对数据的处理则由各个部门的软件分别控制,属于分布式

C/S 模型

C/S (Client/server)模型也叫C/S模式,它是在分散式、集中式以及分布式的基础上发展起来的一种新的模型,目前的大多数网络通信及应用都属于这种模型

C / S 模型将一个网络事务处理分为两部分,一部分是客户端(Client), 它为用户提供向网络请求服务的接口;另一部分是服务器端(Server), 它负责接受用户对服务的请求,并将这些服务透明地提供给用户

C /S模型的优点是它既适用于实际应用程序,又适用于真正的计算装置。举例来说,我们到饭店吃饭时,要先提出请求(吃什么),属于客户端,饭店服务员根据请求提供相应的服务,属于服务器端。至于相应的饭菜是由哪个厨师做出来的(哪个专用服务器提供的),则由饭店的服务员去联系,而客户只需要和服务员打交道就行了

从程序实现的角度来说,客户端和服务器端打交道,实际是两个进程在打交道。服务器端启动 Server进程,并等待客户端与其联系,而客户端则启动客户进程和服务器进程打交道。当服务器进程处理完一个客户进程请求的信息之后,又接着等待其他客户的请求

​ 运行具体Server进程的计算机系统一般通过所提供的服务来命名。例如,接受和提供邮件服务的主机称为邮件服务器;为用户提供远程文件访问的计算机称为文件服务器等

B/S 模型

B/S (Browse/Server) 模型也叫B/S模式,它是一种基于Web的通信模型,使用HTTP (Hypertext Transfer Protocol,超文本传送协议)通信。

B/S是一种特殊的C/S模型,特殊之处就在于这种模型的客户端一般是某种流行的浏览器。B/S模型的优点就是单台计算机可以访问任何一个Web服务器,或者说其客户端程序是通用的,对于用户来说,只需要知道服务器的网址即可访问,而不需要针对不同的服务器分别提供专用的客户端软件。

P2P 模型

P2P(Peer-to-Peer,对等互连)是近年来比较流行的通信模型之一。在 Peer-to-Peer环境中,每个联网的计算机同时运行一个应用程序的Client部分和Sever部分。或者说,一个应用程序既起 Server的作用,又起Client的作用。

P2P模型的优点是配置容易,通信方便,成本低;缺点是可靠性不如C/S模型,遭受黑客攻击的可能性比C/S模型高。

TCP/IP网络协议

参考模型

OSI (Open System Interconnect,开放式系统互连)七层参考模型,它将一种通信协议抽象为7层,每一层执行某一特定的任务。由于OSI模型过于复杂,因此在实际应用中很少使用。

最广泛的则是TCP/IP (Transmission Control Protocol/Internet Protocol, 传输控制协议/网际协议)参考模型。TCP/IP与OSI最大的不同在于OSI是一个理论上的网络通信模型,而 TCP/IP则是实际运行的网络协议。

TCP/IP四层模型和OSI七层模型对应表

image

TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。这4层分别为:应用层、传输层、网际层和网络接口层

应用层

该层负责应用程序之间的沟通

主要协议有简单邮件传输协议(SMTP)、文件传输协议(FTP)以及网络远程访问协议(Telnet)等

传输层

该层提供节点间的数据传送以及应用程序之间的通信服务

主要功能是数据格式化、数据确认和丢失重传等

主要协议有传输控制协议(TCP),用户数据报协议(UDP)等

TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据

并且确定数据已被送达并接收。数据包可以简单地理解为“打包以后的数据

网际层

该层负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机

但它不检查数据包是否被正确接收

网际协议(IP) 即属于该层协议

网络接口层

该层负责接收IP数据报并进行传输,从网络上接收物理帧

抽取IP数据报转交给下一层,对实际的网络媒体进行管理

定义如何使用实际网络(如 Ethernet、Serial Line等 ) 来传送数据

数据报可以简单地理解为“生成报告后的数据”

IP协议

IP (网际协议)是TCP/IP的心脏,也是网络层中最重要的协议

网际层接收由更低层的网络接口层(如以太网设备驱动程序)发来的数据包,并把该数据包发送到更高层,即发送到传输层。相反,网际层也把从传输层接收来的数据包传送到网络接口层。IP数据包是不可靠的,因为IP并没有做任何事情来确认数据包是按顺序发送的或者没被破坏。IP数据包中含有发送它的主机的地址(源地址)和接收它的主机的地址(目的地址)

高层的TCP和UDP服务在接收数据包时,通常假设包中的源地址是有效的。或者说,IP地址形成了许多服务的认证基础,让这些服务相信数据包是从一个有效的主机发送来的。为了便于网络通信测试,IP认证还包含了一个称为IP source routing的选项。用来指定一条源地址和目的地 址之间的直接路径

IP对于网络通信有着重要的意义,网络中的计算机通过安装IP软件。使许许多多的局域网络构成了一个庞大而又严密的通信系统。从而使Internet看起来好像是真实存在的,但实际上它 是一种并不存在的虚拟网络。只不过是利用IP把世界上所有愿意接入Internet的计算机局域网络连接起来,以便相互之间都能够通信。

TCP协议

尽管计算机通过安装IP软件,保证了计算机之间可以发送和接收资料,但 IP协议还不能解决资料分组在传输过程中可能出现的问题。因此,连接Internet的计算机还需要安装TCP来提供可靠的并且无差错的通信服务。

Internet是一个庞大的国际性网络,网络上的阻塞和空闲时间总是交替不定的,加上传送的距离不同,所以传输资料所用时间也会变化不定。而TCP则具有自动调整“超时”的功能,因此能很好地适应Internet上各种各样的变化,从而确保传输数据的正确性。换句话说,IP协议只保证计算机能发送和接收分组资料,而TCP协议则提供一个可靠的、可流控的、全双工的信息流传输服务。

如果IP数据包中有已经封好的TCP数据包,那么IP将把它们传送到传输层。TCP将包排序并进行错误检查,同时实现虚电路间的连接。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。

TCP接受到数据包后,将信息送到更高层的应用程序,如Telnet的服务程序和客户程序。应用程序处理后,再轮流将信息送回传输层,传输层再将它们向下传送到网际层(设备驱动程序和物理介质),最后到接收方。

可见,IP和 TCP这两个协议的功能是不一样的,虽然可以分开单独使用,但它们是在同一时期作为协议整体来设计的,并且在功能上也是互补的。只有两者的结合,才能保证Internet在复杂的环境下正常运行。凡是要连接到Internet的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称作TCP/IP

UDP协议

UDP与TCP位于同一层,但它不考虑数据包的顺序、错误或重发。因此,UDP不被应用于那些使用虚电路的面向连接的服务,即 UDP主要用于那些面向查询/应答的服务,如NFS。相对于FTP或 Telnet,这些服务需要交换的信息量较小。使用UDP的服务包括NTP(网络时间协议) 和DNS。当然,DNS也使用TCP

ICMP协议

ICMP与 IP位于同一层,它被用来传送IP的控制信息。它主要是用来提供有关通向目的地址的路径信息。ICMP的“Redirect”信息通知主机通向其他系统的更准确的路径,而“Unreachable”信息则指出路径有问题。另外,如果路径不可用了,ICMP可以使TCP连接“体面地”终止。例如,Ping程序就是最常用的基于ICMP的服务

TCP/IP模型各个层次的功能和协议

image

IP地址转换与域名解析

基础概念

IP地址和域名解析

在因特网中,为了确定一台主机的位置,每台联网的主机都要有一个在全世界范围内唯一的标识符,该标识符称为IP地址。但是由于IP地址难于记忆,因此人们又使用了便于记忆的域名来代替IP地址。而仅凭域名并不能直接找到远程主机,所以还需要有一个把域名转换成为网络可以识别的IP地址的过程,这个转换过程称为域名解析

IP地址与端口

对于网络上的两台计算机来说,用户操作的计算机称为本地主机,与该计算机通信的另一台计算机称为远程主机。识别远程主机的信息主要由两部分组成,一部分是主机标识,用于识别与本地主机通信的是哪台远程主机;另一部分是端口号,用于识别和远程主机的哪个进程通信

IP地址

一个IP地址主要由两部分组成,一部分用于标识该地址所属的网络号,另一部分指明网络内的主机号。网络号由因特网权力机构分配,主机号由各个网络的管理员统一分配。目前,IP编址方案有两种.一种是采用IPv4编址方案,即使用由4个字节组成的二进制值 进行识别,我们常见的形式是将4个字节分别用十进制表示,中间用圆点分开,这种方法叫做点分十进制表示法。

对于IPv4编址方案, 网络地址分配有以下原则:

(1)网络地址必须唯一

(2)网络标识不能以数字127开头,以数字127开头的地址用于内部回送函数

(3)网络标识的第一个字节不能为255,第一个字节为255表示广播地址

(4)网络标识的第一个字节不能为0 ,第一个字节为0 表示该地址是本地主机

对于IPv4编址方案,主机地址分配有以下原则:

(1)主机标识在同一网络内必须是唯一的

(2)主机标识的各字节不能全为255,全为255表示该机地址是广播地址

(3)主机标识的各字节不能全为0 ,全为。表示“只有这个网络”,而这个网络上没有任何主机

使用IP地址的点分十进制表示法,因特网地址空间又划分为5类,具体如下:

A类:0.x.x.x ~ 127.x.x.x    (32位二进制最高位为0)
B类:128.X.X.X ~ 191.x.x.x  (32位二进制最高2位为10)
C类:192.x.x.x ~ 223.x.x.x  (32位二进制最高3位为110)
D类:224.x.x.x ~ 239.x.x.x  (32位二进制最高4位为1110)
E类:240.x.x.x ~ 255.x.x.x  (32位二进制最高5位为11110)

A 类 IP地址由1字节的网络地址和3 字节主机地址组成,网络地址的最高二进制位必须是“0”,可用网络标识长度为7位。后3个字节为主机标识。A 类 IP地址主要用于网内主机数达1 600多万台的大型网络。

B 类 IP地址由2字节的网络地址和2字节主机地址组成,网络地址的最高二进制位必须是 “10” ,可用网络标识长度为14位。后2个字节为主机标识。B类网络地址适用于中等规模的网络,每个网络所能容纳的计算机数为6万多台。

C 类IP地址由3 字节的网络地址和1 字节主机地址组成,网络地址的最高位必须是“110”,可用网络标识长度为21位。最后1个字节为主机标识。C 类网络地址适用于小规模的局域网,每个网内最多只能包含254台计算机。

D 类地址属于一种特殊类型的IP地址,TCP/IP规定,凡IP地址中的第一个字节以111110"开始的地址都叫多点广播地址。因此,任何第T 字节大于223小于240的1P地址都是多点广播地址。E 类 1P地址则以**11110"开头,作为特殊用途使用。还有一个特殊的IP地址范围,即 127.0.0.1~127.1.1.1,专门用于回路测试。

在这些网络分类中,每类网络又可以与后面的一个或多个字节组合,进一步分成不同的网络,称为子网。每个子网必须用一个公共的网址把它与该类网络中的其他子网分开。

为了识别IP地址的网络部分,又为特定的子网定义了子网掩码。子网掩码用于屏蔽IP地址的一部分以区别网络标识和主机标识,它是判断任意两台计算机的IP地址是否属于同一子网的依据,并说明该IP地址是在局域网上,还是在远程网上。把所有的网络位(二进制)用1来标识,主机位用来0标识,就得到了子网掩码。

IP地址与子网掩码的关系可以简单地理解为:两台计算机各自的IP地址与子网掩码进行二进制“与”运算后,如果得出的结果是相同的,则说明这两台计算机处于同一个子网,否则就是处于不同的子网上。

第2种IP地址编址方案是采用IPv6编址方案。在这种编址方案中,每个IP地址有128位(16个字节),这样就彻底解决了IP地址不足的问题。

端口

从表面上看,好像知道了远程主机的IP地址,本机就能够和远程主机相互通信。其实真正相互完成通信功能的不是两台计算机,而是两台计算机上的进程。IP地址仅仅能够具体标识到某台主机,而不能标识某台计算机上的进程。如果要标识具体的进程,需要引入新的地址空间,这就是端口(Port)。

在网络技术中,端口有两种含义:一是指物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器上连接其他网络设备的接口,如RJ-45端口、SC端口等;二是指逻辑意义上的端门,即进程标识。端口号的范围从0到65535,比如用于HTTP的80端口,用于FTP的21端口等。

定义端口是为了解决与多个应用程序同时进行通信的问题;它主要扩充了IP地址的概念。假设一台计算机正在同时运行多个应用程序,并通过网络接收到了一个数据包,这时就可以利用端口号(该端口号在建立连接时确定)来区分目标进程。因此,主机A要与主机B相互通信,主机A不仅要知道主机B的IP地址,而且还要知道主机B上某个进程监听的端口号。

由于使用两字节二进制数表示端口地址,因此,可用端口地址的范围是十进制的0~65535。由于1000以内的端口大多被标准协议所占用,所以程序中可以自由使用的端口号一般都用大于1000的值。

域名解析

网络通信必须指定双方机器的IP地址。IP地址虽然能够唯一地标识网络上的计算机,但它是数字型的,很难记忆,因而又用字符型的名字来标识它.这个字符型地址称为域名地址,简称域名(DomainName)。将域名转换为对应IP地址的过程称为域名解析

DNS(Domain Name System,域名系统)是因特网的一项核心服务,它可以将域名和IP地址相互转换。为了实现这种转换,在互联网中存在一些装有域名系统的域名服务器,上面分层次存放许多域名到IP地址转换的映射表。由此,能够使人更方便地访问互联网,而不用去记住被机器直接读取的IP地址

Dns类提供了方便的域名解析功能,它从Internet域名系统检索指定主机的信息。Dns类是一个静态类,只能使用“类名.方法名”的形式使用它所提供的方法。该类提供了一系列静态的方法,可以利用它获取本地或远程主机的域名和IP地址

.NET中IP地址转换相关类

System.Net命名空间为Internet网络上使用的多种协议提供了方便的编程接口

开发人员利用这个命名空间下提供的类,编写符合标准网络协议的网络应用程序时

不需要考虑所用协议的具体细节,就能很快实现所需要的功能

IPAddress类 表示IP地址

IPEndPoint类 表示IP地址和端口号

IPHostEntry类 表示Internet主机提供信息容器

Dns类 表示DNS操作

IPAddress类

说明

提供了对IP地址的转换、处理等功能

命名空间

System.Net

构造函数

用类型为byte数组的地址初始化IPAddress类的新实例

public IPAddress(byte[] address)
//参数中的address指IP地址的字节数组值
//如果address的长度为4,则构造一个IPv4地址;否则构造一个IPv6地址

实例:

byte[] ipArray = new byte[] { 143, 24, 20, 36 };
IPAddress locallP = new IPAddress(ipArray);

用类型为long的地址初始化IPAddress类的新实例

public IPAddress(long newAddress)
//参数中的newAddress指IP地址的长整型值
//例如,Big-Endian格式的值0x2414188f代表的IP地址为0143.24.20.36
//其中,点分十进制IP地址143.24.20.36转化为二进制为10001111.00011000.00010100.00100100
//按照Big-Endian格式低地址存放最高有效字节原则

实例:

long ip = 0x2414188f; 
IPAddress locallP = new IPAddress(ip);

解析字符串IP地址

在程序中使用IPAddress类的构造函数来构造IPAddress实例不仅烦琐而且很不直观

在实际编程中,一般用IPAddress类提供的静态Parse方法将IP地址字符串转换为IPAddress 的实例

实例:

try
{
    IPAddress ip = IPAddress.Parse("143.24.20.36");
}
catch
{
    Console.WriteLine("IP地址有误");
}

IPAddress类的常见只读字段

None            表示提供不使用任何网络接口的IP地址
Loopback        表示IPv4回送地址,等效于127.0.0.1
Any             表示IPv4地址,指示服务器应侦听所有网络接口上的客户端活动,等效于0.0.0.0
Broadcast       表示IPv4网络广播地址.等效于255.255.255.255
IPv6None        表示提供不使用任何网络接口的IP地址
IPv6Loopback    表示IPv6回送地址
IPv6Any         Socket.Bind方法用此字段提供可用的IPv6地址

IPEndPoint 类

说明

IPEndPoint是与IPAddress概念相关的一个类

它包含应用程序连接到主机上的服务所需的主机和端口信息

它由两部分组成,一个是主机IP地址,另一个是端口号

命名空间

System.Net

构造函数

public IPEndPoint(IPAddress address, int port);

实例:

//新建IP地址对象
IPAddress iPAddress = IPAddress.Parse("192.168.1.1");
//新建端点对象
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 65000);
//输出端点对象的IP地址  192.168.1.1
Console.WriteLine(iPEndPoint.Address.ToString());
//输出端点对象的端口号  65000
Console.WriteLine(iPEndPoint.Port);

获得端点的IP地址

//新建IP地址对象
IPAddress iPAddress = IPAddress.Parse("192.168.1.1");
//新建端点对象
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 65000);
//输出端点对象的IP地址  192.168.1.1
Console.WriteLine(iPEndPoint.Address.ToString());

获得端点的端口号

//新建IP地址对象
IPAddress iPAddress = IPAddress.Parse("192.168.1.1");
//新建端点对象
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 65000);
//输出端点对象的端口号  65000
Console.WriteLine(iPEndPoint.Port);

IPHostEntry 类

说明

IPHostEntry类将一个域名系统(DNS)主机名与一组别名和一组匹配的IP地址关联

IPHostEntry类一般和Dns类一起使用

IPHostEntry类的实例中包含了Internet主机的相关信息

成员

该类有AddressList、HostName、Aliases属性

AddressList属性

AddressList属性的作用是获取或设置与主机关联的IP地址列表

这是一个IPAddress类型的数组,包含了指定主机的所有IP地址

HostName属性

HostName属性包含了指定主机的主机名

Aliases属性

Aliases属性包含了与主机关联的别名列表

实例:反向查询

//反向查询指定域名的IP
IPAddress[] iPAddresses1 = Dns.GetHostEntry("panda666.com").AddressList;
foreach (IPAddress iPAddress in iPAddresses1)
{
    Console.WriteLine(iPAddress.ToString());
}

//反向查询本地的IP
IPAddress[] iPAddresses2 = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
foreach (IPAddress iPAddress in iPAddresses2)
{
    Console.WriteLine(iPAddress.ToString());
}

Dns类

Dns类的常用方法

GetHostAddresses()  返回指定主机的IP地址,与该方法对应的还有异步方法
GetHostEntry()      将主机名或IP地址解析为IPHostEntry实例
与该方法对应的还有异步方法
GetHostName()       获取本地计算机的主机名

获取指定主机的IP地址

public static IPAddress[] GetHostAddresses(string hostNameOrAddress);

实例:

//本机的所有 IP 地址
IPAddress[] ips = Dns.GetHostAddresses("");
//指定域名的所有 IP 地址
IPAddress[] ips = Dns.GetHostAddresses("panda666.com");

将主机名或IP地址解析为IPHostEntry实例

public static IPHostEntry GetHostEntry (string hostNameOrAddress)

实例:反向查询指定域名的IP

IPAddress[] iPAddresses1 = Dns.GetHostEntry("panda666.com").AddressList;
foreach (IPAddress iPAddress in iPAddresses1)
{
    Console.WriteLine(iPAddress.ToString());
}

实例:反向查询本地的IP

IPAddress[] iPAddresses2 = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
foreach (IPAddress iPAddress in iPAddresses2)
{
    Console.WriteLine(iPAddress.ToString());
}

获取本地计算机的主机名

实例:

Console.WriteLine(Dns.GetHostName());

网卡信息与网络流量检测

基础概念

网卡

网络适配器又称网卡或网络接口卡(NIC), 是连接计算机与网络的硬件设备

网卡的主要工作原理是整理计算机上发往网线上的数据

并将数据分解为适当大小的数据包之后向网络上发送

网卡信息检测相关类

网卡信息检测实际上是对网络适配器的信息检测

网络适配器可能集成在计算机主板上,也可能在一块单独的网卡上

有时称为网络适配器,有时又将其简称为网卡

.NET命名空间

System.Net.NetworkInformation

提供网络流量和本机网络地址等信息的访问

该命名空间还包含实现Ping实用工具的类

可以使用Ping和相关的类检查是否可通过网络连接到远程计算机

NetworkInterface类

说明

提供网络适配器的配置和统计信息

该类可以方便地检测本机有多少个网卡、哪些网络连接可用

并可获取某个网卡的型号、MAC地址和速度等信息

命名空间

System.Net.NetworkInformation

获得网卡对象

在获取网络适配器相关信息时,首先需要构造NetworkInterface对象。需要注意的是,不能直接使用new关键字构造该类的实例,而是利用NetworkInterface类提供的静态方法GetAllNetworklnterfaces得到Networkinterface类型的数组。对于本机的每个网络适配器,该数组中都包含一个NetworkInterface对象与之对应。

例如:

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获取网络适配器的Id标识
    Console.WriteLine("获取网络适配器的Id标识");
    Console.WriteLine(networkInterface.Id);
}

获得描述本地计算机上的所有网络适配器对象

GetAIINetworklnterfaces() 
public static NetworkInterface[] GetAllNetworkInterfaces()
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获取网络适配器的名称
    Console.WriteLine("获取网络适配器的名称");
    Console.WriteLine(networkInterface.Name);
}

获取网络适配器的Id标识

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获取网络适配器的Id标识
    Console.WriteLine("获取网络适配器的Id标识");
    Console.WriteLine(networkInterface.Id);
}

获取网络适配器的名称

Name属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获取网络适配器的名称
    Console.WriteLine("获取网络适配器的名称");
    Console.WriteLine(networkInterface.Name);
}

获取网络适配器的速度(bit/秒)

Speed属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获取网络适配器的速度(默认:bit/秒)
    Console.WriteLine("获取网络适配器的速度(默认:bit/秒)");
    Console.WriteLine(networkInterface.Speed/1000/1000);
}

获得网络适配器的类型

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获得网络适配器的类型
    Console.WriteLine("获得网络适配器的类型");
    Console.WriteLine(networkInterface.NetworkInterfaceType);
}

获得网络适配器的描述

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获得网络适配器的描述
    Console.WriteLine("获得网络适配器的描述");
    Console.WriteLine(networkInterface.Description);
}

获得网络适配器当前状态(开/关)

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获得网络适配器当前状态(开/关)
    Console.WriteLine("获得网络适配器当前状态(开/关)");
    Console.WriteLine(networkInterface.OperationalStatus);
}

描述此网络适配器配置信息的对象

GetIPProperties() 
public abstract IPInterfaceProperties GetIPProperties()
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
//描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获得网关信息
    Console.WriteLine("获得网关信息");
    foreach (GatewayIPAddressInformation gatewayIPAddressInformation in iPInterfaceProperties.GatewayAddresses)
    {
        //网关的IP地址
        Console.WriteLine(gatewayIPAddressInformation.Address);
    }
    //获得DNS地址信息
    Console.WriteLine("获得DNS地址信息");
    foreach (IPAddress iPAddress in iPInterfaceProperties.DnsAddresses)
    {
        Console.WriteLine(iPAddress.Address);
    }
    //获得DHCP服务器地址
    Console.WriteLine("获得DHCP服务器地址");
    foreach (IPAddress iPAddress1 in iPInterfaceProperties.DhcpServerAddresses)
    {
        
        Console.WriteLine(iPAddress1.Address);
    }
}

获取IPv4统计信息

GetIPv4Statistics()
public abstract IPv4InterfaceStatistics GetIPv4Statistics()
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface networkInterface in adapters)
{
}

检测是否有任何可用的网络连接

GctlsNetworkAvailable()
public static bool GetIsNetworkAvailable()
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    
}

返回适配器的媒体访问控制(MAC)地址

GetPhysicalAddress() 
public abstract PhysicalAddress GetPhysicalAddress()
NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获得网络适配器的MAC地址
    Console.WriteLine("获得网络适配器的MAC地址");
    Console.WriteLine(networkInterface.GetPhysicalAddress());
}

指示接口是否支持指定的协议(IPv4或IPv6)

Supports()

如果支持指定的协议,则为true;否则为回false

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //获得网络是否可用
    //检测是否支持IPv4
    Console.WriteLine("检测是否支持IPv4");
    Console.WriteLine(networkInterface.Supports(NetworkInterfaceComponent.IPv4));
    //检测是否支持IPv6
    Console.WriteLine("检测是否支持IPv6");
    Console.WriteLine(networkInterface.Supports(NetworkInterfaceComponent.IPv6));

}

IPInterfaceProperties 类

说明

IPInterfaceProperties类提供了检测IPv4和 IPv6的网络适配器地址信息,利用该类可检测本机所有网络适配器支持的各种地址。如Dns服务器的IP地址、网关地址以及多路广播地址等。IPInterfaceProperties是一个抽象类,因此不能直接创建该类的实例,而是通过调用Networkinterface对象的GetIPProperties()得到该类的实例。

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    Console.WriteLine("=============================");
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
}   

获取此接口的动态主机配置协议(DHCP)服务器的地址

DhcpServerAddresses属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获得DHCP服务器地址
    Console.WriteLine("获得DHCP服务器地址");
    foreach (IPAddress iPAddress1 in iPInterfaceProperties.DhcpServerAddresses)
    {
        
        Console.WriteLine(iPAddress1.Address);
    }
}

获取此接口的域名系统(DNS)服务器的地址

DnsAddresses属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获得DNS地址信息
    Console.WriteLine("获得DNS地址信息");
    foreach (IPAddress iPAddress in iPInterfaceProperties.DnsAddresses)
    {
        Console.WriteLine(iPAddress.Address);
    }
}

获取与此接口关联的域名系统(DNS)后缀

DnsSuffix属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获取与此接口关联的域名系统(DNS)后缀
    Console.WriteLine("获取与此接口关联的域名系统(DNS)后缀");
    Console.WriteLine(iPInterfaceProperties.DnsSuffix);
}

获取此接口的网关地址

GatewayAddresses属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获得网关信息
    Console.WriteLine("获得网关信息");
    foreach (GatewayIPAddressInformation gatewayIPAddressInformation in iPInterfaceProperties.GatewayAddresses)
    {
        //网关的IP地址
        Console.WriteLine(gatewayIPAddressInformation.Address);
    }
}

获取分配给此接口的任意广播IP地址

AnycastAddresses属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获取分配给此接口的任意广播IP地址
    Console.WriteLine("获取分配给此接口的任意广播IP地址");
    foreach (IPAddressInformation iPAddressInformation in iPInterfaceProperties.AnycastAddresses)
    {

        Console.WriteLine(iPAddressInformation.Address);
    }
}

获取分配给此接口的多路广播地址

MulticastAddresses属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();

    //获取分配给此接口的多路广播地址
    Console.WriteLine("获取分配给此接口的多路广播地址");
    foreach (IPAddressInformation iPAddressInformation in iPInterfaceProperties.MulticastAddresses)
    {
        Console.WriteLine(iPAddressInformation.Address);
    }
}

获取分配给此接口的单播地址

UnicastAddresses属性

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();

    //获取分配给此接口的单播地址
    Console.WriteLine("获取分配给此接口的单播地址");
    foreach (IPAddressInformation iPAddressInformation in iPInterfaceProperties.UnicastAddresses)
    {
        Console.WriteLine(iPAddressInformation.Address);
    }
}

获取此网络接口的Internet协议版本4 (IPv4) 配置数据

GetIPv4Properties()

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获取此网络接口的Internet协议版本4 (IPv4) 配置数据
    Console.WriteLine(iPInterfaceProperties.GetIPv4Properties().IsDhcpEnabled);
    Console.WriteLine(iPInterfaceProperties.GetIPv4Properties().Mtu);
    Console.WriteLine(iPInterfaceProperties.GetIPv4Properties().UsesWins);
}

获取此网络接口的Internet协议版本6 (IPv6) 配置数据

GetlPv6Properties()

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface networkInterface in adapters)
{
    //描述此网络适配器配置的对象
    IPInterfaceProperties iPInterfaceProperties = networkInterface.GetIPProperties();
    //获取此网络接口的Internet协议版本6 (IPv6) 配置数据
    Console.WriteLine(iPInterfaceProperties.GetIPv6Properties().Index);
    Console.WriteLine(iPInterfaceProperties.GetIPv6Properties().Mtu);
}

IPGlobalProperties类

说明

如果希望统计本机接收和发送数据的情况,可利用IPGlobalProperties类的来实现。该类提供了本地计算机网络连接和通信统计数据的信息。例如,接收到的数据包的个数、丢弃的数据包个数

命名空间

System.Net.Networklnformation

获得IPGlobalProperties实例

检测网络流量时,首先调用IPGlobalProperties类提供的静态方法GetlPGlobalProperties得到IPGlobalProperties的实例,然后通过该实例的相关属性即可得到需要的信息

//获得对象
IPGlobalProperties iPGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();

获得IPv4的TCP连接信息

GetActiveTcpConnections()

返回有关本地计算机上的Internet协议版本4(IPV4)传输控制协议(TCP)连接的信息

//获得对象
IPGlobalProperties iPGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
//获得TCP连接信息
TcpConnectionInformation[] tcpConnectionInformations = iPGlobalProperties.GetActiveTcpConnections();
//读取TCP连接信息
foreach (TcpConnectionInformation tcpConnectionInformation in tcpConnectionInformations)
{
    Console.WriteLine(
        $"Local:IP:{tcpConnectionInformation.LocalEndPoint.Address} Port:{tcpConnectionInformation.LocalEndPoint.Port}");
    Console.WriteLine(
        $"Remote:IP:{tcpConnectionInformation.RemoteEndPoint.Address} Port:{tcpConnectionInformation.RemoteEndPoint.Port}");
}

获得IPv4的TCP连接监听器终结点信息

GetActiveTcpListencrs()

返回有关本地计算机上的Internet协议版本4(IPV4)传输控制协议(TCP)侦听器的终结点信息

获得IPv4的UDP连接监听器终结点信息

GetActiveUdpListeners()

返回有关本地计算机上的Internet协议版本4(IPv4)用户数据报协议(UDP)侦听器的信息

获得本地计算机的Internet协议版本4(IPv4)统计数据

GetIPv4GlobalStatistics()

获得本地计算机的Internet协议版本6(IPv6)统计数据

GetIPv6GlobaIStatistics()

获得本地计算机的传输控制协议/Internet协议版本4(TCP/IPv4)统计数据

GetTcpIPv4Statistics()

获得本地计算机的传输控制协必Internet协议版本6(TCP/IPv6)统计数据

GetTcpIPv6Statistics()

Ping类及其相关类

说明

在Windows操作系统中,有一个名为Ping.exe的命令行程序,它是微软公司提供用来进行网络连接测试的常用工具,能准确快速地判断出网络故障。当远程主机无法连通时,经常使用Ping命令来查找问题,确定本地主机是否能与另一台主机发送和接收数据。

命名空间

System.Net.NetworkInformation

主要类型

Ping类 进行发送Ping

PingOptions类 进行配置Ping

PingReply类 Ping后返回的类型

这3个类配合使用,可以在程序中轻松实现类似于Ping.exe的功能

Ping类

使用Ping类可以检测是否可访问远程计算机,此类提供的功能类似于Ping.exe命令行工具。 Ping类通过向目标主机发送一个回送请求数据包,要求目标主机收到请求后答复,从而判断网络的响应时间和本机是否与目标主机连通。Ping类提供了同步和异步两种发送数据的方法,其中Send方法以同步方式向目标主机发送回送请求数据包,并返回一个PingReply实例,该实例提供了是否可以访问到远程主机以及发送请求和接收答复所用的时间等信息。

Ping类提供的Send方法有以下两种重载形式:

重载1:

public PingReply Send(IPAddress addrcss)

此方法向 address 参数指定的主机发送一个包含回送消息的32位 Byte数据,然后等待应答消息5 秒钟。如果在指定时间内没有收到应答,则直接返回,并将PingReply实例的Status属性设置为TimedOut

实例:

//新建Ping实例
Ping pingSender = new Ping();
//进行发送数据
PingReply pingReply = pingSender.Send("www.panda666.com");
//返回状态
Console.WriteLine(pingReply.Status);
//IP地址
Console.WriteLine(pingReply.Address.Address);

重载2:

public PingReply Send(string hostNameOrAddress, int timeout, byte[] buffer, PingOptions options)

hostNameOrAddress 表示目标计算机IP地址或者主机名

timeout 指定发送回送消息后,等待答复消息的最大毫秒数

Buffer 是一个Byte数组,存放和回送消息一起发送的数据。

Options 是一个PingOptions对象,用于控制如何传输Ping数据包

实例:

重载3:

public PingReply Send(string hostNameOrAddress, int timeout)

hostNameOrAddress 表示目标计算机IP地址或者主机名

timeout 指定发送回送消息后,等待答复消息的最大毫秒数

实例:

Ping ping = new Ping();
PingReply pingReply = ping.Send("Panda666.com", 2000);
Console.WriteLine(pingReply.Status);

PingOptions 类

PingOptions类提供Ttl和 DontFragment属性以控制Ping数据包的传输

Ttl属性为Ping类发送的数据包指定生存时间。该值指示在丢弃Ping数据包之前可以转发此数据包的路由节点数。默认值为128。

DontFragment属性值控制是否可以将Ping类发送到远程主机的数据分成多个数据包。如果DontFragment属性为true,则表明不能在多个数据包中发送数据。此时如果发送到远程主机的数据大于发送方和远程主机之间的网关或路由器的最大传输单元,则

Ping操作失败,并提示数据包太大的错误。因此,如果不希望因为数据包过大造成数据无法传送时,可将DontFragment属性的值设置为 false。这样,如果数据包的大小超出网关或者路由器的最大传输单元,则会将数据包拆包后发送。但是,在测试用于传输数据包的路由器和网关的最大传输单元(MTU)大小时,需要将DontFragment属性的值设置为true。例如,首次测试时,我们向远程主机发送10 240字节大小的 数据,如果Ping操作发送数据成功,表明数据不用拆包就可以通过网关发送出去,MTU的值大于 10240,然后再进一步增加发送数据的大小,继续测试。如果提示数据太大,则表示数据需要拆开来发送,这时再减少数据的长度,执行上面的Ping类的Send方法。重复尝试,就能找到合适的MTU值。

实例:

//新建Ping对象
Ping ping = new Ping();
//设置PingOptions
PingOptions pingOptions = new PingOptions();
pingOptions.DontFragment = true;//不分段
pingOptions.Ttl = 500;//单次传输数据量
//发送的数据
string data = "Hello Panda666.com";
byte[] buffer = Encoding.UTF8.GetBytes(data);
//发送Ping
PingReply pingReply = ping.Send("Panda666.com", 2000, buffer, pingOptions);
//获得返回状态
Console.WriteLine(pingReply.Status);

PingReply 类

调用Ping类的Send方法后,即得到PingReply类的实例,该实例提供有关Send或SendAsync操作的状态以及发送请求和接收答复所用的时间等信息

PingReply类提供的常用属性

Address 获取发送回送答复消息的主机地址

Status 获取答复的状态,为 IPStatus枚举类型

如果值为IPStatus.Success,代表Send方法执行成功

RoundtripTime 获取发送消息所用的往返时间

Buffer 获取同送答复消息中收到的数据缓冲区

Options 如果Status是 Success,则为一个包含生存时间(TTL)和用于传输答 复的分段指令的PingOptions 对象;否则为 null

网络数据编码与解码

概念基础

编码与解码

在网络通信中,很多情况下通信双方传达的都是字符信息。但是,字符信息并不能直接从网络的一端传递到另一端,这些字符信息首先需要被转换成一个字节序列后才能在网络中传输。将字符序列转换为字节序列的过程称为编码。当这些字节传送到网络的接收方时,接收方需反过来将字节序列再转换为字符序列,这种过程称为解码

​ 对于Unicode字符来说,编码是指将一组Unicode字符转换为一个字节序列的过程,解码则是将一个编码字节序列转换为一组Unicode字符

常见的编码方式

ASCII字符集

ASCII字符集是美国信息交换标准委员会(American Standards Committee for Information Interchange)的缩写,在20世纪60年代由美国英语通信所设计。7位ASCII字符集由128个字符组成.包括大小写字母、数字0~9、标点符号、非打印字符(换行符、制表符等4个)以及控制字符(退格、响铃等)

非ASCII字符集

由于ASCII字符集是针对英语设计的,当处理汉字等其他字符时,这种编码就不能适应了。比如,用128个字符表示英文字符是足够的,但是如果用来表示中文却是不够的。为了解决这些问题,不同的国家和地区指定了自己的编码标准。我国一般使用国标码,常用有GB2312-1980编码和GB18030-2000编码,其中,GB18030编码汉字更多,是我国计算机系统必须遵循的基础性标准之一

Unicode字符集

由于每个国家都拥有独立的编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则就可能出现乱码。为了使国际信息交流更加方便,国际组织制定了Unicode字符集。它为各种语言中的每一个字符规定了统一并且唯一的字符,这种编码只用2个字节就可以表示地球上绝大部分地区的文字。在C#中,字符默认都是Unicode码,即一个英文字符占两个字节,一个汉字也是两个字节。Unicode虽然能够表示大部分国家的文字,但由于它比ASCII占用大一倍的空间,这对能用ASCII字符集来表示的字符来说就显得有些浪费。为了解决这个问题,又出现了一些中间格式的字符集,它们被称为通用转换格式,即UTF(Universal Transformation Format)。目前流行的UTF格式有UTF-8、UTF-16以及UTF-32。UTF-8是在因特网上使用最广泛的一种UTF格式。它是Unicode的一种变长字符编码,一般用14个字节编码一个Unicode字符,即将一个Unicode字符编为14个字节组成的UTF-8格式,根据不同的符号而变化字节长度。UTF-8是与字节顺序无关的,它的字节顺序在所有系统中都是一样的,因此这种编码可以使排序变得很容易。

Encoding 类

说明

主要用于在不同的编码和Unicode之间进行转换

命名空间

System.Text

获取某个指定的编码

Encoding类提供了UTF8、ASCII, UTF7等属性,通过访问这些属性可以直接获得其他非 Unicode的某个指定字符集的编码。如果已知某个字符集编码的名称,也可以利用Encoding类的静态方法GetEndcoing()来获取,例如

//通过快捷方式获得编码对象
Console.WriteLine(Encoding.UTF8.HeaderName);
//通过快捷方式获得编码对象
Console.WriteLine(Encoding.UTF32.HeaderName);
//通过快捷方式获得编码对象
Console.WriteLine(Encoding.ASCII.HeaderName);

//通过输入编码名称字符串获得编码对象
Encoding someEncoding = Encoding.GetEncoding("ascii");
Console.WriteLine(someEncoding.HeaderName);

将一个字节序列解码为一个字符串

GetString()

//测试使用的字符串
string dataStr = "Panda666.com";
//字符串转为字节数组
byte[] data = Encoding.UTF8.GetBytes(dataStr);
//字节数组转为字符串
string orginStr = Encoding.UTF8.GetString(data);
//输出原始的字符串
Console.WriteLine(orginStr);

将一组字符编码为一个字节序列

GetBytes()

//测试使用的字符串
string dataStr = "Panda666.com";
//字符串转为字节数组
byte[] data = Encoding.UTF8.GetBytes(dataStr);
//字节数组转为字符串
string orginStr = Encoding.UTF8.GetString(data);
//输出原始的字符串
Console.WriteLine(orginStr);

获取系统的当前ANSI代码版的编码

Default 属性

Console.WriteLine(Encoding.Default.HeaderName);

获取系统支持的所有编码名称

GetEncodings()

使用GetEncodings方法来获得一个包含所有编码的Encodinginfo数组

//获得所有系统支持的编码信息
EncodingInfo[] encodingInfos = Encoding.GetEncodings();
//遍历读取单个编码对象的信息
foreach (EncodingInfo encodingInfo in encodingInfos)
{
    //从编码信息中获得编码对象
    Encoding encoding = encodingInfo.GetEncoding();
    //获得编码名称
    Console.WriteLine(encoding.HeaderName);
}

获得编码的名称

HeaderName属性

Console.WriteLine(Encoding.UTF8.HeaderName);

不同编码之间的转换

利用Encoding类的Convert静态方法可将字节数组从一种编码转换为另一种编码,转换结果为一个 byte类型的数组

public static byte[] Convert(Encoding srcEncoding, Encoding dstEncoding, byte bytes)

srcEncoding表示源编码格式
dstEncoding表示目标编码格式
bytes指定了待转换的字节数组

实例:

//测试使用的字符串
string str = "panda666.com";
//UTF8转为UTF32
byte[] resultByte = Encoding.Convert(Encoding.UTF8, Encoding.UTF32, Encoding.UTF8.GetBytes(str));
//UTF32 字节数组转为字符串
Console.WriteLine(Encoding.UTF32.GetString(resultByte));

Encoder类

说明

利用Encoder类可以将一组字符转换为一个字节序列

在网络传输和文件操作中,如果数据量比较大,需要将其划分为较小的块,此时可能出现一个数据块的末尾是一个不匹配的高代理项,而与其匹配的低代理项则位于下一个数据块中。对于这种情况,直接使用Encoding类的GetBytes方法编写程序就比较烦琐。此时,可以利用Encoder类轻松地解决这个问题

命名空间

System.Text

获得Encoder实例

在指定的编码对象中使用GetEncoder()即可

实例:

//获取ASCII编码的Encoder实例
Encoder asciiEncoder = Encoding.ASCII.GetEncoder();
//获取Unicode编码的Encoder实例
Encoder unicodeEncoder = Encoding.Unicode.GetEncoder();

字符串转为字节数组

使用GetBytes()方法

获取Encoder实例后,就可以利用它的GetBytes()将一组字符编码转换为字节序列

public virtual int GetBytes(
    char[] chars,   //要编码的字符数组
    int charindex,  //第一个要编码的字符的索引
    int charCount,  //要编码的字符的数目
    byte [] bytes,  //存储编码后的字节序列的字节数组
    int byteindex,  //开始写人所产生的字节序列的索引位置
    bool flush      //是否在转换后清除编码器的内部状态
)

实例:

char[] chars = new char[] {
    '\u0023',
    '\u0025',
    '\uc3a0',
    '\u03ac',
};

//获取编码器
Encoder encoder = Encoding.UTF8.GetEncoder();
//计算编码后所产生的精确字节数
Byte[] bytes = new Byte[encoder.GetByteCount(chars, 0, chars.Length, true)];
//利用GetBytes方法将chars编码为字节数组存放到bytes中
encoder.GetBytes(chars, 0, chars.Length, bytes, 0, true);
//显示转换后的UTF8字符串
Console.WriteLine(Encoding.UTF8.GetString(bytes));

Decoder类

说明

Decoder类可以将已编码的字节序列解码为字符序列。

用Decoder类解码的步骤为:首先通过Encoding的GetDecoder方法创建Decoder实例,然后利用实例的GetChars方法将字节序列解码为一组字符。 GetChars方法用于将一个字节序列解码为一组字符,并从指定的索引位置开始存储这组字符

GetChars方法

image

实例:

image

套接字与数据流

基本概念

.NET中处理TCP/UDP的类

命名空间

System.Net.Sockets

对于需要侦听网络并发送请求的应用程序而言

可以使用TcpClient类、TcpListener类 和 UdpClient类

这些类封装了不同传输协议建立连接的细节,提供了多种传输数据的操作方法

如果需要在套接字级别进行控制,也可以直接使用该命名空间下的Socket类

除非我们准备定义一些新的协议或者对细节进行更灵活的控制

否则的话,一般情况不需要直接使用Socket类

而是使用进一步封装后的TcpListener类、TcpClient类以及UdpClient类来实现

这主要是因为使用Socket编写程序比较麻烦,而且容易出错

套接字

说明

套接字是支持TCP/IP网络通信的基本操作单元

可以将套接字看作不同主机间的进程进行双向通信的端点

在一个双方可以通信的套接字实例中,既保存了本机的IP地址和端口

也保存了对方的IP地址和端口,同时也保存了双方通信采用的协议等信息

编写基于TCP和UDP的应用程序时

可以使用对套接字进一步封装后的TcpListcncr类、 TcpClient类或UdpCIient类

也可以直接使用Socket类来实现,而编写其他类型的应用程序时

或者编写自定义的新协议程序时,只能使用Socket类来实现

套接字类型

流套接字

数据报套接字

原始套接字

流套接字用来实现面向连接的TCP通信

数据报套接字实现无连接的UDP通信

原始套接字实现IP数据包通信

3种类型的套接字均可以使用System.Net.Sockets命名空间中的Socket类来实现

连接类型

IP连接领域有两种通信类型:

面向连接的(Connection-Oriented)和无连接的(Connectionless)

面向连接的套接字中,使用TCP来建立两个IP地址端点之间的会话

新建套接字对象

public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);

参数说明:

addressFamily表示网络类型

该参数使用 AddressFamily 枚举指定 Socket使用的寻址方案

例如,AddressFamily.InterNetwork表示IP版本4的地址

部分 AddressFamily 枚举值

image

socketType指定Socket的类型

该参数使用SocketType枚举指定使用哪种套接字

例如SocketType.Stream表明连接是基于流套接字

SocketType.Dgram表示连接是基于数据报套接字

而SocketType.Raw表示连接基于原始套接字

image

protocolType指定Socket使用的协议

该参数使用ProtocoIType枚举指定使用哪种协设

例如,ProtocolType.Tcp表明连接协议是TCP,ProtocolType.Udp表明连接协议是UDP,该枚举的成员非常多

image

注意:
AddressFamily总是使用AddressFamily.InterNetwork枚举值
而SocketType参数则与ProtocolType参数配合使用
不允许其他的匹配形式,也不允许混淆匹配
可用于IP通信的组合如下

image

套接字常用属性

指定Socket类的实例可以使用的寻址方案

AddressFamily

获取套接字的类型

SocketType

从网络中获取准备读取的数据数量

Blocking

获取或设置表示套接字是否处于阻塞模式的值

AvilableConnected

获取一个值,该值表明套接字是否与最后完成发送或接收操作的远程设备得到连接

Local

获取套接字的远程EndPoint对象

RemoteEndPoint

获取套接字的本地EndPoint对象

EndPoint

获取套接字的协议类型

ProtocolType

面向连接的套接字

通信过程

面向连接的套接字中,使用TCP来建立两个IP地址端点之间的会话

一旦建立了这种连接,就可以在设备之间进行可靠的数据传输

根据连接启动的方式以及本地套接字要连接的目标

套接字之间的连接过程可以分为3个步骤:服务器监听、客户端请求、连接确认

服务器监听是指服务器套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

客户端请求是指由客户端的套接字提出连接请求,要连接的目标是服务器的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后再向服务器套接字提出连接请求

​ 连接确认是指当服务器套接字监听到客户端套接字的连接请求时,它就响应客户端套接字的请求,把服务器套接字的信息发给客户端,一旦客户端确认了此信息,连接即可建立。而服务器套接字继续监听其他客户端套接字的连接请求

​ 为了建立面向连接的套接字,服务器和客户端必须分别进行编程

image

建立连接

服务器和客户端通信的前提是服务器首先在指定的端口监听是否有客户端连接请求,当客户端向服务器发出连接请求,服务器接受请求后,双方即可建立连接

服务器端编程

创建套接字 | Socket()

在服务器程序中,首先应创建一个本地套接字对象

Socket localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
套接字绑定端点 | Bind()

然后将套接字绑定到用于TCP通信的本地IP地址和端口。Bind方法用于完成绑定工作

//获得本机的IP地址
IPAddress ipAddress = Dns.GetHostAddresses("")[0];
//新建本机端点
IPEndPoint iPEndPoint =new IPEndPoint(ipAddress,56000);
//绑定监听的本机端点
localSocket.Bind(iPEndPoint);
监听等待连接 | Listen()

将套接字与端口绑定后,就用Listen方法等待客户端发出的连接尝试

//设置套接字进行监听
localSocket.Listen(10);
允许客户端连接 | Accept

Listen方法自动将客户连接请求放到请求队列中,这里的参数指定为10指出系统等待用户程序服务排队的连接数,超过连接数的任何客户都不能与服务器进行通信。在Listen方法执行之后,服务器已经做好了接收任何连接的准备。这时,可用Accept方法从请求队列中获取连接。例如:

//从请求队列中获取客户端连接
Socket clientSocket = localSocket.Accept();

程序执行到Accept()时被阻塞,直到接收到客户端的连接请求,才继续执行下一条语句。 服务器一旦接受了该客户端的连接请求,Accept方法立即返回一个与该客户端通信的新的套接字,该套接字中既包含了本机的IP地址和端口号,也包含了客户端的IP地址和端口号,然后就可以利用此套接字与该客户端进行通信了。

提示:为了能够与多个客户端通信,可以用一个线程监听客户端连接请求,在线程中循环调用Accept 方法接收新的客户端连接。如果没有继续调用Accept方法,服务器就不会再响应任何新的客户端连接请求

一旦客户端与服务器建立连接

收发数据 | Send() | Receive()

客户端和服务器都可以使用Socket对象的Send和Receive方法进行通信

//建立连接后,利用Send方法向客户端发送信息
clientSocket.Send(Encoding.ASCII.GetBytes("server send Hello"));
//接收客户端信息
byte[] myresult = new Byte[1024];
int receiveNum = clientSocket.Receive(myresult);
Console.WriteLine("接收客户端消息:{0}", Encoding.ASCII.GetString(myresult));
关闭连接 | Close()

通信完成后,必须先用Shutdown方法停止会话,然后关闭Socket实例

//关闭连接
localSocket.Shutdown(SocketShutdown.Both);
localSocket.Close();

SocketShutdown枚举可取值:

image

客户端编程

客户端利用Socket的Connect方法向远程主机的端点发起连接请求

并将自身绑定到系统自动分配的端点上

程序运行后,客户端在与服务器建立连接之前

系统不会执行Connect语句下面的语句,而是处于阻塞状态

直到连接成功或出现异常为止

创建套接字| Socket()
//新建套接字
Socket localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
连接服务器端点 Connect()
//设置服务器端的IP地址
IPAddress remoteHost = IPAddress.Parse("192.168.1.182");
//设置服务器端的端点
IPEndPoint iep = new IPEndPoint(remoteHost, 80);
//尝试连接服务器端点
localsocket.Connect(iep);
收发数据 | Send() | Receive()

客户端可直接使用本地套接字的Send方法向服务器发送信息

//建立连接成功后,向服务器发送信息
string sendMessage = "client send Message Hello" + DateTime.Now;
localSocket.Send(Encoding.ASCII.GetBytes(sendMessage));
Console.WriteLine ("向服务器发送消息:{0}", sendMessage);
//接收服务器信息
byte[] result = new Byte[1024];
localSocket.Receive(result);
Console.WriteLine("接收服务器消息:(0}",Encoding.ASCII.GetString (result));
关闭连接 | Close()

通信完成后,必须先用Shutdown方法停止会话,然后关闭Socket实例

//关闭连接
localSocket.Shutdown(SocketShutdown.Both);
localSocket.Close();

无连接的套接字

说明

UDP使用无连接的套接字,无连接的套接字不需要在网络设备之间发送连接信息。

因此,很难确定谁是服务器谁是客户端。如果一个设备最初是在等待远程设备的信息,

则套接字就必须用Bind方法绑定到一个本地"IP 地址/端口"上。

完成绑定之后,该设备就可以利用套接字接收数据了。

由于发送设备没有建立到接收设备地址的连接,所以收发数据均不需要Connect方法

示意图

image

由于不存在固定的连接

所以可以直接使用SendTo方法和ReceiveFrom方法发送和接收数据,

在两个主到此间的通信结束之后,可以像TCP中使用的方法一样,

对套接字使用Shutdown和Close方法。

注意:必须使用Bind方法将套接字绑定到一个本地地址和端口之后才能使用ReceiveFrom方法接收数据,如果只发送而不接收,则不需要使用Bind方法

实例:(注意代码存在错误,待更新)

//服务器IP地址
IPAddress ip = IPAddress.Parse("127.0.0.1");
//接收端,接收准备
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
EndPoint senderRemote = (EndPoint)sender;
Socket receiveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
receiveSocket.Bind(new IPEndPoint(ip, 8889));
byte[] result = new Byte[1024];

//发送方:发送数据
Socket sendSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress ip = IPAddress.Parse("127.0.0.1");
sendSocket.SendTo(Encoding.UTF8.GetBytes("测试数据"), new IPEndPoint(ip, 8889));
sendSocket.Shutdown(SocketShutdown.Both);
sendSocket.Close();

//接收方:接收数据
//引用类型参数为EndPoint类型,用于存放发送方的IP地址和端点
int length = receiveSocket.ReceiveFrom(result, ref senderRemote);
Console.WriteLine("接收到{0}消息:{1}", senderRemote.ToString(), Encoding.UTF8.GetString(result, 0, length).Trim());
receiveSocket.Shutdown(SocketShutdown.Both);

receiveSocket.Close();
Console.ReadLine();

TCP通信

基础概念

TCP通信说明

TCP是 Transmission Control Protocol (传输控制协议) 的简称

TCP是TCP/IP体系中面向连接的运输层协议在网络中提供全双工的和可靠的服务。

利用TCP编写应用程序时,必须先建立TCP连接。一旦通信双方建立TCP连接,连接中的任何一方都能向对方发送数据和接收对方发送来的数据、TCP负责把用户数据(字节流)按一定的格式和长度组成多个数据报进行发送,并在接收到数据报之后按分解顺序重新组装和恢复用户数据。

利用TCP传输数据时,数据是以字节流的形式进行传输的。客户端与服务器建立连接后,发送方需要先将要发送的数据转换为字节流,然后将其发送给对方。发送数据时,程序员可以通过程序不断地将数据流陆续写入TCP的发送缓冲区中,然后TCP自动从发送缓冲区中取出一定数量的数据,将其组成TCP报文段逐个发送给IP层,再通过IP层发送出去。接收端从IP层接收到TCP报文段后.将其暂时保存在接收缓冲区中,这时程序员就可以通过程序依次读取接收缓冲区中的数据,从而达到相互通信的目的

TCP特点

(1)是面向连接的传输层协议。

(2)每个TCP连接只能有两个端点,而且只能一对一通信,不能一点对多点直接通信。

(3)通过TCP连接传送的数据,能保证数据无差错、不丢失、不重复地准确到达接收方,并且保证各数据到达的顺序与数据发出的顺序相同。

(4)数据以字节流的方式传输。

(5)传输的数据无消息边界。

TCP工作方式

利用TCP开发应用程序时,.NET框架提供两种工作方式。

一种是同步工作方式,一种是异步工作方式。

同步工作方式是指利用TCP编写的程序执行到发送、接收或监听语句时,在未完成工作前不再继续往下执行,即处于阻塞状态,直到该语句完成相应的工作后才继续执行下一条语句。异步工作方式是指程序执行到发送、接收或监听语句时,不论工作是否完成,都会继续往下执行利用Socket类进行编程时,系统也提供有相应的方法,分别称为同步套接字编程和异步套接字编程。

但是使用套接字编程要考虑很多底层的细节。为了简化编程复杂度,.NET框架专门提供了两个类:TcpClient类与TepListener类,这两个类与套接字一样也分别有同步和异步工作方式及其对应的方法。为简化起见,我们不管使用套接字编程还是使用对套接字封装后的类进行编程,一律从工作方式上将其称为同步TCP和异步TCP,所以其编程方式也有两种,一种是同步TCP编程,另一种是异步TCP编程。

注意:这里的同步TCP和异步TCP仅仅指工作方式,它和线程间的同步不是一个概念。线程间的同步指不同线程或其共享资源具有先后关联的关系。而同步TCP和异步TCP则仅仅指TCP编程中采用哪种工作方式,即是从执行到发送、接收或监听语句时,程序是否继续往下执行这个角度来说的。

TcpListener类

TcpListener类说明

.NET对套接字又进行了封装。封装后的类就是System.Net.Sockets命名空间下的 TcpListener类和TcpClient类。注意:TcpListener和TcpClient只支持标准协议编程。如果希望编写非标准协议的程序,只能使用套接字来实现TcpListener类用于监听客户端连接请求, TcpClient类用于提供本地主机和远程主机的连接信息

TcpListener类实例化

TcpListener类用于监听和接收传入的连接请求。该类的构造函数常用的有以下两种重载形式:

TcpListener(IPEndPoint iep)

通过IPEndPoint类型的对象在指定的IP地址与端口监听客户端连接请求,IPEndPoint包含了本机的IP地址与端口号

TcpListener(IPAddress localAddr, int port)

直接指定本机IP地址和端口,并通过指定的本机IP地址和端口监听传入的连接请求。

也可以将本机IP地址指定为IPAddress.Any,将本地端口号指定为0, 这种形式表示IP地址和端口号均由系统自动分配

TcpListener类同步方式使用

创建了TcpListener对象后,就可以监听客户端的连接请求了。在同步工作方式下,对应有Start方法、Stop方法、AcceptSocket方法和 AcceptTcpClient方法。

Start方法

Start方法用于启动监听,方法原型为:

public void Start()
public void Start(int backlog)

整型参数backlog为请求队列的最大长度,即最多允许的客户端连接个数。调用Start方法后系统会自动将LocalEndPoint和底层套接字绑定,并自动监听来自客户端的请求。如果接受了一个客户端请求,就把该请求插入请求队列,然后继续监听下一个请求,直到调用Stop方法为止。 当TcpListener接受的请求超过请求队列的最大长度时,将会在客户端抛出SocketException类型的异常

Stop方法

Stop方法用于关闭TcpListener并停止监听清求,方法原型为:

public void Stop()

程序执行Stop方法后,会立即停止监听客户端连接请求,此时等待队列中所有未接受的连接请求都会丢失,导致等待连接的远程主机(客户端)引发SocketException类型的异常,进而使服务器的Acc叩tTcpClient方法也会产生异常。但是要注意,该方法不会关闭已经接受的连接请求Stop方法还会关闭基础Socket, 并为TcpListener创建新的Socket。如果在调用Stop方法之前在基础Socket中设置任何属性,这些属性将不会传入到新的Socket

AcceptSocket方法

AcceptSocket方法用于在同步阻塞方式下获取并返回一个用来接收和发送数据的Socket对象,同时从传入的连接队列中移除该客户端的连接请求。该套接字包含了本地和远程主机的IP地址与端口号,得到该对象后,就可以通过调用Socket对象的Send和Receive方法和远程主机进行通信。

AcceptTcpClient方法

AcceptTcpClient方法用于在同步阻塞方式下获取并返回一个封装了Socket的TcpClient对象,同时从传人的连接队列中移除该客户端的连接请求。得到该对象后,就可以通过该对象的GetStream方法生成Networkstream对象,并通过Networkstream对象与客户端通信。如果应用程序仅需要同步I/O ,调用AcceptTcpClient即可,如果要进行更加细化的行为控制,则需要调用AcceptSocket方法注意,当程序执行AcceptTcpClient方法时,线程会处于阻塞状态,直到接受客户端向服务器发送的连接请求后,才会继续执行下一条语句

TcpClient类

TcpClient类说明

.NET对套接字进行了封装,封装后的类就是System.Net.Sockets命名空间下的 TcpListener类和TcpClient类,TcpClient类类主要用于客户端编程,而服务器端程序是通过TcpListener对象的AcceptTcpClient方法得到TcpClient对象的,所以不需要使用TcpClient类的构造函数来创建TcpClient对象

注意:TcpListener和TcpClient只支持标准协议编程。如果希望编写非标准协议的程序,只能使用套接字来实现TcpListener类用于监听客户端连接请求, TcpClient类用于提供本地主机和远程主机的连接信息。

TcpClient实例化

TcpClient的构造函数有以下4种重载形式:

TcpClient()

该构造函数创建一个默认的TcpClient对象,并自动分配本机(客户端)IP地址和端口号。
利用此构造函数创建对象后,还必须调用Connect方法与服务器建立连接。
例如:

TcpClient tcpClient = new TcpClient();
tcpClient.Connect("www.panda666.com", 51888);
TcpClient(AddressFamily family)

该构造函数创建的TcpClient对象也能自动分配本机(客户端)IP地址和端口号,但是使用AddressFamily枚举指定使用哪种网络协议。创建该对象后,必须调用Connect方法与服务器建立连接。例如:

TcpClient tcpClient = new TcpClient(AddressFamily.InterNetwork);
tcpClient.Connect("www.panda666.com", 51888);
TcpClient(IPEndPoint iep)

该构造函数的参数iep指定本机(客户端)IP地址与端口号。当客户端有一个以上的IP地址时,而且程序员希望直接指定使用的IP地址和端口号,可以使用这种方式。如果使用这种方式,必须调用Connect方法与服务器建立连接。例如:

IPAddress[] address = Dns.GetHostAddresses(Dns.GetHostName( ));
IPEndPoint iep = new IPEndPoint(address[0], 51888);
TcpClient tcpClient = new TcpClient(iep);
tcpClient.Connect("www.panda666.com", 51888);
TcpClient(string hostname, int port)

这是使用最方便的一种构造函数。

参数中的hostname表示要连接到的远程主机的DNS名,port表示要连接到的远程主机的端口号。该构造函数会自动分配最合适的本地主机IP地址和端口号,并对DNS进行解析,然后与远程主机建立连接。例如:

TcpClient tcpClient = new TcpClient("www.panda666.com", 51888);

它相当于:

TcpClient tcpClient = new TcpClient();
tcpClient Connect("www.panda666.com",51888);

TcpClient类的常用属性和方法

Client 获取或设置基础套接字

LingerState 获取或设置套接字保持连接的时间

NoDelay 获取或设置一个值,该值在发送或接收缓冲区未满时禁用延迟

ReceiveBufferSize 获取或设置Tcp接收缓冲区的大小

ReceiveTimeout 获取或设置套接字接收数据的超时时间

SendBuflerSize 获取或设置Tcp发送缓冲区的大小

SendTimeout 获取或设置套接字发送数据的超时时间

TcpClient类的常用方法

Close 释放TcpClient实例, 而不关闭基础连接

Connect 用指定的主机名和端口号将客户端连接到TCP主机

BeginConnect 开始一个对远程主机连接的异步请求 EndConnect 异步接受传入的连接尝试

GetStream 获取能够发送和接收数据的NetworkStream对象

TcpClient类使用

一旦创建了TcpClient对象,就可以利用该对象的GetStream方法得到Networkstream对象,然后再利用NetworkStream对象向远程主机发送流数据,或者从远程主机接收流数据。需要注意的是,直接使用Networkstream对象进行通信仍然比较复杂,所以一般利用该对象得到其他使用更方便的对象,然后再与对方通信,比如BinaryReader对象、BinaryWriteer对象、StreamReader对象和Stream Writer对象

TCP应用编程的步骤

不论是多么复杂的TCP应用程序,网络通信的最基本前提就是客户端要先和服务器建立TCP连接,然后才可以在此基础上相互传输数据。由于服务器需要同时为多个客户端服务,因此程序相对复杂一些。

在服务器端,程序员需要编写程序不断地监听客户端是否有连接请求,一旦接受了客户端连接请求,即能识别是哪个客户。而客户端与服务器连接则相对比较简单,只需要指定连接哪个服务器即可。一旦双方建立了连接并创建了对应的套接字,就可以相互传输数据了。在程序中,发送和接收数据的方法都是一样的,区别仅是方向不同。

编写服务器端程序的一般步骤

使用对套接字封装后的类,编写基于TCP的服务器端程序的一般步骤如下。

(1)创建一个TcpListener对象,然后调用该对象的Start方法在指定的端口进行监听。

(2)在单独的线程中,循环调用AcceptTcpClient方法接受客户端的连接请求,

并根据该方法的返回的结果得到与该客户端对应的TcpClient对象。

(3)每得到一个新的TcpClient对象,就创建一个与该客户对应的线程,在线程中与对应的客户进行通信。

(4)根据传送信息的情况确定是否关闭与客户的连接。

编写客户端程序的一般步骤

使用对套接字封装后的类,编写基于TCP的客户端程序的一般步骤如下。

(1)利用TcpClient的构造函数创建一个TcpClient对象。

(2)使用Connect方法与服务器建立连接。

(3)利用TcpClient对象的GetStream方法得到网络流,然后利用该网络流与服务器进行数据传输。

(4)创建一个线程监听指定的端口,循环接收并处理服务器发送过来的信息。

(5)完成工作后,向服务器发送关闭信息,并关闭与服务器的连接。

TCP消息边界问题

问题

采用TCP通信时,接收方能够按照发送方发送的顺序接收数据,但是在网络传输中,可能会出现发送方一次发送的消息与接收方一次接收的消息不一致的现象

解决方法

1、发送固定长度的消息

该方法适用于消息长度固定的场合。具体实现时,可以通过System.IO命名空间下的 BinaryReader对象每次向网络流发送一个固定长度的数据。例如,每次发送一个jnt 类型的32位整数。BinaryReader和 BinaryWriter对象提供了多种重载方法,利用它发送和接收具有固定长度 类型的数据非常方便,而且可以发送任何类型的数据。例如:

TcpClient client = new TcpClient("www.abcd.com", 51888);
NetworkStream networkstream = client.Getstream();
Binarywriter bw = new BinaryWriter(networkstream,Encoding.UTF8);
bw.Write(35);

2、将消息长度与消息一起发送

这种方法一般在每次发送的消息前面用4个字节表明本次消息的长度,然后将包含消息长度的消息发送到对方;对方接收到消息后,首先从消息的前4个字节获取消息长度,然后根据消息长度值接收发送方发送的数据。这种方法适用于任何场合,而且一样可以利用BinaryReader对象和BinaryWriter对象方便地实现。BinaryWriter对象提供了很多重载的Write方法。例如,向网络流写入字符串时,该方法会H 动计算出字符串占用的字节数,并使用4 个字节作为前缀将其附加到字符串的前面;接收方使用BinaryReader对象的ReadString方法接收字符串时,它会首先自动读取字符串前缀,并自动根据字符串前缀读取指定长度的字符串。如果是大量的二进制流数据、比如使用TCP通过网络传递二进制文件,由于C#没有封装对应的方法,程序员只能自己在代码中实现计算长度的功能。

3、使用特殊标记分隔消息

这种方法主要用于消息中不包含特殊标记的场合,例如:在每个命令后添加回车换行(\r\n) 符号作为分隔符的场合。对于字符串处理,实现这种方法最方便的途径是使用StreamWriter对象和StreamReader对象。发送方每次使用StreamWriter对象的WriteLinc方法将发送的字符串写入网络流中,接收方每次只需要用StreamReader时象的ReadLine方法将以回车换行作为分隔符的字符串从网络流中读出即可。在介绍如何解决T C P 的无消息边界问题时,我们均提到了先利用Networkstream对象得到其他对象,然后再利用其他对象收发数据。实际上,所有数据的收发也都可以直接用Networkstream对象来完成,只是该对象目前提供的功能还不是太多,直接用它解决消息边界问题麻烦一些而已。

同步TCP编写网络聊天程序

同步TCP和BinaryReader对象及BinaryWriter对象

(1)任何一个客户,均可以与服务器进行通信。

(2)服务器要能显示客户端连接的状态,当客户端连接成功后,要及时告知客户端已经连接成功的信息,并将当前在线的所有客户告知该客户端。

(3)客户和服务器建立连接后,即可以通过服务器和任一个在线的其他客户聊天。

(4)不论客户何时退出程序,服务器都要做出正确判断,同时将该客户是否在线的情况告诉其他所有在线的客户。

实际上,服务器和客户端只是相对的概念,当我们把服务器程序安装在A机上,把客户端程序安装在B机上,则A机就是服务器,B 机就是客户端,反之亦然

posted @ 2022-10-04 08:33  重庆熊猫  阅读(365)  评论(0编辑  收藏  举报