C# Socket网络编程入门

C# Socket网络编程入门
C# Socket网络编程入门

【转】C#Socket网络编程入门

第一章C#Socket编程(1)基本的术语和概念

计算机程序能够相互联网,相互通讯,这使一切都成为可能,这也是当今互联网存在的基础。那么程序是如何通过网络相互通信的呢?这就是我记录这系列的笔记的原因。C#语言从一开始就是为了互联网而设计的,它为实现程序的相互通信提供了许多有用API,这类应用编程接口被称为套接字(Socket)。在开始学习C#Socket之前我们需要先来了解一下基本的术语和概念。

1.1计算机网络

计算机网络由一组通过通信信道(Communication channel)相互连接的机器组成。这些机器被称为:主机(hosts)和路由器(routers)。

*通信信道——是将字节序列从一个主机传输到另一个主机的一种手段(有线、无线(WiFi)等方式)。

*主机——是运行程序的计算机。

*路由器——是将信息从一个通信信道传递或转发到另一个通信信道。

TCP/IP网络通信流程图:

1.2分组报文

*分组报文——在网络环境中由程序创建和解释的字节序列。

1.3协议

协议相当于相互通信的一种约定,协议规定了分组报文的交换方式和它们包含意义。

互联网所使用的协议是TCP/IP协议,TCP/IP协议族主要包括:

*IP协议(Internet Protocol,互联网协议)

*TCP协议(Transmission Control Protocol,传输控制协议)

*UDP协议(User Datagram Protocol,用户数据报协议)

1.3.1IP协议

*IP协议——是TCP/IP协议中唯一属于网络层的协议。将数据从一台主机传输到另一台主机。

*IP协议——提供了一种数据服务:每组分组报文都有网络独立处理和分发,类似于信件或包裹通过邮政系统发送一样。

*IP协议——是一个"尽力而为"(best-effort)的协议:它试图分发每一个分组报文,在网络传输过程中,偶尔也会发生丢失报文或报文顺序打乱,或者重复发送报文的情况。

在IP协议层之上是传输层(transport layer),它提供了两种可选的协议:TCP协议和UDP协议,两种协议都建立在IP层所提供的服务基础上,二者也被称为"端到端传输协议(end-to-end transport protocol)"。根据应用程序协议

(Application protocol)的不同需求,使用了不同的方式传输数据。二者都有一个共同的功能:寻址。TCP协议和UDP协议使用的地址叫做端口号(port number),是用来区分同一主机不同应用程序的。

1.3.2TCP协议

TCP协议能够检测和恢复IP层提供的主机到主机的信道中可能发生的报文丢失、重复以及其他错误。TCP协议是一种面向连接(connection oriented)协议:在使用它进行通信之前,两个应用程序之间首先要建立一个TCP连接,这涉及两台相互通信的主机的TCP部件间完成的握手消息(handshake message)的交换。

1.3.3UDP协议

UDP协议并不尝试对IP层产生的错误进行修复,它仅仅简单拓展了IP协议,"尽力而为"的数据服务,使它能够在应用程序之间工作,而不是在主机之间工作。使用UDP协议的应用程序需要对处理报文丢失、顺序混乱等问题做好准备。

1.4网络地址

1.4.1IP地址

在TCP/IP协议中,有两部分信息用来定位一个指定的程序:互联网地址(Internet address)和端口号(port number,范围1-65535)。前者由IP协议使用,后者由传输协议(TCP/UDP)对其进行解析。

互联网地址由二进制数字组成,有两种形式:IPv4(32位)和IPv6(128

位)。为了方便使用,两个版本的IP协议有不同的表示方法:

IPv4地址被表示为一组为4个十进制数,每两个数之间用圆点隔开,这种表示方法叫做:点分形式(dotted-quad)。

IPv6地址的16个字节由几组16进制的数字表示,这些十六进制数之间有分号隔开,每组数字分别代表了地址中的两个字节。

1.4.2回环地址

回环地址(loopback address)是被分配的一个特殊的回环接口(loopback interface),回环接口是一种虚拟设备,它的功能只是简单的把发送给它的报文立即返回给发送者。如IPv4中的127.0.0.1

