IPHostEntry 类的实例对象中包含了 Internet 主机的相关信息。常用属性有两个:一个是AddressList 属性,另一个是 HostName 属性。AddressList 属性的作用是获取或设置与主机关联的 IP 地址列表,是一个 IPAddress 类型的数组,包含了指定主机的所有 IP 地址;HostName 属性则包含了服务器的主机名。在 Dns 类中,有一个专门获取 IPHostEntry 对象的方法,通过 IPHostEntry 对象,可以获

1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10 using System.Net;
11 namespace NetProgramDemo
12 {
13 public partial class Form4 : Form
14 {
15 public Form4()
16 {
17 InitializeComponent();
18 }
19
20 private void button1_Click(object sender, EventArgs e)
21 {
22 listBox1.Items.Clear();
23 string name = Dns.GetHostName();
24 listBox1.Items.Add("本机主机名:" + name);
25 IPHostEntry me = Dns.GetHostEntry(name);
26 listBox1.Items.Add("本机所有 IP 地址:");
27 foreach (IPAddress ip in me.AddressList)
28 {
29 listBox1.Items.Add(ip);
30 }
31 IPAddress localip = IPAddress.Parse("127.0.0.1");
32 IPEndPoint iep = new IPEndPoint(localip, 80);
33 listBox1.Items.Add("The IPEndPoint is: " + iep.ToString());
34 listBox1.Items.Add("The Address is: " + iep.Address);
35 listBox1.Items.Add("The AddressFamily is: " + iep.AddressFamily);
36 listBox1.Items.Add("The max port number is: " + IPEndPoint.MaxPort);
37 listBox1.Items.Add("The min port number is: " + IPEndPoint.MinPort);
38 }
39
40 private void button2_Click(object sender, EventArgs e)
41 {
42 listBox1.Items.Clear();
43 IPHostEntry remoteHost = Dns.GetHostEntry("www.cctv.com");
44 IPAddress[] remoteIP = remoteHost.AddressList;
45 listBox1.Items.Add("中央电视台:");
46 foreach (IPAddress ip in remoteIP)
47 {
48 IPEndPoint iep = new IPEndPoint(ip, 80);
49 listBox1.Items.Add(iep);
50 }
51 }
52 }
53 }
View Code
效果图:

