ZetCode

C# 网络

最后修改于 2023 年 7 月 5 日

在本文中,我们将展示如何在 C# 中创建基本网络程序。

System.Net 命名空间为当今网络上使用的许多协议提供了一个简单的编程接口。

C# Uri

Uri 提供了统一资源标识符 (URI) 的对象表示形式,并可以轻松访问 URI 的各个部分。

Program.cs
var resource = "http://webcode.me:80/";
var resource2 = "http://webcode.me/index.html";
var resource3 = "http://www.webcode.me/name=Peter&age=23";

var path = new Uri(resource);
var path2 = new Uri(resource2);
var path3 = new Uri(resource3);

Console.WriteLine(path.Port);
Console.WriteLine(path.Host);
Console.WriteLine(path.Authority);
Console.WriteLine(path.LocalPath);
Console.WriteLine(path.Scheme);

Console.WriteLine("-----------------------");

Console.WriteLine(path2.Port);
Console.WriteLine(path2.LocalPath);

Console.WriteLine("-----------------------");

Console.WriteLine(path3.Authority);
Console.WriteLine(path3.PathAndQuery);
Console.WriteLine(path3.Query);
Console.WriteLine(path3.AbsolutePath);
Console.WriteLine(path3.AbsoluteUri);

在这个例子中,我们使用 Uri 类。

var resource = "http://webcode.me:80/";
var resource2 = "http://webcode.me/index.html";
var resource3 = "http://www.webcode.me/name=Peter&age=23";

我们定义三个资源路径。

var path = new Uri(resource);
var path2 = new Uri(resource2);
var path3 = new Uri(resource3);

从这些路径,我们创建 Web 资源。

Console.WriteLine(path.Port);
Console.WriteLine(path.Host);
Console.WriteLine(path.Authority);
Console.WriteLine(path.LocalPath);
Console.WriteLine(path.Scheme);

这里我们打印 Uri 的各个部分。

$ dotnet run
80
webcode.me
webcode.me
/
http
-----------------------
80
/index.html
-----------------------
www.webcode.me
/name=Peter&age=23

/name=Peter&age=23
http://www.webcode.me/name=Peter&age=23

C# UriBuilder

UriBuilder 提供了一种方便的方法来修改 Uri 实例的内容,而无需为每个修改创建一个新的 Uri 实例。

Program.cs
using System.Net;

var uriBuilder = new UriBuilder();
uriBuilder.Scheme = "http";
uriBuilder.Host = "webcode.me";
uriBuilder.Path = "/";

Uri uri = uriBuilder.Uri;

WebRequest request = WebRequest.Create(uri);
using WebResponse response = request.GetResponse();

var headers = response.Headers;
Console.WriteLine(headers);

该示例使用 UriBuilder 构建一个 Uri,并向该资源发出一个简单的 GET 请求。

var uriBuilder = new UriBuilder();
uriBuilder.Scheme = "http";
uriBuilder.Host = "webcode.me";
uriBuilder.Path = "/";

Uri uri = uriBuilder.Uri;

我们使用 UriBuilder 构建 Uri

WebRequest request = WebRequest.Create(uri);

我们使用 WebRequest 创建到 Uri 的 Web 请求。

using WebResponse response = request.GetResponse();

通过 GetResponse 方法,我们对 Uri 指定的资源发出同步请求。

var headers = response.Headers;
Console.WriteLine(headers);

从响应中,我们获取标头并将它们打印到控制台。

$ dotnet run
Server: nginx/1.6.2
Date: Wed, 10 Feb 2021 12:42:16 GMT
Connection: keep-alive
ETag: "5d32ffc5-15c"
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Content-Type: text/html
Content-Length: 348
Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT

我们看到了服务器对我们请求的响应标头。

C# 主机名

Dns.GetHostName 方法获取本地计算机的主机名。

Program.cs
using System.Net;

var hostName = Dns.GetHostName();
Console.WriteLine($"Hostname: {hostName}");

该程序打印出我们本地计算机的主机名。

$ dotnet run
Hostname: LAPTOP-OBLOFB9J

C# Dns.GetHostAddresses

Dns.GetHostAddresses 返回一个 IPAddress 类型的数组,其中包含主机的 IP 地址。

Program.cs
using System.Net;

var hostname = "something.com";

IPAddress[] addresses = Dns.GetHostAddresses(hostname);

foreach (IPAddress address in addresses)
{
    Console.WriteLine($"{address}");
}

该示例打印出网页的所有 IP 地址。

$ dotnet run
2606:4700:3033::ac43:b7a8
2606:4700:3031::6815:3bce
172.67.183.168
104.21.59.206

C# GetHostEntry

使用 Dns.GetHostEntry 方法,我们可以从主机名确定 IP 地址。