1.5域名系统(Domain Name System,DNS)和本地配置数

据库

DNS是一种分布式数据库,它将像https://www.360docs.net/doc/8111788057.html,这样的域名映射到真实的互联网地址和其他信息上。DNS协议允许连接到互联网的主机通过TCP或者UDP协议从DNS数据库获取信息。

本地配置数据库通常是一种与具体操作系统相关的机制,用来实现本地与互联网地址的映射。

1.6客户端和服务器

客户端(client)和服务器(server)这两个术语分别代表了两种角色:

*客户端是通信的发起者,而服务器程序则被动等待客户端发起通信,并

对其作出响应。

*客户端和服务器组成了应用程序。

1.7什么是Socket(套接字)

Socket(套接字)是一种抽象层,应用程序通过它来发送和接受数据,就像应用程序打开一个文件句柄,将数据读写到稳定的存储器上一样。

在TCP/IP协议族中的主要Socket类型为:

*流套接字(stream socket):传输层使用TCP协议,提供了一个可信赖的字节流服务

*数据报套接字(datagram socket):传输层使用UDP协议,提供了一个"尽力而为"的数据报服务,最长一次可以发送65500个字节的数据。

第二章C#Socket编程(2)识别网络主机

通过前面的笔记我们可以知道:一个客户端想要发起一次通信,先决条件就是需要知道运行着服务器端程序的主机的IP地址是多少,端口号是多少。然后我们才能够通过这个地址向服务器特定的应用程序发送信息。对于网络上的两台计算机来说,用户操作的计算机称为本地主机,与该计算机通信的另一台计算机称为远程主机。识别远程主机依靠两部分组成:一是主机标识,用于识别与本地主机通信的远程主机;二是端口号,用于识别是在和远程主机中的哪个进程通信。

2.1获取主机地址信息

在C#开发中https://www.360docs.net/doc/8111788057.html,命名空间为网络上使用的多种协议提供了简单的编程接口。我们可以利用这个命名空间下的类,编写基于网络标准协议的应用程序时,不必去考虑各种不同协议的具体细节。在获取主机(local和remote)地址信息时,我们需要使用和学习这几个最基本的类(更加详细API可以参考MSDN,下面会给出对应链接),用它们来实现相关的功能。

*提供网际协议的IP地址的IPAddress类

*包含IP地址和端口号的IPEndPoint类

*为Internet主机提供信息容器的IPHostEntry类

*提供简单的域名解析功能的Dns类

说千遍不如做一遍,学习编程的最好方式就是自己动手实践,下面我们通过创建一个简单的Windows Forms示例程序(下载地址在本章末尾)来学习如何获取网络主机的地址信息,下面是示例的示例代码主要代码:

1//获取本地主机名

2string localHostName=Dns.GetHostName();

3

4//通过主机名获取该主机下存储所有IP地址信息的容器

5IPHostEntry local=Dns.GetHostEntry(HostName);

6

7//通过IPHostEntry对象的AddressList属性获取相关联主机的所有IP地址

8IPAddress[]ipList=local.AddressList;

9

10//获取本机回环地址

11IPAddress loopbackIP=IPAddress.Loopback;

12

13//通过它Parse函数构造IPAddress对象

14IPAddress localIp=IPAddress.Parse("192.168.1.101"); 15

16//通过IPAddress对象和端口号构造IPEndPoint对象17IPEndPoint iep=new IPEndPoint(localIp,80);

查看运行示例程序效果:

2.2获取网卡信息和网络检测

网络适配器又被称为网卡或者网络接口卡(NIC),是连接计算机和网络的硬件设备。网卡主要的工作原理是:整理计算机发往信道上的数据,并将数据分解为适当大小的数据包之后向网络上发送。在.NET开发中我们使用https://www.360docs.net/doc/8111788057.html,workInformation命名空间获取:网络流量数据、网络地址信息和本地计算机的地址更改通知等信息。该命名空间还包含实现Ping实用工具的类。可以使用Ping和相关的类检查是否可通过网络连接到计算机。

