.NET 在 System.Net. 命名空间中供应了各种类,用于通过标准网络协议(如 HTTP 和 TCP/IP)进行通信。以下是关键组件的择要:
用于利用 HTTP Web API 和 RESTful 做事的 HttpClient用于编写 HTTP 做事器的 HttpListener用于通过SMTP构建和发送邮件的smtp客户端用于在域名和地址之间进行转换的 DNSTcpClient 、UdpClient 、TcpListener 和 Socket 类,用于直接访问传输层和网络层本章中的 .NET 类型位于 System.Net. 和 System.IO 命名空间中。
.NET 还供应对 FTP 的客户端支持,但只能通过已从 .NET 6 标记为过期的类。如果须要利用 FTP,最好的选择是利用 NuGet 库,例如 FluentFTP。

解释了 .NET 网络类型及其所在的通信层。大多数类型驻留在层或中。传输层定义了发送和吸收字节(TCP 和 UDP)的基本协议;运用层定义了为特定运用设计的更高等别的协议,例如检索网页 (HTTP)、发送邮件 (SMTP) 以及在域名和 IP 地址 (DNS) 之间进行转换。
网络架构
在运用程序层编程常日是最方便的;但是,您可能希望直接在传输层事情的缘故原由有几个。一种是是否须要 .NET 中未供应的运用程序协议(如 POP3)来检索邮件。另一个是如果您想为分外运用程序(如点对点客户端)发明自定义协议。
在运用程序协议中,HTTP在对通用通信的适用性方面是分外的。它的基本操作模式(“给我包含此 URL 的网页”)很好地适应了“让我理解利用这些参数调用此闭幕点的结果”。(除了“get”动词之外,还有“put”,“post”和“delete”,许可基于REST的做事。
HTTP 还具有一组丰富的功能,这些功能在多层业务运用程序和面向做事的体系构造中非常有用,例如用于身份验证和加密、分块、可扩展标头和 Cookie 的协议,以及让许多做事器运用程序共享单个端口和 IP 地址的能力。由于这些缘故原由,HTTP 在 .NET 中得到了很好的支持,既可以直接支持(如本章所述),也可以通过 Web API 和 ASP.NET Core 等技能在更高等别得到支持。
正如前面的谈论所表明的那样,网络是一个充斥着首字母缩略词的领域。我们在 中列出了最常见的。
网络缩略语
缩写
扩展
条记
域名解析
域名做事
在域名(例如 )和 IP 地址(例如 199.54.213.2)之间进行转换
邮票
文件传输协议
用于发送和吸收文件的基于互联网的协议
HTTP
超文本传输协议
检索网页并运行 Web 做事
二世
互联网信息做事
Microsoft的网络做事器软件
知识产权
网际协议
低于 TCP 和 UDP 的网络层协议
局域网
局域网
大多数局域网利用基于互联网的协议,如TCP/IP
盛行
邮局协议
检索互联网邮件
安歇
再现状态转移
一种盛行的 Web 做事体系构造,在相应中利用机器可遵照的链接,并且可以在基本 HTTP 上运行
短信通信
大略邮件传输协议
发送互联网邮件
技能互助操持(TCP
传输和掌握协议
传输层互联网协议,大多数更高层做事都在其上构建
UDP
通用数据报协议
用于低开销做事(如 VoIP)的传输层互联网协议
北卡罗来纳大学
通用命名约定
\\打算机\共享名\文件名
乌里
统一资源标识符
无处不在的资源命名系统(例如, 或mailto:)
网址
统一资源定位器
技能含义(从利用中淡出):URI的子集;普通含义:URI 的同义词
地址和端口要使通信正常事情,打算机或设备须要一个地址。互联网利用两种寻址系统:
IPv4
目前占主导地位的寻址系统;IPv4 地址的宽度为 32 位。当字符串格式时,IPv4 地址被写入为四个点分隔的小数(例如,101.102.103.104)。地址在世界上可以是唯一的,也可以在特定中是唯一的(例如在公司网络上)。
IPv6
较新的 128 位寻址系统。地址采取十六进制格式的字符串格式,带有冒号分隔符(例如,[3EA0:FFFF:198A:E4A3:4FF2:54fA:41BC:8D31])。.NET 哀求在地址两边添加方括号。
System.Net 命名空间中的 IPAddress 类表示任一协议中的地址。它有一个接管字节数组的布局函数和一个接管精确格式字符串的静态 Parse 方法:
IPAddress a1 = new IPAddress (new byte[] { 101, 102, 103, 104 });IPAddress a2 = IPAddress.Parse ("101.102.103.104");Console.WriteLine (a1.Equals (a2)); // TrueConsole.WriteLine (a1.AddressFamily); // InterNetworkIPAddress a3 = IPAddress.Parse ("[3EA0:FFFF:198A:E4A3:4FF2:54fA:41BC:8D31]");Console.WriteLine (a3.AddressFamily); // InterNetworkV6
TCP 和 UDP 协议将每个 IP 地址分成 65,535 个端口,许可单个地址上的打算机运行多个运用程序,每个运用程序都在自己的端口上。许多运用程序具有标准的默认端口分配;例如,HTTP 利用端口 80;SMTP 利用端口 25。
把稳从 49152 到 65535 的 TCP 和 UDP 端口是正式未分配的,因此它们非常适宜测试和小规模支配。
IP 地址和端口组合在 .NET 中由 IPEndPoint 类表示:
IPAddress a = IPAddress.Parse ("101.102.103.104");IPEndPoint ep = new IPEndPoint (a, 222); // Port 222Console.WriteLine (ep.ToString()); // 101.102.103.104:222
把稳
防火墙阻挡端口。在许多企业环境中,只有少数端口处于打开状态,常日是端口 80(用于未加密的 HTTP)和端口 443(用于安全 HTTP)。
目录URI 是一个分外格式的字符串,用于描述互联网或 LAN 上的资源,例如网页、文件或电子邮件地址。示例包括 、ftp://myisp/doc.txt 和 mailto:。确切的格式由 (IETF) 定义。
URI 可以分解为一系列元素,常日是、和。System 命名空间中的 Uri 类仅实行此划分,为每个元素公开一个属性,如图 所示。
把稳
当您须要验证 URI 字符串的格式或将 URI 拆分为其组成部分时,Uri 类非常有用。否则,可以将 URI 大略地视为字符串 - 大多数网络方法都会重载以接管 Uri 工具或字符串。
可以通过将以下任何字符串通报到其布局函数中来布局 Uri 工具:
URI 字符串,例如 或硬盘上文件的绝对路径,例如 或者在 Unix 上为 /局域网上文件的 UNC 路径,例如 \\文件和 UNC 路径会自动转换为 URI:添加“file:”协议,并将反斜杠转换为正斜杠。Uri 布局函数还会在创建 Uri 之前对字符串实行一些基本的清理,包括将方案和主机名转换为小写以及删除默认和空缺端口号。如果供应不带方案的 URI 字符串(如“”),则会引发 UriFormatException。
Uri 具有 IsLoopback 属性,该属性指示 Uri 是否引用本地主机(IP 地址 127.0.0.1)和一个 IsFile 属性,该属性指示 Uri 引用本地路径还是 UNC (IsUnc) 路径(对付挂载在 文件系统中的 共享,IsUnc 报告 false)。如果 IsFile 返回 true ,则 LocalPath 属性返回对本地操作系统友好的 AbsolutePath 版本(根据操作系统利用斜杠或反斜杠),您可以在该版本上调用 File.Open 。
Uri 的实例具有只读属性。要修正现有的 Uri,请实例化 UriBuilder 工具 - 该工具具有可写属性,可以通过其 Uri 属性转换回来。
Uri 还供应了比较和减去路径的方法:
Uri info = new Uri ("http://www.domain.com:80/info/");Uri page = new Uri ("http://www.domain.com/info/page.html");Console.WriteLine (info.Host); // www.domain.comConsole.WriteLine (info.Port); // 80Console.WriteLine (page.Port); // 80 (Uri knows the default HTTP port)Console.WriteLine (info.IsBaseOf (page)); // TrueUri relative = info.MakeRelativeUri (page);Console.WriteLine (relative.IsAbsoluteUri); // FalseConsole.WriteLine (relative.ToString()); // page.html
相对 Uri,例如本例中的 ,如果您调用除 IsAbsoluteUri 和 ToString() 之外的险些任何属性或方法,则会引发非常。你可以直接实例化一个相对的 Uri,如下所示:
Uri u = new Uri ("page.html", UriKind.Relative);
警告
尾部斜杠在 URI 中很主要,如果存在路径组件,则做事器如何处理要求会有所不同。
例如,在传统的Web做事器中,给定URI ,您可以期望HTTP Web做事器在站点Web文件夹中的子目录中查找并返回默认文档(常日是)。
如果没有尾部斜杠,Web 做事器将直接在站点的根文件夹中查找一个名为 的文件(不带扩展名),这常日不是您想要的。如果不存在此类文件,大多数 Web 做事器将假定用户键入缺点,并将返回 301 缺点,建议客户端利用尾部斜杠重试。默认情形下,.NET HTTP 客户端将以与 Web 浏览器相同的办法透明地相应 301,方法是利用建议的 URI 重试。这意味着,如果在该当包含尾部斜杠时省略了尾部斜杠,您的要求仍旧有效,但会遭受不必要的额外来回。
Uri 类还供应了静态赞助方法,例如 EscapeUriString() ,它通过将 ASCII 值大于 127 的所有字符转换为十六进制表示形式,将字符串转换为有效的 URL。CheckHostName() 和 CheckSchemeName() 方法接管字符串并检讨它对付给定属性在语法上是否有效(只管它们意外验测验确定主机或 URI 是否存在)。
HttpClientHttpClient 类公开了一个用于 HTTP 客户端操作的当代 API,取代了旧的 WebClient 和 WebRequest / WebResponse 类型(这些类型已被标记为过期)。
HttpClient 是为了相应基于 HTTP 的 Web API 和 REST 做事的增长而编写的,并且在处理比大略地获取网页更繁芜的协议时供应了良好的体验。特殊:
单个 HttpClient 实例可以处理并发要求,同时与自定义标头、Cookie 和身份验证方案等功能很好地合营利用。HttpClient 许可您编写和插入自定义处理程序。这许可在单元测试中进行仿照,并创建自定义管道(用于日志记录、压缩、加密等)。HttpClient 具有用于标头和内容的丰富且可扩展的类型系统。把稳HttpClient 不支持进度报告。有关办理方案,请参阅 ,或通过 LINQPad 的交互式示例库。
利用 HttpClient 的最大略方法是实例化它,然后调用其 Get 方法之一,传入一个 URI:
string html = await new HttpClient().GetStringAsync ("http://linqpad.net");
(还有GetByteArrayAsync和GetStreamAsync。HttpClient 中的所有 I/O 绑定方法都是异步的。
与其WebRequest / WebResponse的前成分歧,要得到最佳性能 HttpClient ,重用相同的实例(否则DNS解析之类的事情可能会不必要地重复,并且套接字保持打开的韶光超过必要的韶光)。HttpClient 许可并发操作,因此以下内容是合法的,并且可以一次下载两个网页:
var client = new HttpClient();var task1 = client.GetStringAsync ("http://www.linqpad.net");var task2 = client.GetStringAsync ("http://www.albahari.com");Console.WriteLine (await task1);Console.WriteLine (await task2);
HttpClient 具有超时属性和 BaseAddress 属性,该属性为每个要求添加前缀 URI。HttpClient有点像一个薄壳:您可能希望在此处找到的大多数其他属性都是在另一个名为HttpClientHandler的类中定义的。要访问这个类,你实例化它,然后将实例通报到 HttpClient 的布局函数中:
var handler = new HttpClientHandler { UseProxy = false };var client = new HttpClient (handler);...
在此示例中,我们见告处理程序禁用代理支持,这有时可以通过避免自动代理检测的成本来提高性能。还有一些属性可以掌握 Cookie、自动重定向、身份验证等(我们将在以下各节以及“利用 HTTP”中先容这些属性)。
获取异步和相应GetStringAsync 、GetByteArrayAsync 和 GetStreamAsync 方法是调用更通用的 GetAsync 方法的便捷快捷办法,该方法返回:
var client = new HttpClient();// The GetAsync method also accepts a CancellationToken.HttpResponseMessage response = await client.GetAsync ("http://...");response.EnsureSuccessStatusCode();string html = await response.Content.ReadAsStringAsync();
HttpResponseMessage 公开了用于访问标头(请参阅和 HTTP 状态代码的属性。不堪利的状态代码(如 404(未找到))不会导致引发非常,除非您显式调用 确保成功状态代码 。但是,通信或 DNS 缺点确实会引发非常。
HttpContent 有一个用于写入另一个流的 CopyToAsync 方法,该方法在将输出写入文件时很有用:
using (var fileStream = File.Create ("linqpad.html")) await response.Content.CopyToAsync (fileStream);
GetAsync 是对应于 HTTP 的四个动词的四种方法之一(其他方法是 PostAsync、PutAsync 和 DeleteAsync)。稍后我们将在“上传表单数据”中演示 PostAsync。
发送异步和要求GetAsync 、PostAsync、PutAsync 和 DeleteAsync 都是调用 SendAsync 的快捷办法,SendAsync 是其他所有内容都馈送到的单一低级方法。要利用它,您首先布局一个 HttpRequestMessage :
var client = new HttpClient();var request = new HttpRequestMessage (HttpMethod.Get, "http://...");HttpResponseMessage response = await client.SendAsync (request);response.EnsureSuccessStatusCode();...
实例化 HttpRequestMessage 工具意味着您可以自定义要求的属性,例如标头(请参阅和内容本身,从而许可您上传数据。
上传数据和 httpContent实例化 HttpRequestMessage 工具后,可以通过分配其 Content 属性来上载内容。此属性的类型是一个名为 HttpContent 的抽象类。.NET 包含以下用于不同类型内容的详细子类(您也可以编写自己的子类):
字节数组内容字符串内容FormUrlEncodedContent(请参阅)流内容例如:
var client = new HttpClient (new HttpClientHandler { UseProxy = false });var request = new HttpRequestMessage ( HttpMethod.Post, "http://www.albahari.com/EchoPost.aspx");request.Content = new StringContent ("This is a test");HttpResponseMessage response = await client.SendAsync (request);response.EnsureSuccessStatusCode();Console.WriteLine (await response.Content.ReadAsStringAsync());
HttpMessageHandler
我们之前说过,大多数用于自定义要求的属性不是在 HttpClient 中定义的,而是在 HttpClientHandler 中定义的。后者实际上是抽象 HttpMessageHandler 类的子类,定义如下:
public abstract class HttpMessageHandler : IDisposable{ protected internal abstract Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken); public void Dispose(); protected virtual void Dispose (bool disposing);}
SendAsync方法是从HttpClient的SendAsync方法调用的。
HttpMessageHandler非常大略,可以轻松进行子类化,并为HttpClient供应了一个扩展点。
单元测试和仿照我们可以对 HttpMessageHandler 进行子类化,创建一个处理程序来帮助进行单元测试:
class MockHandler : HttpMessageHandler{ Func <HttpRequestMessage, HttpResponseMessage> _responseGenerator; public MockHandler (Func <HttpRequestMessage, HttpResponseMessage> responseGenerator) { _responseGenerator = responseGenerator; } protected override Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var response = _responseGenerator (request); response.RequestMessage = request; return Task.FromResult (response); }}
它的布局函数接管一个函数,该函数见告仿照者如何从要求天生相应。这是最通用的方法,由于同一个处理程序可以测试多个要求。
SendAsync 是同步的,凭借 Task.FromResult 。我们本可以通过让我们的相应天生器返回一个 Task<HttpResponseMessage> 来保持异步性,但这是没故意义的,由于我们可以预期仿照函数运行韶光很短。以下是利用我们的仿照处理程序的方法:
var mocker = new MockHandler (request => new HttpResponseMessage (HttpStatusCode.OK) { Content = new StringContent ("You asked for " + request.RequestUri) });var client = new HttpClient (mocker); var response = await client.GetAsync ("http://www.linqpad.net");string result = await response.Content.ReadAsStringAsync();Assert.AreEqual ("You asked for http://www.linqpad.net/", result);
(Assert.AreEqual是您希望在单元测试框架(如NUnit)中找到的方法。
利用委派处理程序链接处理程序您可以通过子类化 De委派处理程序 来创建调用另一个处理程序(天生处理程序链)。您可以利用它来实现自定义身份验证、压缩和加密协议。下面演示了一个大略的日志记录处理程序:
class LoggingHandler : DelegatingHandler { public LoggingHandler (HttpMessageHandler nextHandler) { InnerHandler = nextHandler; } protected async override Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) { Console.WriteLine ("Requesting: " + request.RequestUri); var response = await base.SendAsync (request, cancellationToken); Console.WriteLine ("Got response: " + response.StatusCode); return response; }}
请把稳,我们在覆盖 SendAsync 时保持了异步。在重写任务返回方法时引入异步润色符是完备合法的,在这种情形下是可取的。
比写入掌握台更好的办理方案是让布局函数接管某种日志记录工具。更好的办法是接管几个 Action<T> 委托,见告它如何记录要乞降相应工具。
代理是可以路由 HTTP 要求的中介。组织有时会将代理做事器设置为员工访问互联网的唯一办法,紧张是由于它简化了安全性。代理有自己的地址,可以哀求身份验证,以便只有 LAN 上的选定用户才能访问互联网。
要将代理与 HttpClient 一起利用,首先创建一个 HttpClientHandler 并分配其 Proxy 属性,然后将其馈送到 HttpClient 的布局函数中:
WebProxy p = new WebProxy ("192.178.10.49", 808);p.Credentials = new NetworkCredential ("username", "password", "domain");var handler = new HttpClientHandler { Proxy = p };var client = new HttpClient (handler);...
HttpClientHandler 还有一个 UseProxy 属性,您可以将其分配给 false,而不是清空 Proxy 属性以阻挡自动检测。
如果在布局 NetworkCredential 时供应域,则利用基于 Windows 的身份验证协议。若要利用当前经由身份验证的 Windows 用户,请将静态 CredentialCache.DefaultNetworkCredentials 值分配给代理的 Credentials 属性。
作为重复设置代理的替代方法,您可以按如下办法设置全局默认值:
HttpClient.DefaultWebProxy = myWebProxy;
认证
您可以向 HttpClient 供应用户名和密码,如下所示:
string username = "myuser";string password = "mypassword";var handler = new HttpClientHandler();handler.Credentials = new NetworkCredential (username, password);var client = new HttpClient (handler);...
这适用于基于对话框的身份验证协议(如基本和择要),并且可通过 AuthenticationManager 类进行扩展。它还支持 Windows NTLM 和 Kerberos(如果在布局 NetworkCredential 工具时包含域名)。如果要利用当前经由身份验证的 Windows 用户,可以将“凭据”属性保留为空,而是将“利用默认凭据”设置为 true 。
当您供应凭据时,HttpClient 会自动协商兼容的协议。在某些情形下,可以选择:例如,如果检讨来自Microsoft Exchange 做事器 Web 邮件页面的初始相应,则它可能包含以下标头:
HTTP/1.1 401 UnauthorizedContent-Length: 83Content-Type: text/htmlServer: Microsoft-IIS/6.0WWW-Authenticate: NegotiateWWW-Authenticate: NTLMWWW-Authenticate: Basic realm="exchange.somedomain.com"X-Powered-By: ASP.NETDate: Sat, 05 Aug 2006 12:37:23 GMT
401 代码表示须要授权;“WWW 身份验证”标头指示理解的身份验证协议。但是,如果利用精确的用户名和密码配置 HttpClientHandler,则此将对你隐蔽,由于运行时通过选择兼容的身份验证协议,然后利用额外的标头重新提交原始要求来自动相应。下面是一个示例:
Authorization: Negotiate TlRMTVNTUAAABAAAt5II2gjACDArAAACAwACACgAAAAQATmKAAAAD0lVDRdPUksHUq9VUA==
此机制供应透明度,但会为每个要求天生额外的来回行程。通过将 HttpClientHandler 上的 PreAuthenticate 属性设置为 true 来避免对同一 URI 的后续要求进行额外的来回。
凭据缓存您可以利用凭据缓存工具逼迫利用特定的身份验证协议。凭据缓存包含一个或多个 NetworkCredential 工具,每个工具都以特定协议和 URI 前缀为密钥。例如,您可能希望在登录 Exchange Server 时避免利用基本协议,由于它以纯文本形式传输密码:
CredentialCache cache = new CredentialCache();Uri prefix = new Uri ("http://exchange.somedomain.com");cache.Add (prefix, "Digest", new NetworkCredential ("joe", "passwd"));cache.Add (prefix, "Negotiate", new NetworkCredential ("joe", "passwd"));var handler = new HttpClientHandler();handler.Credentials = cache;...
身份验证协议指定为字符串。有效值包括:
Basic, Digest, NTLM, Kerberos, Negotiate
在这种分外情形下,它将选择协商,由于做事器在其身份验证标头中未指示它支持 Digest。协商是一种 Windows 协议,目前归结为 Kerberos 或 NTLM,详细取决于做事器的功能,但在支配未来安全标准时可确保运用程序的向前兼容性。
静态 CredentialCache.DefaultNetworkCredentials 属性许可您将当前经由身份验证的 Windows 用户添加到凭据缓存中,而无需指定密码:
cache.Add (prefix, "Negotiate", CredentialCache.DefaultNetworkCredentials);
通过标头进行身份验证
另一种身份验证方法是直接设置身份验证标头:
var client = new HttpClient();client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue ("Basic", Convert.ToBase64String (Encoding.UTF8.GetBytes ("username:password")));...
此策略也适用于自定义身份验证系统,如 OAuth。
头HttpClient 许可您向要求添加自定义 HTTP 标头,以及在相应中列举标头。标头只是包含元数据(如内容类型或做事器软件)的键/值对。HttpClient 公开具有标准 HTTP 标头属性的强类型凑集。属性适用于运用于每个要求的标头:
var client = new HttpClient (handler);client.DefaultRequestHeaders.UserAgent.Add ( new ProductInfoHeaderValue ("VisualStudio", "2022"));client.DefaultRequestHeaders.Add ("CustomHeader", "VisualStudio/2022");
但是,类上的 Headers 属性用于特定于要求的标头。
查询字符串查询字符串只是附加到带有问号的 URI 的字符串,用于将大略数据发送到做事器。可以利用以下语法在查询字符串中指定多个键/值对:
?key1=value1&key2=value2&key3=value3...
下面是一个带有查询字符串的 URI:
string requestURI = "http://www.google.com/search?q=HttpClient&hl=fr";
如果您的查询可能包含符号或空格,则可以利用 Uri 的 EscapeDataString 方法创建一个合法的 URI:
string search = Uri.EscapeDataString ("(HttpClient or HttpRequestMessage)");string language = Uri.EscapeDataString ("fr");string requestURI = "http://www.google.com/search?q=" + search + "&hl=" + language;
此生成的 URI 为:
http://www.google.com/search?q=(HttpClient%20OR%20HttpRequestMessage)&hl=fr
(EscapeDataString 与 EscapeUriString 类似,不同之处在于它还对 & 和 = 等字符进行编码,否则会弄乱查询字符串。
上传表单数据若要上载 HTML 表单数据,请创建并添补 FormUrlEncodedContent 工具。然后,可以将其通报到 PostAsync 方法中,也可以将其分配给要求的 Content 属性:
string uri = "http://www.albahari.com/EchoPost.aspx";var client = new HttpClient();var dict = new Dictionary<string,string> { { "Name", "Joe Albahari" }, { "Company", "O'Reilly" }};var values = new FormUrlEncodedContent (dict);var response = await client.PostAsync (uri, values);response.EnsureSuccessStatusCode();Console.WriteLine (await response.Content.ReadAsStringAsync());
饼干
Cookie 是 HTTP 做事器在相应标头中发送到客户真个名称/值字符串对。Web 浏览器客户端常日会记住 Cookie,并在每次后续要求(到同一地址)中将它们重播到做事器,直到它们到期。Cookie 许可做事器知道它是在与一分钟前还是昨天的同一客户端通信,而无需在 URI 中供应混乱的查询字符串。
默认情形下,HttpClient 会忽略从做事器吸收的任何 Cookie。要接管 cookie,请创建一个 CookieContainer 工具并为其分配一个 HttpClientHandler:
var cc = new CookieContainer();var handler = new HttpClientHandler();handler.CookieContainer = cc;var client = new HttpClient (handler);...
要在将来的要求中重播收到的 Cookie,只需再次利用相同的 CookieContainer 工具即可。或者,您可以重新的 CookieContainer 开始,然夹帐动添加 cookie,如下所示:
Cookie c = new Cookie ("PREF", "ID=6b10df1da493a9c4:TM=1179...", "/", ".google.com");freshCookieContainer.Add (c);
第三个和第四个参数指示发起方的路径和域。客户端上的 CookieContainer 可以容纳来自许多不同位置的 Cookie;HttpClient 仅发送路径和域与做事器路径和域匹配的 cookie。
编写 HTTP 做事器把稳如果须要在 .NET 6 中编写 HTTP 做事器,另一种更高等别的方法是利用最小 API ASP.NET。以下是入门所需的全部内容:
var app = WebApplication.CreateBuilder().Build();app.MapGet ("/", () => "Hello, world!");app.Run();
您可以利用 HttpListener 类编写自己的 .NET HTTP 做事器。下面是一个大略的做事器,它侦听端口 51111,等待单个客户端要求,然后返回一行回答:
using var server = new SimpleHttpServer();// Make a client request:Console.WriteLine (await new HttpClient().GetStringAsync ("http://localhost:51111/MyApp/Request.txt"));class SimpleHttpServer : IDisposable{ readonly HttpListener listener = new HttpListener(); public SimpleHttpServer() => ListenAsync(); async void ListenAsync() { listener.Prefixes.Add ("http://localhost:51111/MyApp/"); // Listen on listener.Start(); // port 51111 // Await a client request: HttpListenerContext context = await listener.GetContextAsync(); // Respond to the request: string msg = "You asked for: " + context.Request.RawUrl; context.Response.ContentLength64 = Encoding.UTF8.GetByteCount (msg); context.Response.StatusCode = (int)HttpStatusCode.OK; using (Stream s = context.Response.OutputStream) using (StreamWriter writer = new StreamWriter (s)) await writer.WriteAsync (msg); } public void Dispose() => listener.Close();}OUTPUT: You asked for: /MyApp/Request.txt
在Windows上,HttpListener在内部不该用.NET Socket工具;相反,它调用Windows HTTP Server API。这许可打算机上的许多运用程序侦听相同的 IP 地址和端口,只要每个运用程序注册不同的地址前缀即可。在我们的示例中,我们注册了前缀 http://localhost/myapp,因此另一个运用程序可以自由侦听另一个前缀(如 http://localhost/anotherapp)上的同一 IP 和端口。这是有代价的,由于在公司防火墙上打开新端口在政治上可能很困难。
当您调用 GetContext 时,HttpListener 会等待下一个客户端要求,返回具有要乞降相应属性的工具。每个都类似于客户端要求或相应,但从做事器的角度来看。例如,您可以读取和写入标头和 Cookie 到要乞降相应工具,就像在客户端一样。
您可以根据预期的客户端受众选择完备支持 HTTP 协议功能的程度。至少应设置每个要求的内容长度和状态代码。
这是一个非常大略的网页做事器,
using System;using System.IO;using System.Net;using System.Text;using System.Threading.Tasks;class WebServer{ HttpListener _listener; string _baseFolder; // Your web page folder. public WebServer (string uriPrefix, string baseFolder) { _listener = new HttpListener(); _listener.Prefixes.Add (uriPrefix); _baseFolder = baseFolder; } public async void Start() { _listener.Start(); while (true) try { var context = await _listener.GetContextAsync(); Task.Run (() => ProcessRequestAsync (context)); } catch (HttpListenerException) { break; } // Listener stopped. catch (InvalidOperationException) { break; } // Listener stopped. } public void Stop() => _listener.Stop(); async void ProcessRequestAsync (HttpListenerContext context) { try { string filename = Path.GetFileName (context.Request.RawUrl); string path = Path.Combine (_baseFolder, filename); byte[] msg; if (!File.Exists (path)) { Console.WriteLine ("Resource not found: " + path); context.Response.StatusCode = (int) HttpStatusCode.NotFound; msg = Encoding.UTF8.GetBytes ("Sorry, that page does not exist"); } else { context.Response.StatusCode = (int) HttpStatusCode.OK; msg = File.ReadAllBytes (path); } context.Response.ContentLength64 = msg.Length; using (Stream s = context.Response.OutputStream) await s.WriteAsync (msg, 0, msg.Length); } catch (Exception ex) { Console.WriteLine ("Request error: " + ex); } }}
以下代码启动了操作:
// Listen on port 51111, serving files in d:\webroot:var server = new WebServer ("http://localhost:51111/", @"d:\webroot");try{ server.Start(); Console.WriteLine ("Server running... press Enter to stop"); Console.ReadLine();}finally { server.Stop(); }
您可以利用任何 Web 浏览器在客户端对此进行测试;在这种情形下,URI 将 http://localhost:51111/ 加上网页的名称。
警告如果其他软件竞争同一端口,HttpListener 将不会启动(除非该软件也利用 Windows HTTP Server API)。可能侦听默认端口 80 的运用程序示例包括 Web 做事器或对等程序(如 Skype)。
我们对异步函数的利用使该做事用具有可扩展性和效率。但是,从用户界面 (UI) 线程开始会阻碍可伸缩性,由于对付每个,实行会在每次等待后反弹回 UI 线程。鉴于我们没有共享状态,产生这样的开销特殊没故意义,因此在 UI 场景中,我们会像这样离开 UI 线程。
Task.Run (Start);
或者在调用 GetContextAsync 后调用 ConfigureAwait(false)。
请把稳,我们利用 Task.Run 来调用 ProcessRequestAsync,纵然该方法已经是异步的。这许可调用方处理另一个要求,而不必首先等待方法的同步阶段(直到第一个等待)。
利用域名解析静态 Dns 类封装 DNS,该 DNS 在原始 IP 地址(如 66.135.192.87)和人类友好域名(如 )之间进行转换。
方法从域名转换为 IP 地址(或多个地址):
foreach (IPAddress a in Dns.GetHostAddresses ("albahari.com")) Console.WriteLine (a.ToString()); // 205.210.42.167
GetHostEntry 方法则相反,从地址转换为域名:
IPHostEntry entry = Dns.GetHostEntry ("205.210.42.167");Console.WriteLine (entry.HostName); // albahari.com
GetHostEntry 还接管 IPAddress 工具,因此您可以将 IP 地址指定为字节数组:
IPAddress address = new IPAddress (new byte[] { 205, 210, 42, 167 });IPHostEntry entry = Dns.GetHostEntry (address);Console.WriteLine (entry.HostName); // albahari.com
当您利用 WebRequest 或 TcpClient 等类时,域名会自动解析为 IP 地址。但是,如果您操持在运用程序的生命周期内向同一地址发出许多网络要求,则有时可以通过首先利用 Dns 将域名显式转换为 IP 地址,然后从该点开始直接与 IP 地址通信来提高性能。这避免了重复来回解析相同的域名,并且在传输层(通过 TcpClient 、UdpClient 或 Socket )处理时可能会有所帮助。
DNS 类还供应可等待的基于任务的异步方法:
foreach (IPAddress a in await Dns.GetHostAddressesAsync ("albahari.com")) Console.WriteLine (a.ToString());
利用 SMTP 客户端发送邮件
命名空间中的 SmtpClient 类许可您通过无处不在的大略邮件传输协议 (SMTP) 发送邮件。要发送大略的文本,请实例化 SmtpClient ,将其 Host 属性设置为 SMTP 做事器地址,然后调用 发送 :
SmtpClient client = new SmtpClient();client.Host = "mail.myserver.com";client.Send ("from@adomain.com", "to@adomain.com", "subject", "body");
布局 MailMessage 工具会公开更多选项,包括添加附件的功能:
SmtpClient client = new SmtpClient();client.Host = "mail.myisp.net";MailMessage mm = new MailMessage();mm.Sender = new MailAddress ("kay@domain.com", "Kay");mm.From = new MailAddress ("kay@domain.com", "Kay");mm.To.Add (new MailAddress ("bob@domain.com", "Bob"));mm.CC.Add (new MailAddress ("dan@domain.com", "Dan"));mm.Subject = "Hello!";mm.Body = "Hi there. Here's the photo!";mm.IsBodyHtml = false;mm.Priority = MailPriority.High;Attachment a = new Attachment ("photo.jpg", System.Net.Mime.MediaTypeNames.Image.Jpeg);mm.Attachments.Add (a);client.Send (mm);
为了阻挡垃圾邮件发送者,互联网上的大多数SMTP做事器仅接管来自经由身份验证的连接的连接,并哀求通过SSL进行通信。
var client = new SmtpClient ("smtp.myisp.com", 587){ Credentials = new NetworkCredential ("me@myisp.com", "MySecurePass"), EnableSsl = true};client.Send ("me@myisp.com", "someone@somewhere.com", "Subject", "Body");Console.WriteLine ("Sent");
通过变动 DeliveryMethod 属性,可以指示 SmtpClient 改用 IIS 发送邮件,或者只是将每封邮件写入指定目录中的 文件。这在开拓过程中可能很有用。
SmtpClient client = new SmtpClient();client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;client.PickupDirectoryLocation = @"c:\mail";
利用TCP
TCP 和 UDP 构成了传输层协议,大多数互联网和 LAN 做事都在其上构建。HTTP(版本 2 及更低版本)、FTP 和 SMTP 利用 TCP;DNS 和 HTTP 版本 3 利用 UDP。TCP 是面向连接的,包括可靠性机制;UDP 是无连接的,开销较低,并支持广播。利用UDP,IP语音(VoIP)也是如此。
与较高层比较,传输层供应了更大的灵巧性,并可能提高性能,但它哀求您自己处理身份验证和加密等任务。
利用 .NET 中的 TCP,您可以选择更易于利用的 TcpClient 和 TcpListener 外不雅观类,也可以选择功能丰富的 Socket 类。 (实际上,您可以稠浊搭配,由于 TcpClient 通过 Client 属性公开根本套接字工具。Socket 类公开了更多的配置选项,并许可直接访问网络层 (IP) 和非基于 Internet 的协议,例如 Novell 的 SPX/IPX。
与其他协议一样,TCP 区分客户端和做事器:客户端发起要求,而做事器等待要求。下面是同步 TCP 客户端要求的基本构造:
using (TcpClient client = new TcpClient()){ client.Connect ("address", port); using (NetworkStream n = client.GetStream()) { // Read and write to the network stream... }}
TcpClient 的连接方法会壅塞,直到建立连接(ConnectAsync 是异步等价物)。然后,NetworkStream供应了一种双向通信办法,用于从做事器发送和吸收字节的数据。
一个大略的TCP做事器如下所示:
TcpListener listener = new TcpListener (<ip address>, port);listener.Start();while (keepProcessingRequests) using (TcpClient c = listener.AcceptTcpClient()) using (NetworkStream n = c.GetStream()) { // Read and write to the network stream... }listener.Stop();
TcpListener 须要侦听确当地 IP 地址(例如,具有两个网卡的打算机可以有两个地址)。您可以利用 IPAddress.Any 指示它侦听所有(或唯一)本地 IP 地址。AcceptTcpClient 壅塞,直到收到客户端要求(同样,还有一个异步版本),此时我们调用 GetStream ,就像在客户端一样。
在传输层事情时,您须要决定谁何时通话以及通话多永劫光的协议,就像利用对讲机一样。如果双方同时交谈或谛听,沟通就会中断!
让我们发明一个协议,在这个协议中,客户端首先说“你好”,然后做事器通过说“你好立时回来!
代码如下:
using System;using System.IO;using System.Net;using System.Net.Sockets;using System.Threading;new Thread (Server).Start(); // Run server method concurrently.Thread.Sleep (500); // Give server time to start.Client();void Client(){ using (TcpClient client = new TcpClient ("localhost", 51111)) using (NetworkStream n = client.GetStream()) { BinaryWriter w = new BinaryWriter (n); w.Write ("Hello"); w.Flush(); Console.WriteLine (new BinaryReader (n).ReadString()); }}void Server() // Handles a single client request, then exits.{ TcpListener listener = new TcpListener (IPAddress.Any, 51111); listener.Start(); using (TcpClient c = listener.AcceptTcpClient()) using (NetworkStream n = c.GetStream()) { string msg = new BinaryReader (n).ReadString(); BinaryWriter w = new BinaryWriter (n); w.Write (msg + " right back!"); w.Flush(); // Must call Flush because we're not } // disposing the writer. listener.Stop();}// OUTPUT: Hello right back!
在此示例中,我们利用 localhost 环回在同一台打算机上运行客户端和做事器。我们任意选择了未分配范围内的端口(高于 49152),并利用 BinaryWriter 和 BinaryReader 对文本进行编码。我们避免关闭或处置读取器和编写器,以便在我们的对话完成之前保持底层 NetworkStream 打开。
BinaryReader 和 BinaryWriter 彷佛是读取和写入字符串的奇怪选择。但是,它们比StreamReader和StreamWriter有一个紧张上风:它们在字符串前面加上一个指示长度的整数,因此BinaryReader总是确切地知道要读取多少字节。如果你调用StreamReader.ReadToEnd,你可能会无限期地阻挡,由于NetworkStream没有终点!
只要连接处于打开状态,网络流就永久无法确定客户端不会发送更多数据。
StreamReader实际上完备超出了NetworkStream的界线,纵然你只打算调用ReadLine。这是由于 StreamReader 具有预读缓冲区,这可能导致它读取的字节数超过当前可用的字节数,从而无限期壅塞(或直到套接字超时)。其他流(如 FileStream)不会遭受与 StreamReader 的这种不兼容,由于它们有一个明确的 — 此时 Read 立即返回值 0 。
与 TCP 并发TcpClient 和 TcpListener 供应基于任务的异步方法,以实现可扩展的并发性。利用这些只是将阻挡方法调用更换为其 Async 版本并等待返回的任务的问题。
不才面的示例中,我们编写了一个异步 TCP 做事器,该做事器接管长度为 5,000 字节的要求,反转字节,然后将其发送回客户端:
async void RunServerAsync (){ var listener = new TcpListener (IPAddress.Any, 51111); listener.Start (); try { while (true) Accept (await listener.AcceptTcpClientAsync ()); } finally { listener.Stop(); }}async Task Accept (TcpClient client){ await Task.Yield (); try { using (client) using (NetworkStream n = client.GetStream ()) { byte[] data = new byte [5000]; int bytesRead = 0; int chunkSize = 1; while (bytesRead < data.Length && chunkSize > 0) bytesRead += chunkSize = await n.ReadAsync (data, bytesRead, data.Length - bytesRead); Array.Reverse (data); // Reverse the byte sequence await n.WriteAsync (data, 0, data.Length); } } catch (Exception ex) { Console.WriteLine (ex.Message); }}
这样的程序是可扩展的,由于它不会在要求期间壅塞线程。因此,如果 1,000 个客户端通过慢速网络连接同时连接(例如,每个要求从开始到结束须要几秒钟),则该程序在这段韶光内不须要 1,000 个线程(与同步办理方案不同)。相反,它仅在 await 表达式之前和之后实行代码所需的短韶光内租用线程。
利用 TCP 吸收 POP3 邮件.NET 不供应对 POP3 的运用程序层支持,因此您须要在 TCP 层写入才能从 POP3 做事器吸收邮件。幸运的是,这是一个大略的协议;POP3 对话是这样的:
客户
邮件做事器
条记
客户端连接...
+好的 你好。
欢迎辞
用户乔
+确定 须要密码。
通票密码
+确定已登录。
列表
+OK 1 1876 2 5412 3 845 .
列出做事器上每封邮件的 ID 和文件大小
RETR 1
+OK 1876 八位字节 .
检索具有指定 ID 的邮件
德勒 1
+确定已删除。
从做事器中删除邮件
退出
+好的再见。
每个命令和相应都由换行符 (CR + LF) 终止,但多行 LIST 和 RETR 命令除外,它们由单独行上的单个点终止。由于我们不能将 StreamReader 与 网络流 ,我们可以从编写一个赞助方法开始,以非缓冲办法读取一行文本:
string ReadLine (Stream s){ List<byte> lineBuffer = new List<byte>(); while (true) { int b = s.ReadByte(); if (b == 10 || b < 0) break; if (b != 13) lineBuffer.Add ((byte)b); } return Encoding.UTF8.GetString (lineBuffer.ToArray());}
我们还须要一个帮助程序方法来发送命令。由于我们总是期望收到以 +OK 开头的相应,以是我们可以同时读取和验证相应:
void SendCommand (Stream stream, string line){ byte[] data = Encoding.UTF8.GetBytes (line + "\r\n"); stream.Write (data, 0, data.Length); string response = ReadLine (stream); if (!response.StartsWith ("+OK")) throw new Exception ("POP Error: " + response);}
编写这些方法后,检索邮件的事情很随意马虎。我们在端口 110(默认 POP3 端口)上建立 TCP 连接,然后开始与做事器通信。在此示例中,我们将每封邮件写入扩展名为 的随机命名文件,然后再从做事器中删除邮件:
using (TcpClient client = new TcpClient ("mail.isp.com", 110))using (NetworkStream n = client.GetStream()){ ReadLine (n); // Read the welcome message. SendCommand (n, "USER username"); SendCommand (n, "PASS password"); SendCommand (n, "LIST"); // Retrieve message IDs List<int> messageIDs = new List<int>(); while (true) { string line = ReadLine (n); // e.g., "1 1876" if (line == ".") break; messageIDs.Add (int.Parse (line.Split (' ')[0] )); // Message ID } foreach (int id in messageIDs) // Retrieve each message. { SendCommand (n, "RETR " + id); string randomFile = Guid.NewGuid().ToString() + ".eml"; using (StreamWriter writer = File.CreateText (randomFile)) while (true) { string line = ReadLine (n); // Read next line of message. if (line == ".") break; // Single dot = end of message. if (line == "..") line = "."; // "Escape out" double dot. writer.WriteLine (line); // Write to output file. } SendCommand (n, "DELE " + id); // Delete message off server. } SendCommand (n, "QUIT");}
把稳
可以在 NuGet 上找到开源 POP3 库,这些库为协议方面供应支持,例如身份验证 TLS/SSL 连接、MIME 剖析等。