1.3 套接字
套接字是支持 TCP/IP 协议的网络通信的基本操作单元。可以将套接字看作不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。各种进程使用这个相同的域互相之间用 Internet 协议进行通信。
套接字可以根据通信性质分类,这种性质对于用户是可见的。应用程序一般仅在同一类的套接字间进行通信。不过只要底层的通信协议允许,不同类型的套接字间也照样可以通信。套接字有两种不同的类型:流套接字和数据报套接字。
要通过互联网进行通信,至少需要一对套接字,其中一个运行于客户端,我们称之为ClientSocket,另一个运行于服务器端,我们称之为 ServerSocket。根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
服务器监听是指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。客户端请求是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为
此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后再向服务器端套接字提出连接请求。连接确认是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的信息发给客户端,一旦客户端确认了此信息,连接即可建立。而服务器端套接字继续处于监听状态,继续接收其他客户端
套接字的连接请求。使用套接字处理数据有两种基本模式:同步套接字和异步套接字。
1. 同步套接字
同步套接字的特点是在通过 Socket 进行连接、接收、发送操作时,客户机或服务器在接收到对方响应前会处于阻塞状态,即一直等到接收到对方请求时才继续执行下面的语句。可见,同步套接字适用于数据处理不太多的场合。当程序执行的任务很多时,长时间的等待可能会让用户无法忍受。
2. 异步套接字
在通过 Socket 进行连接、接收、发送操作时,客户机或服务器不会处于阻塞方式,而是利用 callback 机制进行连接、接收和发送处理,这样就可以在调用发送或接收的方法后直接返回,并继续执行下面的程序。可见,异步套接字特别适用于进行大量数据处理的场合。
使用同步套接字进行编程相对比较简单,而异步套接字则比较复杂。
1.3.1Socket 类
Socket 类包含在 System.Net.Sockets 命名空间中。一个 Socket 实例包含了一个本地或者一个远程端点的套接字信息。
Socket 类的构造函数为:
public Socket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType);
其 中 , addressFamily 为 网 络 类 型 , 指 定 Socket 使 用 的 寻 址 方 案 , 例 如AddressFamily.InterNetwork 表明为 IP 版本 4 的地址;socketType 指定 Socket 的类型,例如SocketType.Stream 表明连接是基于流套接字的,而 SocketType.Dgram 表示连接是基于数据报套接字的。protocolType 指定 Socket 使用的协议,例如 ProtocolType.Tcp 表明连接协议是 TCP协议,而 ProtocolType.Udp 则表明连接协议是 UDP 协议。
Socket 构造函数的三个参数中,对于网络上的 IP 通信来说,AddressFamily 总是使用AddressFamily.InterNetwork 枚举值。而 SocketType 参数则与 ProtocolType 参数配合使用,不允许其他的匹配形式,也不允许混淆匹配。
熟悉了构造函数的参数含义,我们就可以创建套接字的实例:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
套接字被创建后,我们就可以利用 Socket 类提供的一些属性方便的设置或检索信息。表
在实际应用中,还可以通过调用 Socket 对象的 SetSocketOption 方法设置套接字的各种选
项,它有四种重载的形式:
public void SetSocketOption(SocketOptionLevel ol, SocketOptionName on, boolean value)
public void SetSocketOption(SocketOptionLevel ol, SocketOptionName on, byte[] value)
public void SetSocketOption(SocketOptionLevel ol, SocketOptionName on, int value)
public void SetSocketOption(SocketOptionLevel ol, SocketOptionName on, object value)
其中,ol 定义套接字选项的类型,可选类型有:IP、IPv6、Socket、Tcp、Udp。
on 指定套接字选项的值,表 1-3 列出了套接字常用的选项值。21
Value 参数指定所使用的套接字选项名 SocketOptionName 的值。例如:
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 1000);
该语句设置套接字发送超时时间为 1000 毫秒。
1.3.2 面向连接的套接字
IP 连 接 领 域 有 两 种 通 信 类 型 : 面 向 连 接 的 ( connection-oriented ) 和 无 连 接 的(connectionless)。在面向连接的套接字中,使用 TCP 协议来建立两个 IP 地址端点之间的会
话。一旦建立了这种连接,就可以在设备之间可靠的传输数据。为了建立面向连接的套接字,服务器和客户端必须分别进行编程,
对于服务器端程序,建立的套接字必须绑定到用于 TCP 通信的本地 IP 地址和端口上。
Bind方法用于完成绑定工作:Bind(IPEndPoint address)Address 为 IPEndPoint 的实例,该实例包括一个本地 IP 地址和一个端口号。在套接字绑定到本地之后,就用 Listen 方法等待客户机发出的连接尝试:
Listen(int backlog)Backlog 参数指出系统等待用户程序服务排队的连接数,超过连接数的任何客户都不能与服务器进行通信。在 Listen 方法执行之后,服务器已经做好了接收任何引进连接的准备,这是用 Accept 方法来完成的,当有新客户进行连接时,该方法就返回一个新的套接字描述符。
下面是完成上述步骤的服务器端部分代码的例子:
IPHostEntry local = Dns.GetHostByName(Dns.GetHostName());
IPEndPoint iep = new IPEndPoint(local.AddressList[0], 80);
Socket localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
localSocket.Bind(iep);
locatSocket.Listen(10);
Socket clientSocket = localSocket.Accept();
程序执行到 Accept 方法时会处于阻塞状态,直到有客户机请求连接,一旦有客户机连接到服务器,clientSocket 对象将包含该客户机的所有连接信息。而 localSocket 对象仍然绑定到原来的 IPEndPoint 对象,并可以通过增加循环语句继续用 Accept 方法接收新的客户端连接。如果没有继续调用 Accept 方法,服务器就不会再响应任何新的客户机连接。在接受客户机连接之后,客户机和服务器就可以开始传递数据了。表 1-4 和 1-5 分别列出
了可用的 Receive 方法和 Send 方法的格式以及 SocketFlag 的值。
对于客户端程序,客户机也必须把一个地址绑定到创建的 Socket 对象,不过它不使用 Bind方法,而是使用 Connect 方法:
IPAddress remoteHost = IPAddress.Parse("192.168.0.1");
PEndPoint iep = new IPEndPoint(remoteHost, 80);
Socket localSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
localSocket.Connect(iep);
程序运行后,客户端在与服务器建立连接之前,系统不会执行 Connect 方法下面的语句,而是处于阻塞方式。一旦客户端与服务器建立连接,客户机就可以像服务器收发数据使用的方法一样,使用 Send 和 Receive 方法进行通信。注意通信完成后,必须先用 Shutdown 方法停止会话,然后关闭 Socket 实例。
下面是关闭连接的典型用法:
sock.Shutdown(SocketShutdown.Both);
sock.Close();
该方法允许 Socket 对象一直等待,直到将内部缓冲区的数据发送完为止。
1.3.3 无连接的套接字
UDP 协议使用无连接的套接字,无连接的套接字不需要在网络设备之间发送连接信息。因此,很难确定谁是服务器谁是客户机。如果一个设备最初是在等待远程设备的信息,则套接字就必须用 Bind 方法绑定到一个本地地址/端口对上。完成绑定之后,该设备就可以利用套接字接收数据了。由于发送设备没有建立到接收设备地址的连接,所以收发数据均不需要 Connect
由于不存在固定的连接,所以可以直接使用 SendTo 方法和 ReceiveFrom 方法发送和接收数据,在两个设备之间的通信结束之后,可以像 TCP 中使用的方法一样,对套接字使用Shutdown 和 Close 方法。
再次提醒注意,需要接收数据时,必须使用 Bind 方法将套接字绑定到一个本地地址/端口对上之后才能使用 ReceiveFrom 方法接收数据,如果只发送而不接收,则不需要使用 Bind方法。
实际上,为了简化复杂的网络编程,.NET Framework 除了提供可以灵活控制的套接字类
以外,还在此基础上提供了对套接字封装后的基于不同协议的更易于使用的类。
1.4 网络流
流(stream)是对串行传输的数据的一种抽象表示,底层的设备可以是文件、外部设备、主存、网络套接字等等。
流有三种基本的操作:写入、读取和查找。
如果数据从内存缓冲区传输到外部源,这样的流叫作“写入流”。
如果数据从外部源传输到内存缓冲区,这样的流叫作“读取流”。
在网络上传输数据时,使用的是网络流(NetworkStream)。网络流的意思是数据在网络的各个位置之间是以连续的形式传输的。为了处理这种流,C#在 System.Net.Sockets 命名空间中提供了一个专门的 NetworkStream 类,用于通过网络套接字发送和接收数据。NetworkStream 类支持对网络数据的同步或异步访问,它可以被视为在数据来源端和接收端之间架设了一个数据通道,这样我们读取和写入数据就可以针对这个通道来进行。对于 NetworkStream 流,写入操作是指从来源端内存缓冲区到网络上的数据传输;读取操作是从网络上到接收端内存缓冲区(如字节数组)的数据传输。
构造 NetworkStream 对象的常用形式为:
Socket socket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
NetWorkStream networkStream=new NetworkStream(socket);
一旦构造了一个 NetworkStream 对象,就不需要使用 Socket 对象了。也就是说,在关闭25
网 络连接之前就一直使用 NetworkStream 对 象发送和接收网络数据。
在这个表中,比较常用的一个属性就是 DataAvailable,通过这个属性,可以迅速查看在缓冲区中是否有数据等待读出。
注意:网络流没有当前位置的概念,因此不支持查找和对数据流的随机访问,相应属 性CanSeek 始 终 返 回 false , 而 读 取 Position 属 性 和 调 用 Seek 方 法 时 , 都 将 引 发
NotSupportedException 异常。
网络数据传输完成后,不要忘记用 Close 方法关闭 NetworkStream 对象。