2.2.1获取网卡信息

获取网卡信息、网络连接和网络速度以及网络协议版本(包括:IPv4和IPv6)的网络接口信息,我们使用下面的两个类:

https://www.360docs.net/doc/8111788057.html,workInterface类:提供了访问主机所有接口的信息的功能。利用该类我们可以方便的检测本机有多少个网卡、哪些网络连接可用、并获取某个网卡的型号、Mac地址和速度等信息。

2.IPInterfaceProperties类:可用于访问支持IPv4或IPv6的网络接口的配置和地址信息。该类是一个抽象类,不能直接创建,使用NetworkInterface.GetIPProperties()返回实例。

下面我们通过一个简单的Windows Forms示例程序来学习如何获取网络接口的配置和统计信息,主要代码如下:

1//获取主机上所有的网络适配器对象数组

2NetworkInterface[]adapters=NetworkInterface.GetAllNetworkInterfaces();

3

4//获取该网络适配器的配置对象

5IPInterfaceProperties adapterProperties=adapters[i].GetIPProperties();

6

7//获取该网络适配器DNS服务器地址信息

8IPAddressCollection dnsServers=adapterProperties.DnsAddresses;

运行实例程序效果:

2.2.2网络流量检测

我们可以使用https://www.360docs.net/doc/8111788057.html,workInformation命名空间的IPGlobalProperties 类获取网络适配器接收、转发、丢弃、发送的数据包数目,该类提供有关本地计算机的网络连接的信息。检测网络流量是我们通过使用IPGlobalProperties类

的GetIPGlobalProperties()方法获取记录本地计算机的网络连接和通信统计数据的信息的对象实例,通过实例的属性来获取相关信息,达到检测网络流量的目的:

1//获取包含本机的网络连接和通信统计数据的信息的对象

2IPGlobalProperties properties=IPGlobalProperties.GetIPGlobalProperties();

3

4//获取本机IPv4统计数据

5IPGlobalStatistics ipstate=properties.GetIPv4GlobalStatistics();

下面我们还是通过上面的代码来创建一个小例子来学习如何检测网络流量,程序运行效果如下:

2.2.3网络连接检测

我们知道可以利用CMD命令行中输入的ping命令,通过调用ping.exe命令行程序来检测网络连接,能够快速判断出网络故障。在.NET开发环境中我们可以通过使用https://www.360docs.net/doc/8111788057.html,workInformation命名空间下的Ping类、PingOptions

类和PingReply类来实现类似于ping.exe命令行的功能。

*Ping类可以帮助应用程序确定是否可通过网络访问远程计算机

*PingOptions类用于控制如何传输Ping数据包

*PingReply类提供有关Send或SendAsync操作的状态及产生的数据的信息。

示例程序主要代码如下:

1//获取主机地址

2string hostAddress=this.txt_HostAddress.Text.Trim();

3//构造Ping实例

4Ping pingSender=new Ping();

5//Ping选项设置

6PingOptions options=new PingOptions();

7options.DontFragment=true;

8//测试数据

9string testData="Test Data";

10byte[]buffer=Encoding.ASCII.GetBytes(testData);

11//设置超时时间

12int timeout=120;

13//调用同步的Send方法发送数据,将结果保存至PingReply实例

14PingReply reply=pingSender.Send(hostAddress,timeout,buffer,options);

示例程序运行效果:

2.3示例下载

https://www.360docs.net/doc/8111788057.html,/s/1kT7UUWZ

2.4参考资料

https://www.360docs.net/doc/8111788057.html, Framework4类库

2.《C#网络应用编程2》

第三章C#Socket编程(3)编码和解码

在网络通信中,很多情况下:比如说QQ聊天,通讯双方直接传递的都是字符信息。但是字符信息并不能够直接通过网络传输,这些字符集必须先转换成一个字节序列后才能够在网络中传输,于是这里就产生了编码和解码的概念:*将字符序列转换为字节序列的过程称之为:编码

*将编码的字节序列转换为字符序列的过程称之为:解码

