使用 TCP 服务
TCPClient 类使用 TCP 从 Internet 资源请求数据。TcpClient 的方法和属性提取某个 Socket 实例的创建细节,该实例用于通过 TCP 请求和接收数据。由于到远程设备的连接表示为流,因此可以使用 .NET 框架流处理技术读取和写入数据。
TCP 协议建立与远程终结点的连接,然后使用此连接发送和接收数据包。TCP 负责确保将数据包发送到终结点并在数据包到达时以正确的顺序对其进行组合。
若要建立 TCP 连接,必须知道承载所需服务的网络设备的地址以及该服务用于通讯的 TCP 端口。Internet 分配号码机构 (Internet Assigned Numbers Authority, IANA) 定义公共服务的端口号(请访问 http://www.iana.org/assignments/port-numbers)。IANA 列表中所没有的服务可使用 1,024 到 65,535 这一范围中的端口号。
下列代码说明如何设置 TcpClient 以连接到 TCP 端口 13 上的时间服务器。
[C#]
using System;
using System.Net.Sockets;
using System.Text;
public class TcpTimeClient {
private const int portNum = 13;
private const string hostName = "host.contoso.com";
public static int Main(String[] args) {
try {
TcpClient client = new TcpClient(hostName, portNum);
NetworkStream ns = client.GetStream();
byte[] bytes = new byte[1024];
int bytesRead = ns.Read(bytes, 0, bytes.Length);
Console.WriteLine(Encoding.ASCII.GetString(bytes,0,bytesRead));
client.Close();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
return 0;
}
}
TCPListener 用于监视 TCP 端口上的传入请求,然后创建 Socket 或 TcpClient 实例来管理与客户端的连接。Start 方法启用侦听,而 Stop 方法禁用端口上的侦听。AcceptTcpClient 方法接受传入的连接请求并创建 TcpClient 以处理请求,AcceptSocket 方法接受传入的连接请求并创建 Socket 以处理请求。
下面的代码说明如何使用 TcpListener 创建网络时间服务器以监视 TCP 端口 13。当接受传入的连接请求时,时间服务器用来自宿主服务器的当前日期和时间进行响应。
[C#]
using System;
using System.Net.Sockets;
using System.Text;
public class TcpTimeServer {
private const int portNum = 13;
public static int Main(String[] args) {
bool done = false;
TcpListener listener = new TcpListener(portNum);
listener.Start();
while (!done) {
Console.Write("Waiting for connection...");
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("Connection accepted.");
NetworkStream ns = client.GetStream();
byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());
try {
ns.Write(byteTime, 0, byteTime.Length);
ns.Close();
client.Close();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
listener.Stop();
return 0;
}
}
使用 UDP 服务
UDPClient 类使用 UDP 与网络服务通讯。UdpClient 类的属性和方法提取某个 Socket 实例的创建细节,该示例用于通过 UDP 请求和接收数据。
UDP 的优点是简单易用,并且能够同时向多个地址广播消息。但由于 UDP 协议是一个无连接协议,因此发送到远程终结点的 UDP 数据文报不一定能够到达,也不一定能够以发送的相同顺序到达。使用 UDP 的应用程序必须准备处理丢失的和顺序有误的数据文报。
若要使用 UDP 发送数据文报,必须知道承载所需服务的网络设备的网络地址以及该服务用于通讯的 UDP 端口号。Internet 分配号码机构 (Internet Assigned Numbers Authority, IANA) 定义公共服务的端口号(请访问 http://www.iana.org/assignments/port-numbers)。IANA 列表中所没有的服务可使用 1,024 到 65,535 这一范围中的端口号。
特殊网络地址用于支持基于 IP 的网络上的 UDP 广播消息。下面探讨的内容以 Internet 上使用的 IP 版本 4 地址族作为示例。
IP 版本 4 地址使用 32 位指定网络地址。对于使用 255.255.255.0 网络掩码的 C 类地址,这些位被分为四个八位字节。当以十进制数表示时,这四个八位字节构成熟悉的以点分隔的四部分表示法,如 192.168.100.2。前两个八位字节(此示例中为 192.168)构成网络号;第三个八位字节 (100) 定义子网;最后一个八位字节 (2) 是主机标识符。
将 IP 地址的所有位均设置为 1(即 255.255.255.255)可构成有限的广播地址。将 UDP 数据文报发送到此地址可将消息传递到该广播网络上的任何主机。由于路由器从不转发发送到此地址的消息,因此只有已连接的网络上的主机才可看到这些广播。
通过将部分地址的所有位全都设置为 1,可以将广播定向到特定的网络部分。例如,若要将广播发送到以 192.168 打头的 IP 地址标识的网络上的所有主机,请将地址的子网和主机部分全都设置为 1,如 192.168.255.255。若要将广播限制在单个子网,则只将主机部分设置全都为 1,如 192.168.100.255。
UdpClient 类可向任何网络广播地址广播,但它无法侦听发送到网络的广播。必须使用 Socket 类才能侦听网络广播。
当所有接收者都位于单个网络中时,或者当许多客户端需要接收广播时,广播地址将起作用。当接收者为网络的一小部分时,应将消息发送到多路广播组,在那里只有加入此组的客户端才能接收到消息。范围从 224.0.0.2 到 244.255.255.255 的 IP 地址保留为主机组地址。IP 号 224.0.0.0 被保留,而 224.0.0.1 分配给所有 IP 主机的固定组。
下面的示例使用 UdpClient 侦听端口 11000 上的多路广播地址组 224.168.100.2 的 UDP 数据文报广播。它接收消息字符串并将消息写入控制台。
[C#]
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class UDPMulticastListener {
private static readonly IPAddress GroupAddress =
IPAddress.Parse("224.168.100.2");
private const int GroupPort = 11000;
private static void StartListener() {
bool done = false;
UdpClient listener = new UdpClient();
IPEndPoint groupEP = new IPEndPoint(GroupAddress,GroupPort);
try {
listener.JoinMulticastGroup(GroupAddress);
listener.Connect(groupEP);
while (!done) {
Console.WriteLine("Waiting for broadcast");
byte[] bytes = listener.Receive( ref groupEP);
Console.WriteLine("Received broadcast from {0} :\n {1}\n",
groupEP.ToString(),
Encoding.ASCII.GetString(bytes,0,bytes.Length));
}
listener.Close();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
public static int Main(String[] args) {
StartListener();
return 0;
}
}
下面的示例使用 UdpClient 将 UDP 数据文报发送到端口 11000 上的多路广播地址组 224.268.100.2。它发送命令行上指定的消息字符串。
[C#]
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
public class UDPMulticastSender {
private static IPAddress GroupAddress =
IPAddress.Parse("224.168.100.2");
private static int GroupPort = 11000;
private static void Send( String message) {
UdpClient sender = new UdpClient();
IPEndPoint groupEP = new IPEndPoint(GroupAddress,GroupPort);
try {
Console.WriteLine("Sending datagram : {0}", message);
byte[] bytes = Encoding.ASCII.GetBytes(message);
sender.Send(bytes, bytes.Length, groupEP);
sender.Close();
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
}
public static int Main(String[] args) {
Send(args[0]);
return 0;
}
}
套接字
System.Net.Sockets 命名空间包含 Windows 套接字接口的托管实现。System.Net 命名空间中的所有其他网络访问类都建立在该套接字实现之上。
.NET 框架 Socket 类是 Winsock32 API 提供的套接字服务的托管代码版本。大多数情况下,Socket 类方法只是将数据封送到它们的本机 Win32 副本中并处理任何必要的安全检查。
Socket 类支持两种基本模式:同步和异步。在同步模式中,对执行网络操作的函数(如 Send 和 Receive)的调用一直等到操作完成后才将控制返回给调用程序。在异步模式中,这些调用立即返回。
创建套接字
在可以使用套接字与远程设备通讯之前,必须使用协议和网络地址信息初始化套接字。Socket 类的构造函数具有指定套接字用来建立连接的地址族、套接字类型和协议类型的参数。下面的示例创建一个 Socket,它可用于在基于 TCP/IP 的网络(如 Internet)上通讯。
[C#]
Socket s = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
若要使用 UDP 而不是 TCP,请更改协议类型,如下面的示例所示。
[C#]
Socket s = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
AddressFamily 枚举指定 Socket 类用来解析网络地址的标准地址族(例如,AddressFamily.InterNetwork 成员指定 IP 版本 4 地址族)。
SocketType 枚举指定套接字的类型(例如,SocketType.Stream 成员指示一个用于通过流控制发送和接收数据的标准套接字)。
ProtocolType 枚举指定在 Socket 上通讯时使用的网络协议(例如,ProtocolType.Tcp 指示套接字使用 TCP;ProtocolType.Udp 指示套接字使用 UDP)。
创建 Socket 后,它既可以初始化与远程终结点的连接,也可以接收来自远程设备的连接。
使用客户端套接字
在可以通过 Socket 初始化对话之前,必须在应用程序和远程设备之间创建一个数据管道。尽管存在其他网络地址族和协议,但本示例说明如何创建与远程服务的 TCP/IP 连接。
TCP/IP 使用一个网络地址和一个服务端口号来唯一标识设备。网络地址标识网络上的特定设备;端口号标识要连接到的该设备上的特定服务。网络地址和服务端口的组合称为终结点,它在 .NET 框架中由 EndPoint 类表示。为每个受支持的地址族定义了 EndPoint 的子代;对于 IP 地址族,该类为 IPEndPoint。
Dns 类向使用 TCP/IP Internet 服务的应用程序提供域名服务。Resolve 方法查询 DNS 服务器以将用户友好的域名(如“host.contoso.com”)映射到数字形式的 Internet 地址(如 192.168.1.1)。Resolve 返回一个 IPHostEnty 实例,该实例包含所请求名称的地址和别名的列表。大多数情况下,可以使用 AddressList 数组中返回的第一个地址。下面的代码获取一个 IPAddress 实例,该实例包含服务器 host.contoso.com 的 IP 地址。
[C#]
IPHostEntry ipHostInfo = Dns.Resolve("host.contoso.com");
IPAddress ipAddress = ipHostInfo.AddressList[0];
Internet 分配号码机构 (Internet Assigned Numbers Authority, IANA) 定义公共服务的端口号(有关更多信息,请访问 http://www.iana.org/assignments/port-numbers)。其他服务可以具有在 1,024 到 65,535 范围内的注册端口号。下面的代码将 host.contoso.com 的 IP 地址与端口号组合,以便为连接创建远程终结点。
[C#]
IPEndPoint ipe = new IPEndPoint(ipAddress,11000);
确定了远程设备的地址并选择了用于连接的端口后,应用程序可以尝试建立与远程设备的连接。下面的示例使用现有的 IPEndPoint 实例与远程设备连接,并捕获引发的任何异常。
[C#]
try {
s.Connect(ipe);
} catch(ArgumentNullException ae) {
Console.WriteLine("ArgumentNullException : {0}", ae.ToString());
} catch(SocketException se) {
Console.WriteLine("SocketException : {0}", se.ToString());
} catch(Exception e) {
Console.WriteLine("Unexpected exception : {0}", e.ToString());
}
使用同步客户端套接字
同步客户端套接字在等待网络操作完成时挂起应用程序。同步套接字不适用于在操作中大量使用网络的应用程序,但它们可以为其他应用程序启用对网络服务的简单访问。
若要发送数据,请将字节数组传递到 Socket 类的数据发送方法(Send 和 SendTo)之一。下面的示例使用 Encoding.ASCII 属性将字符串编码到字节数组缓冲区中,然后使用 Send 方法将该缓冲区传输到网络设备。Send 方法返回发送到网络设备的字节数。
[C#]
byte[] msg = System.Text.Encoding.ASCII.GetBytes("This is a test");
int bytesSent = s.Send(msg);
Send 方法从缓冲区中移除字节,并用网络接口将这些字节排队以便发送到网络设备。网络接口可能不会立即发送数据,但它最终是会发送的,只要连接是用 Shutdown 方法正常关闭的。
若要接收来自网络设备的数据,请将缓冲区传递到 Socket 类的数据接收方法(Receive 和 ReceiveFrom)之一。同步套接字将挂起应用程序,直到从网络接收字节或者套接字关闭为止。下面的示例接收来自网络的数据,然后将其显示在控制台上。该示例假定来自网络的数据是用 ASCII 编码的文本。Receive 方法返回从网络接收的字节数。
[C#]
byte[] bytes = new byte[1024];
int bytesRec = s.Receive(bytes);
Console.WriteLine("Echoed text = {0}",
System.Text.Encoding.ASCII.GetString(bytes, 0, bytesRec));
不再需要套接字时需要释放它,具体方法是调用 Shutdown 方法,然后调用 Close 方法。下面的示例释放 Socket。SocketShutdown 枚举定义常数以指示是关闭用于发送的套接字、用于接收的套接字还是用于两者的套接字。
[C#]
s.Shutdown(SocketShutdown.Both);
s.Close();