Program.cs
using System.Net;

var name = "wikipedia.org";
IPHostEntry host = Dns.GetHostEntry(name);
var addresses = host.AddressList;

foreach (var address in addresses)
{
    Console.WriteLine($"{address}");
}

该示例打印出 wikipedia.org 网站的 IP 地址。

var addresses = host.AddressList;

AddressList 属性包含与主机关联的 IP 地址列表。

$ dotnet run
2620:0:862:ed1a::1
91.198.174.192

输出由 IPv6 和 IPv4 地址组成。

C# Ping

Ping 是一种网络管理实用程序,用于测试 Internet 协议 (IP) 网络上主机的可用性。 Ping 的工作原理是向目标主机发送 Internet 控制消息协议 (ICMP) 回显请求数据包,并等待 ICMP 回显回复。 该程序报告错误、数据包丢失以及结果的统计摘要。

.NET 包含用于发送 ping 请求的 Ping 类。 Ping 类使用 PingReply 类的实例来返回有关操作的信息并接收回复。

Program.cs
using System.Net.NetworkInformation;

using var ping = new Ping();

PingReply reply = ping.Send("192.168.0.23", 100);

if (reply.Status == IPStatus.Success)
{
    var msg = @$"Status: {reply.Status}
IP Address:{reply.Address}
Time:{reply.RoundtripTime}ms";
    Console.WriteLine(msg);
}
else
{
    Console.WriteLine(reply.Status);
}

我们向本地 lan 上的主机发送 Ping 请求。

PingReply reply = ping.Send("192.168.0.23", 100);

Send 方法尝试发送 ICMP 回显消息。 第二个参数是超时。

$ dotnet run
Status: Success
IP Address:192.168.0.23
Time:4ms

C# 套接字

在编程中,套接字是网络上两个程序之间通信的端点。 套接字用于在客户端程序和服务器程序之间创建连接。

System.Net.Sockets.Socket 类实现了 Berkeley 套接字接口。

Program.cs
using System.Text;
using System.Net;
using System.Net.Sockets;

string server = "webcode.me";
int port = 80;

var request = $"GET / HTTP/1.1\r\nHost: {server}\r\nConnection: Close\r\n\r\n";

Byte[] requestBytes = Encoding.ASCII.GetBytes(request);
Byte[] bytesReceived = new Byte[256];

IPHostEntry hostEntry = Dns.GetHostEntry(server);

var ipe = new IPEndPoint(hostEntry.AddressList[0], port);
using var socket = new Socket(AddressFamily.InterNetwork,
    SocketType.Stream, ProtocolType.Tcp);

socket.Connect(ipe);

if (socket.Connected)
{
    Console.WriteLine("Connection established");
}
else
{
    Console.WriteLine("Connection failed");
    return;
}

socket.Send(requestBytes, requestBytes.Length, 0);

int bytes = 0;
var sb = new StringBuilder();

do
{

    bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
    sb.Append(Encoding.ASCII.GetString(bytesReceived, 0, bytes));
} while (bytes > 0);

Console.WriteLine(sb.ToString());

该示例使用 Socket 类向 HTTP 服务器发送数据并接收响应。 此示例会阻塞,直到收到整个页面。 请注意,套接字编程是低级的。 诸如 HttpWebRequestHttpClient 之类的类抽象掉了这些低级细节。

string server = "webcode.me";
int port = 80;

我们定义服务器和端口。

var request = $"GET / HTTP/1.1\r\nHost: {server}\r\nConnection: Close\r\n\r\n";

我们定义一个 GET 请求。 GET 请求以 GET 命令开头,后跟资源 URL 和 HTTP 协议版本。 请注意,\r\n 字符是通信过程的强制部分。 详细信息在 RFC 7231 文档中描述。

Byte[] requestBytes = Encoding.ASCII.GetBytes(request);

我们将请求的文本数据转换为字节。

Byte[] bytesReceived = new Byte[256];

这个字节数组用于来自服务器的数据。

IPHostEntry hostEntry = Dns.GetHostEntry(server);

通过 Dns.GetHostEntry,我们找出域名对应的 IP 地址。

var ipe = new IPEndPoint(hostEntry.AddressList[0], port);

我们创建一个 IPEndPoint; 它是一个网络端点,由 IP 地址和端口号组成。 IP 地址是从 AddressList 属性检索的。

using var socket = new Socket(AddressFamily.InterNetwork,
    SocketType.Stream, ProtocolType.Tcp);

创建一个新的 TCP 套接字。 AddressFamily.InterNetwork 指定我们使用 IPv4 地址。 SocketType.Stream 提供可靠的、双向的、基于连接的字节流。 ProtocolType.Tcp 确定协议类型。

socket.Connect(ipe);