例如:对于Unicode字符来说,编码是指将一组Unicode字符转换为一个字

节序列的过程,解码就是将编码字节序列转换为一组Unicode字符。

3.1字符编码基础知识

字符集(Charset):是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的编码方式主要有一下三种:

3.1.1ASCII字符集

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本EASCII则可以勉强显示其他西欧语言。它是现今最通用的单字节编码系统(但是有被Unicode追上的迹象),并等同于国际标准ISO/IEC646。

3.1.2非ASCII字符集

由于ASCII字符集是针对英语设计的,当处理汉字等其他非拉丁语系的字符时,这种编码就不能适用了(因为适用128个字符表示英文是完全足够的,但是用于表示中文就远远不够了)。为了解决这个问题,不同的国家和地区制定了自己编码标准。中国一般适用国标码,常用的有GB2312-1980编码和GB183030-2000编码,其中GB183030-2000编码汉字更多,是中国计算机系统必须遵循的基础性标准之一。

3.1.3Unicode字符集

由于每个国家、语系都拥有独立的编码方式,同一个二进制数字可以被解

释成不同的字符,因此要想打开一个文本文件,就必须知道它的编码方式,否则就可能出现乱码。为了使国际信息交流更加方便,非营利机构统一码联盟制定和标准化了Unicode字符集。使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示216(即65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。

3.1.4UTF(通用转换格式)的出现

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的(例如:在C#中字符默认都是Unicode码,即一个英文字符占两个字节,一个汉字也是两个字节,这对于能适应ASCII字符集来表示的字符来说比较显得浪费),对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称UTF)。目前流行和UFT 格式包括UTF-8、UTF-16和UTF-32。

其中,UTF-8编码是互联网上使用最广泛的一种UTF格式,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以7位ASCII字符为主的西文文档就大大节省了编码长度。UTF-8是与字节顺序无关的,它的字节顺序在所有系统中都是一样的,因此这种编码可以使排序变得很容易。

3.2C#中不同编码和Unicode之间的转换

在C#语言中对于不同编码和Unicode之间的转换使用位于System.Text命名空间中的Encoding类。通过这个类我们可以为不同字符集直接进行转换以及获取各个字符集的相关信息。

3.2.1获取系统所有编码信息

我们通过调用Encoding类的GetEncodings()方法获取包含所有编码的数组,通过数组元素为EncodingInfo类,通过数组内的元素可以获得各种类型编码的信息。例如我们可以通过下面的代码获取主机上所有编码的信息:

1//获取系统所有编码名称及其描述信息

2EncodingInfo[]allEncoding=Encoding.GetEncodings();

3foreach(EncodingInfo encoding in allEncoding)

4{

5Console.WriteLine("编码标识符:{0,-10}编码名称:{1,-12}编码说明:{2}", encoding.CodePage,https://www.360docs.net/doc/8111788057.html,,encoding.DisplayName);

6}

运行如下:

3.2.2获取指定的编码信息

Encoding类提供了常用的字符集编码,可以直接通过调用属性获取UTF-8,ASCII等属性,也可以通过调用GetEncoding(+4重载)方法直接获取指定的字符集编码对象。例如下面的代码:

1//获取指定的编码描述信息

2Encoding gb18030Encoding=Encoding.GetEncoding("GB18030");

3Encoding asciiEncoding=Encoding.ASCII;

4Console.WriteLine("编码标识符:{0,-10}编码名称:{1,-12}编码说明:{2}", gb18030Encoding.CodePage,gb18030Encoding.HeaderName,

gb18030Encoding.EncodingName);

5Console.WriteLine("编码标识符:{0,-10}编码名称:{1,-12}编码说明:{2}", asciiEncoding.CodePage,asciiEncoding.HeaderName,

asciiEncoding.EncodingName);

运行如下:

3.2.3在不同编码之间进行转换

我们可以通过利用Encoding.Convert(+2重载)直接将字节数组从一种编码转换为另一种编码。下面我们同样通过一个示例代码来学习如何对不同编码的字节序列进行转换。下面的示例程序,为了清楚的演示如何使用,可能代码比

较冗余(代码中包含解码和编码部分,在随后会给出相应示例),实际的应用中我们可以根据自己的情况进行适当的对方法抽象,重构,提升程序的可读性和效率。代码如下:

1//不同编码之间的转换

2string GB18030String="你好!晴天猪";

3Console.WriteLine("需要转换的字符串:{0}",GB18030String);

4#region对字符串进行GB18030格式编码

5

6//获取编码器

7Encoding gb18030Encoding=Encoding.GetEncoding("GB18030");

8//将字符串转换为char类型数组

9char[]chars=GB18030String.ToCharArray();

10//获取编码为字节序列后的字节数组长度

11int buffLength=gb18030Encoding.GetByteCount(chars,0,chars.Length);

12//根据获取的字节长度声明数组,存储编码后的字节

13byte[]gb18030Buffer=new byte[buffLength];

14//获取GB18030编码的字节序列

15gb18030Buffer=gb18030Encoding.GetBytes(chars,0,chars.Length);

16Console.WriteLine("GB18030编码的字节序列:{0}",BitConverter.ToString (gb18030Buffer));

17//将GB18030编码的字节序列转换成UTF-8编码的字节序列

18byte[]unicodeBuffer=Encoding.Convert(gb18030Encoding,Encoding.UTF8,

gb18030Buffer);

19Console.WriteLine("转换为UTF-8编码字节序列:{0}",BitConverter.ToString (unicodeBuffer));

20

21#endregion

22

23#region将GB18030编码转换为UTF-8编码

24

25//获取UTF-8解码器

26Decoder utf8Decoder=Encoding.UTF8.GetDecoder();

27//获取解码为字符后字符数组的长度

28int utfChartsLength=utf8Decoder.GetCharCount(unicodeBuffer,0, unicodeBuffer.Length,true);

29//根据获取解码后的长度创建char数组

30char[]utfChart=new char[utfChartsLength];

31//将UTF-8编码的字节序列转换为字符串

32utf8Decoder.GetChars(unicodeBuffer,0,unicodeBuffer.Length,utfChart,0); 33

34StringBuilder strBuilder=new StringBuilder();

35foreach(char ca in utfChart)

36{

37strBuilder.Append(ca);

39Console.WriteLine("UTF-8的字符序列解码:{0}",strBuilder.ToString());

运行程序:

3.3C#编码和解码

在C#中为我们提供了Encoder和Decoder类,分别对字符进行编码和对字节序列进行解码的两个类。通过使用它们,我们可以很方便进行对字符和字节序列进行编码和解码操作。由于它们的构造函数都是protected级别的,需要使用Encoding实现的GetEncoder方法才能获取到它们的实例对象。下面我们通过一个Windows Forms示例程序来了解和学习如何使用这两个类,编码和解码的主要代码如下:

1///

2///获取字符串编码之后的bytes数组

3///

4///编码类型名称

5///将被编码的字符串

6///

7private byte[]GetEncodeBeforeBuffer(string codeType,string strCode)

9//根据编码类型构造该类型编码的编码器的实例

10Encoder encoder=Encoding.GetEncoding(codeType).GetEncoder();

11char[]chars=strCode.ToCharArray();

12//根据获取对字符进行编码所产生的字节数来创建一个byte数组

13byte[]bytes=new byte[encoder.GetByteCount(chars,0,chars.Length,true)]; 14//将字符写入到byte数组中

15encoder.GetBytes(chars,0,chars.Length,bytes,0,true);

16return bytes;

17}

18///

19///获取字符串解码之后的字符串

20///

21///编码格式

22///编码的字节数组

23///

24private string GetDecodeBeforeText(string codeType,byte[]byteCode)

25{

26//根据编码类型构造该类型编码的解码器的实例

27Decoder decoder=Encoding.GetEncoding(codeType).GetDecoder();

28//计算对字节序列(从指定字节数组开始)进行解码所产生的字符数29char[]chars=new char[decoder.GetCharCount(byteCode,0,byteCode.

相关主题
相关文档
最新文档