Connect 方法使用给定的 IPEndPoint 连接到网络端点。

if (socket.Connected)
{
    Console.WriteLine("Connection established");
}
else
{
    Console.WriteLine("Connection failed");
    return;
}

我们检查是否已成功连接。

socket.Send(requestBytes, requestBytes.Length, 0);

我们使用 Send 方法将请求发送到服务器。

do
{

    bytes = socket.Receive(bytesReceived, bytesReceived.Length, 0);
    sb.Append(Encoding.ASCII.GetString(bytesReceived, 0, bytes));
} while (bytes > 0);

我们使用 Receive 方法从套接字获取数据。 我们使用 GetString 方法将字节转换为文本,并将文本数据添加到 StringBuilder 中。

Console.WriteLine(sb.ToString());

最后,文本数据被写入控制台。

C# UdpClient

UdpClient 提供用户数据报协议 (UDP) 网络服务。 它包含以阻塞同步模式发送和接收无连接 UDP 数据报的方法。 由于 UDP 是一种无连接传输协议,因此我们不需要在发送和接收数据之前建立远程主机连接。

由于安全问题,大多数公共服务器不再提供回显服务。 为了测试该示例,我们需要在本地 lan 上设置一个服务器并启用 xinetd (Debian) 或 inetd (FreeBSD) 守护程序。

Program.cs
using System.Text;
using System.Net;
using System.Net.Sockets;

UdpClient udpClient = new UdpClient("core9", 7);
Byte[] data = Encoding.ASCII.GetBytes("Hello there");
udpClient.Send(data, data.Length);

IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);

Byte[] received = udpClient.Receive(ref RemoteIpEndPoint);
string output = Encoding.ASCII.GetString(received);

Console.WriteLine(output);

udpClient.Close();

该示例将消息发送到回显服务。 这是一种用于测试目的的简单服务。 回显服务在端口 7 上侦听。请注意,该服务可以在 TCP 或 UDP 上运行。

$ dotnet run
Hello there

我们发送的消息会回显给我们。

C# TcpClient

TcpClient 类使用 TCP 从 Internet 资源请求数据。 TcpClient 抽象了创建 Socket 以通过 TCP 请求和接收数据的低级细节。

由于与远程设备的连接表示为流,因此可以使用 .NET 流处理技术来读取和写入数据。

Program.cs
using System.Text;
using System.Net.Sockets;

using var client = new TcpClient();

var hostname = "webcode.me";
client.Connect(hostname, 80);

using NetworkStream networkStream = client.GetStream();
networkStream.ReadTimeout = 2000;

var message = @"GET / HTTP/1.1
Accept: text/html, charset=utf-8
Accept-Language: en-US
User-Agent: Console app
Connection: close
Host: webcode.me" + "\r\n\r\n";

using var reader = new StreamReader(networkStream, Encoding.UTF8);
byte[] bytes = Encoding.UTF8.GetBytes(message);

networkStream.Write(bytes, 0, bytes.Length);
Console.WriteLine(reader.ReadToEnd());

该示例使用 TcpClient 创建一个 GET 请求。

using var client = new TcpClient();

var hostname = "webcode.me";
client.Connect(hostname, 80);

创建一个 TCP 客户端。 我们使用 Connect 方法连接到远程主机。

using NetworkStream networkStream = client.GetStream();
networkStream.ReadTimeout = 2000;

使用 GetStream 方法,我们获得一个网络流。 我们将超时设置为两秒。

var message = @"GET / HTTP/1.1
Accept: text/html, charset=utf-8
Accept-Language: en-US
User-Agent: Console app
Connection: close
Host: webcode.me" + "\r\n\r\n";

我们构建 GET 请求消息。 不支持持久连接的 HTTP/1.1 应用程序必须在每条消息中包含 close 连接选项。

using var reader = new StreamReader(networkStream, Encoding.UTF8);
byte[] bytes = Encoding.UTF8.GetBytes(message);

对于来自服务器的响应,我们创建一个 StreamReader。 我们使用 GetBytes 方法将文本数据转换为字节。

networkStream.Write(bytes, 0, bytes.Length);

我们使用 Write 方法将消息写入网络流。

Console.WriteLine(reader.ReadToEnd());

我们使用 ReadToEnd 方法读取响应。

C# FtpWebRequest

FtpWebRequest 实现了文件传输协议 (FTP) 客户端。

Program.cs
using System.Net;

string uri = "ftp://192.168.0.21";

FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(uri);
ftpRequest.Credentials = new NetworkCredential("user7", "s$cret");
ftpRequest.Method = WebRequestMethods.Ftp.ListDirectory;

using FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse();
using var streamReader = new StreamReader(response.GetResponseStream());

var content = new List<string>();

string line = streamReader.ReadLine();
while (!string.IsNullOrEmpty(line))
{
    content.Add(line);
    line = streamReader.ReadLine();
}

foreach (var el in content)
{
    Console.WriteLine(el);
}

该示例列出了 FTP 目录上的内容。

string uri = "ftp://192.168.0.21";

这是本地 FTP 服务器上的 URL 路径。

FtpWebRequest ftpRequest = (FtpWebRequest)WebRequest.Create(uri);

从 URI 中,我们创建 FtpWebRequest

ftpRequest.Credentials = new NetworkCredential("user7", "s$cret");
ftpRequest.Method = WebRequestMethods.Ftp.ListDirectory;

我们提供凭据和 FTP 方法; 我们将列出目录的内容。

using FtpWebResponse response = (FtpWebResponse)ftpRequest.GetResponse();
using var streamReader = new StreamReader(response.GetResponseStream());

我们获取响应并创建一个流读取器来读取它。

var content = new List<string>();

string line = streamReader.ReadLine();
while (!string.IsNullOrEmpty(line))
{
    content.Add(line);
    line = streamReader.ReadLine();
}

数据被读入列表中。

foreach (var el in content)
{
    Console.WriteLine(el);
}

最后,内容被打印到控制台。

C# SmtpClient

SmtpClient 允许应用程序使用简单邮件传输协议 (SMTP) 发送电子邮件。

请注意,该类被标记为已过时,建议使用 MailKit 库来处理电子邮件。

Program.cs
using System.Net.Mail;

var client = new SmtpClient("core9", 25);

using var msg = new MailMessage();
msg.From = new MailAddress("john.doe@example.com");
msg.Subject = "Hello";

msg.Body = "hello there";

msg.To.Add(new MailAddress("root@core9"));
client.Send(msg);

该示例将一封简单的邮件发送到本地网络上的服务器。

C# HttpClient

HttpClient 是一个基类,用于发送 HTTP 请求和接收来自 URI 标识的资源的 HTTP 响应。

Program.cs
using var client = new HttpClient();

var result = await client.GetAsync("http://webcode.me");
Console.WriteLine(result.StatusCode);

此示例创建一个 GET 请求到一个小型网站。我们获取请求的状态码。

using var client = new HttpClient();

创建一个新的 HttpClient

var result = await client.GetAsync("http://webcode.me");

GetAsync 方法以异步操作向指定的 Uri 发送 GET 请求。 await 运算符暂停封闭 async 方法的评估,直到异步操作完成。 当异步操作完成时,await 运算符返回操作的结果(如果有)。

$ dotnet run
OK

我们获得 200 OK 状态代码; 该网站已启动。

C# HttpListener

HttpListener 是一个简单的、以编程方式控制的 HTTP 协议监听器。

Program.cs
using System.Net;
using System.Text;

using var listener = new HttpListener();
listener.Prefixes.Add("https://:8001/");

listener.Start();

Console.WriteLine("Listening on port 8001...");

while (true)
{
    HttpListenerContext context = listener.GetContext();
    HttpListenerRequest req = context.Request;

    using Stream ris = req.InputStream;
    using StreamReader sr = new StreamReader(ris, Encoding.UTF8);
    string doc = sr.ReadToEnd();

    Console.WriteLine($"Received request for {req.Url}");
    Console.WriteLine(doc);

    HttpListenerResponse resp = context.Response;

    string data = "Hello there!";
    byte[] buffer = Encoding.UTF8.GetBytes(data);
    resp.ContentLength64 = buffer.Length;

    Stream ros = resp.OutputStream;
    ros.Write(buffer, 0, buffer.Length);

    ros.Close();
}

该示例创建一个简单的 HTTP 服务器。

using var listener = new HttpListener();
listener.Prefixes.Add("https://:8001/");

listener.Start();

我们创建一个在端口 8001 上监听的监听器。

HttpListenerContext context = listener.GetContext();
HttpListenerRequest req = context.Request;

通过 GetContext,我们等待传入请求,并在收到请求时返回。

using Stream ris = req.InputStream;
using StreamReader sr = new StreamReader(ris, Encoding.UTF8);
string doc = sr.ReadToEnd();

Console.WriteLine($"Received request for {req.Url}");
Console.WriteLine(doc);

服务器将请求记录到终端。

HttpListenerResponse resp = context.Response;

string data = "Hello there!";
byte[] buffer = Encoding.UTF8.GetBytes(data);
resp.ContentLength64 = buffer.Length;

我们构建响应对象。

Stream ros = resp.OutputStream;
ros.Write(buffer, 0, buffer.Length);

服务器使用 Write 将数据写入响应输出流。

来源

.NET 中的网络编程

在本文中,我们使用 C# 创建了一些基本的网络编程。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直撰写编程文章。 迄今为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程