16.1 网络体系结构

System.Net.* 命名空间包含各种支持标准网络协议的通信。

  • WebClient 外观类:支持通信HTTP或FTP执行简单的下载/上传操作。
  • WebRequestWebResponse 类:支持更多的客户端HTTP或FTP操作。
  • HttpListener 类:可用来编写HTTP服务器。
  • SmtpClient类:支持通过 SMTP 创建和发送电子邮件。
  • DNS类:支持域名和地址之间的转换。
  • TcpClientUdpClientTcpListenerSocket类:支持传输层和网络层的直接访问。

16.2 地址与端口

IPv4

  • 目前主流,32位。用点号分隔的4个十进制数(101.102.103.104)。地址可能是唯一,也可能是一个特定子网中的唯一(例如,企业网络)。

IPv6

  • 128位地址。这些地址用冒号分隔的十六进制数(例如,[3EA0:FFFF:198A:E4A3:4FF2:54fA:41BC:8D31])。.NET FrameWork要求加方括号。

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地址划分为65535个端口,从而允许一台计算机在一个地址上运行多个应用程序,每一个应用程序使用一个端口。许多应用程序都分配有标准端口,例如,HTTP使用端口80,SMTP使用端口25。

49152到65535的TCP和UDP端口官方保留,它们只用于测试和小规模部署。

IP地址和端口组合在.NETFramwork 使用 IPEndPoint 类表示:

            IPAddress a = new IPAddress(new byte[] { 101, 102, 103, 104 });IPEndPoint ep = new IPEndPoint(a, 222);    //端口:222Console.WriteLine(ep.ToString());          //101.102.103.104:222

16.3 URI

URI描述了一个 Internet 或 LAN 的资源,例如网页。文件或电子邮件地址。
URI一般氛围三个元素:协议(scheme)、权限(authority)和路径(path)。System.Uri 类正是采用这种划分方式,为每一种元素提供对应的属性。

在构造函数中传入以下字符串之一,就可以创建一个 Uri 对象:

  • URI字符串,例如http://www.ebay.com 或 file://janespc/sharedpics/dolphin.jpg
  • 硬盘中一个文件的绝对路径,例如 c:\myfiles\data.xls
  • LAN中一个文件的 UNC路径,例如 \janespc\sharedpics\dolphin.jpg

文件和UNC路径会自动转换为URI:添加协议 "file:",反斜杠会转换为斜杠。Uri的构造函数在创建Uri之前也会对传入的字符串执行一些基本的清理操作,包括将协议和主机名转换为小写、删除默认端口和空端口号。如果传入一个不带协议的URI字符串,例如"www.test.com",那么会抛出一个 UriFormatException 异常。

Uri 有一个 IsLoopback 属性,它表示 Uri 是否引用本地主机(IP地址为127.0.0.1),以及一个 IsFile 属性,它表示Uri是否引用一个本地或UNC(IsUnc)路径。如果 IsFile 返回 true,LocalPath 属性会返回一个符合本地操作系统习惯的 AbsolutePath(带反斜杠),然后可以用它来调用 File.OPen

Uri 的实例有一些只读属性。要修改一个 Uri,我们需要实例化一个 UriBuilder 对象,这是一个可写属性,它可以通过 Uri 属性转换为 Uri。

        Uri info = new Uri("http://www.domain.com:80/hosting/");Uri page = new Uri("http://www.domain.com/hosting/page.html");Console.WriteLine(info.Host);             //www.domain.comConsole.WriteLine(info.Port);             //80Console.WriteLine(page.Port);             //80Console.WriteLine(info.IsBaseOf(page));      //TrueUri relative = info.MakeRelativeUri(page);Console.WriteLine(relative.IsAbsoluteUri);   //FlaseConsole.WriteLine(relative.ToString());      //page.html

Uri 一些静态方法:

Uri.EscapeDataString(url);
UriHostNameType type =  Uri.CheckHostName(url);
bool isScheme =  Uri.CheckSchemeName(url);

16.4 客户端类

WebRequest 和 WebRespone是管理 HTTP 和 FTP 客户端活动及 "file:" 协议的通用基类。

WebClient 是一个便利的门面类。它负责调用 WebRequestWebRespone,可以节省很多编码。WebClient 支持字符串、字节数组、文件和流,而 WebRequestWebRespone 只支持流。但是,WebClient不是万能的,因为它不支持某些特性(如cookie)。

HttpClient 是另一个基于 WebRequestWebRespone 的类(更准确说基于HttpWebRequest和HttpWebResponse),Framework 4.5 引入。

WebClient主要作为请求/响应类之上薄薄的一层,而 HttpClient 则增加更多功能,能够处理基于HTTP的Web API、基于REST的服务和自定义验证模式。

WebClientHttpClient都支持以字符串或字节数组方式处理简单的文件下载/上传操作。它们都拥有一些异步方法,但是只有 WebClient 支持进度报告。

  请求/响应类 引入 异步进度报告
WebClient 薄薄一层 -
HttpClient 处理基于HTTP的Web API等 Framework 4.5

提示:WinRT应用程序不能使用 WebClient,必须使用WebRequest/WebResponseHttpClient(用于HTTP连接)

16.4.1 WebClient

WebClient 使用步骤:

  1. 实例化一个 WebClient 对象。
  2. 设置 Proxy 属性值。
  3. 在需要验证时设置 Credentials 属性值。
  4. 使用响应的 URI 调用 DownliadXXXUploadXXX 方法。

下载方法有:

public void DownloadFile(string address, string fileName);
public string DownloadString(string address);
public byte[] DownloadData(string address);
public Stream OpenRead(string address);

每一个方法都有重载,接收 URI 对象代替字符串地址的参数。上传类似,返回包含服务器响应的值:

public byte[] UploadFile(string address, string fileName);
public byte[] UploadFile(Uri address, string fileName);
public byte[] UploadFile(string address, string method, string fileName);
public byte[] UploadFile(Uri address, string method, string fileName);
...
public string UploadString(string address, string data);
public byte[] UploadData(string address, byte[] data);
public Stream OpenWrite(string address);
...

UploadValues 方法可用于以 POST 方法参数提交一个 HTTP 表单的值。WebClient 还包含一个 BaseAddress 属性,可用于为所有地址添加一个字符串前缀,如http://www.mysite.com/data。

public byte[] UploadValues(string address, NameValueCollection data);
...

例子,下载 http://www.cnblogs.com/artwl/archive/2012/03/07/2382848.html 并以 code111.htm 保存

            System.Net.WebClient webClient = new System.Net.WebClient();webClient.Proxy = null;webClient.DownloadFile("http://www.cnblogs.com/artwl/archive/2012/03/07/2382848.html", "code111.htm");System.Diagnostics.Process.Start("code111.htm");

从 Framework 4.5 开始,WebClient 提供了长任务方法的异步版本,他们会返回可以等待的任务

await webClient.DownloadFileTaskAsync("http://www.qq.com", "wegpage.htm");

这些方法使用"TaskAsync"后缀,不同于使用"Async"后缀的EAP旧异步方法。但是,新方法不支持取消操作和进步报告的标准“TAP”模式。相反,在处理延续时,必须调用 WebClient 对象的 CanceAsync 方法;而处理报告时,需要处理 DownloadProgressChanged/UploadProgressChanged 事件。

下面例子下载一个网页并显示进度报告,如果下载超过5秒,则取消下载。

//winform.private void button1_Click(object sender, EventArgs e){Test();}async Task Test(){var wc = new WebClient();wc.DownloadProgressChanged += (sender, args) =>//                获取异步任务的进度百分比Console.WriteLine(args.ProgressPercentage + "% 完成");Task.Delay(5000).ContinueWith(ant => wc.CancelAsync());await wc.DownloadFileTaskAsync("http://www.qq.com", "wegpage.htm");}

提示: 当请求取消时,程序会抛出一个 WebException 异常,它的 Status 属性是 WebExceptionStatus.RequestCanceled.(历史原因不抛出 OpeerationCanceledException 异常。)

捕捉与进度相关的事件,将它们提交到激活的同步上下文,所以它们的处理器不需要使用 Dispatcher.BeginInvoke,就可以更新UI控件。

警告: 如果需要使用取消操作或进度报告,要避免使用同一个 WebClient 对象依次执行多个操作,因为这样形成竞争条件。

16.4.2 WebRequest 和 WebResponse

比 WebClient 复杂,但是更灵活。
1.使用一个 URI 调用 WebRequest.Create,创建一个 Web 请求实例。
2.设置 Proxy 属性。
3.如果需要身份验证,设置 Credentials 属性。

如果要上传数据,则:
4.调用请求对象的 GetRequestStream,然后在流中写入数据。如果需要处理响应,则转到第5步。

如果要下载数据,则:
5.调用请求对象的 GetResponse,创建一个 Web 响应实例。

6.调用响应对象的 GetResponseStream,然后(可以使用 StreamReader)从流中读取数据。

下面例子演示如何下载和显示一个示例网页

        //同步public static void Test1(){WebRequest req = WebRequest.Create("http://www.baidu.com");  //1.req.Proxy = null;                                            //2.using (WebResponse res = req.GetResponse())                  //5.下载{using (Stream rs = res.GetResponseStream())              //6.{using (FileStream fs = File.Create("code.html")){rs.CopyTo(fs);}}}}

异步方式

        //异步public static async void AsyncTest(){WebRequest req = WebRequest.Create("http://www.qq.com");req.Proxy = null;using (WebResponse res = await req.GetResponseAsync()){using (Stream rs = res.GetResponseStream()){using (FileStream fs = File.Create("code2.html")){await rs.CopyToAsync(fs);}}}}

静态方法 Create 会创建一个 WebRequest 类型的子类实例,如 HttpWebRequestFtpWebRequest
它选择的子类取决 URI 的前缀。

前缀 Web请求类型
http:或https: HttpWebRequest
ftp: FtpWebRequest
file: FileWebRequest

此外,调用 WebRequest.RegisterPrefix,可以注册自定义前缀。

WebRequest 包含一个 Timeout 属性,单位为毫秒。如果出现超时,那么会抛出一个 WebException 异常,其中包含一个 Status 属性,WebExceptionStatus.Timeout。HTTP默认超时时间为100秒,而FTP超时时间为无限。

WebRequest 对象不能回收并用于处理多个请求——每个实例只适用于一个作业。

16.4.3 HttpClient

HttpClient 在 HttpWebRequest 和 HttpWebRespone 之上提供了另一层封装。它的设计是为了支持越来越多的 Web API 和 REST 服务,在处理比获取网页等更复杂的协议时实现比 WebClient 更佳的体验。

  • 一个 HttpClient 就可以支持并发请求。(使用 WebClient 处理并发请求,需要为每一个并发线程创建一个新实例,需要自定义请求头、cookies和验证模式,因此很麻烦)
  • HttpClient 可用于编写和插入自定义消息处理器。这样可以创建单元测试桩函数,以及创建自定义管道(用于记录日志、压缩、加密等)调用WebClient的单元测试代码则很难编写。
  • HttpClient 包含丰富的可扩展的请求头与内容类型系统。

    HttpClient 不能完全替代 WebClient,因为它不支持进度报告。WebClient 也有一个有点,它支持 FTP、file://和自定义URI模式,适应所有Framework版本。

简单 HttpClient 使用方法创建一个实例,然后使用 URI 调用 其中一个 Get* 方法:

    string html = await new HttpClient().GetStringAsync("http://linqpad.net");

(另外还有 GetByteArrayAsync和GetStreamAsync)HttpClient的所有 I/O 密集型方法都是异步的(没有同步版本)。

与 WebClient 不同,想获取最佳性能 HttpClient,必须重用相同的实例。HttpClient 允许并发操作,所以下面语句合法。同时下载两个网页:

    var client = new HttpClient();var tast1 = client.GetStringAsync("http://www.linqpad.net");var tast2 = client.GetStringAsync("http://www.albahari.com");Console.WriteLine(await task1);Console.WriteLine(await task2);

HttpClinet 包含一个 TimeOut 属性和一个 BaseAddress 属性,为每一个请求添加一个 URI 前缀。
HttpClient 在一定程度上就是一层实现:通常使用的大部分属性都定义在另一个类中,即 HttpClientHandler。要访问这个类,先创建一个实例,然后将这个实例传递给 HttpClient 的构造方法:

    var handler = new HttpClientHandler{UseProxy = false};var  client = new HttpClient(handler);...

这个例子在处理器中禁用了代理支持。此外,还有其他一些属性可用于控制 cookies、自动重定向、验证等(后续看16.5“HTTP访问”)

1.GetAsync与响应消息

GetStringAsync、GetByteArrayAsync和GetStreamAsync方法是更常用的 GetAsync 方法的快捷方法。GetAsync方法会返回一个响应消息:

        var client = new HttpClient();//GetAsync 方法也接受一个 CancellationTokenHttpResponseMessage response = await client.GetAsync("http://www.baidu.com");response.EnsureSuccessStatusCode();string html = await response.Content.ReadAsStringAsync();Console.WriteLine(html);

HttpResponseMessage 包含一些访问请求头,和HTTP StatusCode 的属性,与 WebClient 不同。除非显示地调用 EnsureSuccessStatusCode,否则返回不成功状态(如404,资源未找到)不会抛出异常,然而,通信或DNS错误会抛出异常。

HttpResponseMessage 包含一个 CopyToAsync 方法,它可以将数据写到一个流中,适用于将输入写到一个文件中:

using (var fileStream =File.Create("linqpad.htm"))
{await response.Content.CopyToAsync(fileStream);
}

GetAsync 是与 HTTP 的4种动作相关的4个方法之一(PostAsync,PutAsync,DeleteAsync)

2.SendAsync与请求消息

这是访问所有数据的唯一低层方法,要使用这个类,首先要创建一个 HttpRequestMessage

    var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Get, "http://www.weibo.com");HttpResponseMessage response = await client.SendAsync(request);response.EnsureSuccessStatusCode();...

创建一个 HttpResponseMessage 对象,意味着可以自定义请求的属性,如请求头和内容本身,它们可用于上传数据。

3.上传数据与HttpContent

创建一个 HttpRequestMessage 对象以后,设置它的 Content 属性,就可以上传内容。这个属性类型是抽象类 HttpContent。
Framework包含下面子类,对应不同类型。

  • ByteArrayContent
  • StreamContent
  • FormUrlEncodedContent
  • StreamContent
    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());

4.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 的扩展点。

5.单元测试与桩处理器

创建 HttpMessageHandler 的子类,就可以创建一个帮助进行单元测试的桩处理器:
...

6.使用实现处理器链

创建 DelegatingHandler 的子类,就可以创建一个调用其他消息处理器的消息处理器(形成处理器链)。这种方法适用于实现自定义身份验证、压缩和加密协议。
例子,简单的日志处理器:

    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 时保持异步性。在重载任务的方法时添加 async 修饰符是绝对合法的如这个例子。
相对于将信息直接写到控制台,更好的方法是给构造方法传入某种日志记录对象。最好是接受一对 Action<T> 代理,然后让他们记录请求和响应对象的日志信息。

16.4.4 代理

16.4.5 身份验证

创建一个 NetworkCredential 对象,...

16.4.6 异常处理

16.5 HTTP访问

16.5.1 请求头

WebClient、WebRequest 和 HttpClient 都可以添加自定义HTTP请求头,以及在响应中列举请求头信息。
请求头只是一些键/值对,其中包含相应的元数据,如消息内容类型或服务器软件。

例子演示如何在请求中添加自定义请求头信息,然后在 HttpClient 的响应消息中列举所有请求头信息:

        WebClient wc =new WebClient();wc.Proxy = null;wc.Headers.Add("CustomHeader","JustPlaying/1.0");wc.DownloadString("http://www.cnblogs.com");foreach (string name in wc.ResponseHeaders.Keys){Console.WriteLine(name + "=" + wc.ResponseHeaders[name]);}

相反,HttpClient 包含一些强类型集合,其中包含与标准HTTP头信息相对应的属性。
DefaultRequestHeaders 属性包含适用于每一个请求的头信息:

        var client = new HttpClient(handler);   //handler = new HttpClientHandler() { UseProxy = false };client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("VisualStudio","2013"));client.DefaultRequestHeaders.Add("CustomHeader", "VisualStudio/2013");

HttpRequestMessage 类的 Headers 属性则包含请求头特有的头信息。

16.5.2 查询字符串

?key1=value1&key2=value2&key3=value3...
WebClient 包含一个字段风格的属性,它可以简化查询字符串的操作。
例子,在百度搜索单词 "WebClient" ,然后显示结果:

    WebClient wc = new WebClient();wc.Proxy = null;wc.QueryString.Add("wd", "WebClient");     // Search for "WebClient"//wc.QueryString.Add("hl", "fr");           // 本来想法语,貌似错的,这个是谷歌规则wc.DownloadFile("http://www.baidu.com/s", "results.html");System.Diagnostics.Process.Start("results.html");

如果要使用 WebRequest 或 HttpClient 实现相同效果,那么必须手工赋值给请求URL正确格式字符串:

string requestURI = "https://www.baidu.com/s?wd=webclient";

如果查询中包含符号或空格,那么使用 URI 的 EscapeDataString 方法:

string search = Uri.EscapeDataString("(WebClient OR HttpClient)");string language = Uri.EscapeDataString("fr");string requestURI = "http://www.google.com/search?q=" + search +"&hl=" + language;

结果为 http://www.google.com/search?q=(WebClient%20OR%20HttpClient)&hl=fr

16.5.3 上传表单数据

WebClent 的 UploadValues 方法可以把HTML表单的方式提交数据:

    WebClient wc = new WebClient();wc.Proxy = null;var data = new System.Collections.Specialized.NameValueCollection();data.Add("Name", "Joe Albahari");data.Add("Company", "O'Reilly");byte[] result = wc.UploadValues("http://www.albahari.com/EchoPost.aspx","POST", data);Console.WriteLine(Encoding.UTF8.GetString(result));

结果:
You posted=Name=Joe+Albahari&Company=O'Reilly

NameValueCollection 中的键(如 searchtextbox 和 searchMode) 与HTML表单的输入框相对应。使用 WebRequest 上传表单数据操作更复杂。(如果需要使用 cookies等特性,则必须采用这种方法)

下面是具体操作过程:

  1. 将请求的 ContentType 设置为 "application/x-www.form-urlencoded",将它的方法设置为“POST”。
  2. 创建一个包含上传数据的字符串,并且将其编码为:
    name1=value1&name2=value2&name3=value3...
  3. 使用 Encoding.UTF8.GetBytes 将字符串转为字节数组。
  4. 将 Web 请求的 ContentLength 属性设置为字节组的长度。
  5. 调用 Web 请求的 GetRequestStream,然后写入数据数组。
  6. 调用 GetResponse,读取服务器的响应。
//WebRequest 上传表单数据
var req = WebRequest.Create ("http://www.albahari.com/EchoPost.aspx");
req.Proxy = null;
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";string reqString = "Name=Joe+Albahari&Company=O'Reilly";
byte[] reqData = Encoding.UTF8.GetBytes (reqString);
req.ContentLength = reqData.Length;using (Stream reqStream = req.GetRequestStream())reqStream.Write (reqData, 0, reqData.Length);using (WebResponse res = req.GetResponse())
using (Stream resSteam = res.GetResponseStream())
using (StreamReader sr = new StreamReader (resSteam))Console.WriteLine (sr.ReadToEnd());

如果使用 HttpClient,则要创建和生成 FormUrlEncodedContent 对象,然后再传递到 PostAsync 方法,或者设置到请求的 Content 属性上。

//HttpClient 上传表单数据
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());

16.5.4 Cookies

Cookies是名称/值字符串对,它是HTTP服务器通过响应头发送到客户端。Web浏览器客户端一般会记住cookie,然后在终止之前,后续请求都会将它们重复发送给服务器(相同地址)。
Cookie 使服务器知道它是否正在连接之前连接过的相同客户端,从而不需要在URI重复添加负责查询字符串。

默认情况,HttpWebRequest 会忽略从服务器接收的任意 cookie。为了接收 cookie,必须创建一个 CookieCotainer 对象,然后分配到WebRequest。然后,就可以列举响应中接收到的 cookie:

CookieContainer cc = new CookieContainer();var request = (HttpWebRequest) WebRequest.Create ("http://www.baidu.com");
request.Proxy = null;
request.CookieContainer = cc;
using (var response = (HttpWebResponse) request.GetResponse())
{foreach (Cookie c in response.Cookies){Console.WriteLine (" Name:   " + c.Name);Console.WriteLine (" Value:  " + c.Value);Console.WriteLine (" Path:   " + c.Path);Console.WriteLine (" Domain: " + c.Domain);}// Read response stream 读取响应流...
}


如果使用 HttpClient,则需要创建一个 HttpClientHandler 实例:

var cc = new CookieContainer();
var handler = new HttpClientHandler();
handler.CookieContainer = cc;  //设置
var client = new HttpClient (handler);

WebClient 不支持 cookie。
如果需要在将来的请求上接收到的 cookie,则只需要给每一个新的 WebRequest 对象设置相同的 CookieContainer 对象,或者在 HttpClient 中使用相同对象发送请求。
CookieContainer 是可序列化的类,所以它可以写到磁盘中。此外,可以先使用一个新的 CookieContainer,然后再按照下面的方法手动添加cookie:

        CookieContainer cc = new CookieContainer();....Cookie c = new Cookie("BAIDUID","00DD40CC4B46.....","/",".baidu.com");cc.Add(c);

16.5.5 表单验证

16.4.5 身份验证 介绍如何使用 NetworkCredentials 对象实现其中一些类型的验证,如Basic或NTLM(在 Web浏览器弹出一个对话框)。然而,大多数验证的网站都使用某种基于表单的方法。
用户在文本框输入用户名和密码,单击按钮提交数据,然后在成功验证之后接收到 cookie,这个 cookie 属于所访问网页的私有数据。通过 WebRequest 或 HttpClient,就可以实现。

<form action="http://www.somesite.com/login" method="post"><input type="text" name="username" id="user"><input type="password" name="password" id="pass"><button type="submit" id="login-btn">Log In</button>
</form>

下面演示 WebRequest/WebResponse 登录网站:

string loginUri = "http://www.somesite.com/login";
string username = "username";   // (Your username)
string password = "password";   // (Your password)
string reqString = "username=" + username + "&password=" + password;
byte[] requestData = Encoding.UTF8.GetBytes (reqString);CookieContainer cc = new CookieContainer();
var request = (HttpWebRequest)WebRequest.Create (loginUri);
request.Proxy = null;
request.CookieContainer = cc;
request.Method = "POST";request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = requestData.Length;using (Stream s = request.GetRequestStream())s.Write (requestData, 0, requestData.Length);using (var response = (HttpWebResponse) request.GetResponse())foreach (Cookie c in response.Cookies)Console.WriteLine (c.Name + " = " + c.Value);//我们现在已经成功登录,只要给后续的 WebRequest 对象添加 cc,就可以保持已验证用户的状态

使用 HttpClient 实现的方法:

string loginUri = "http://www.somesite.com/login";
string username = "username";
string password = "password";CookieContainer cc = new CookieContainer();
var handler = new HttpClientHandler { CookieContainer = cc };var request = new HttpRequestMessage (HttpMethod.Post, loginUri);
request.Content = new FormUrlEncodedContent (new Dictionary<string,string>
{{ "username", username },{ "password", password }
});var client = new HttpClient (handler);
var response = await client.SendAsync (request);
response.EnsureSuccessStatusCode();

16.5.6 SSL

16.6 编写HTTP服务器

我们可以使用 HttpListener 类编写自定义 HTTP 服务器,下面是一个监听端口 51111 的简单服务器,它会等待一个客户端请求,然后返回一行回复。

static void Main()
{ListenAsync();                           // Start serverWebClient wc = new WebClient();          // Make a client request.Console.WriteLine (wc.DownloadString("http://localhost:51111/MyApp/Request.txt"));
}async static void ListenAsync()
{HttpListener listener = new HttpListener();listener.Prefixes.Add ("http://localhost:51111/MyApp/");  // Listen onlistener.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);listener.Stop();
}

输出You asked for: /MyApp/Request.txt

HttpListener 内部并步使用 .net Socket 对象,相反,它调用 Windows HTTP Server API。支持计算机中多个应用程序监听相同的IP地址和端口,前提是每一个应用程序都注册不同的地址前缀。

调用 GetContext 时,HttpListener 会等待下一个客户端请求,这个方法会返回一个对象,其中包含 Request 和 Response 属性。每一个属性都与 WebRequest 和 WebResponse 对象类似,但是它们属于服务器。例如,我们可以读写请求和响应对象的头信息和 Cookie,使用的方法与客户端非常类似。

我们可以基于预期客户端类型选择如何支持 HTTP 协议特性。至少,每一个请求都需要设置内容长度和状态码。

下面这个简单网页服务器,最多支持50并发请求:

using System;
using System.IO;
using System.Net;
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); }}
}

下面是启动程序的 Main 方法:

static void Main()
{// 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/ 加上网站名称。

警告: 如果其他软件占用同一个端口,那么 HttpListener 不会启动(除非这个软件也使用Windows HTTP Server API)

使用异步函数可以实现服务器的可扩展性和提高运行效率。然而,从UI线程开始都可能会影响可扩展性,因为每一个请求中,执行过程都会在每一次等待之后返回UI线程。这种等待毫无意义,因为没有共享的状态。所以UI中,最好去掉UI线程,或者这样:
Task.Run(start);
或者在调用 GetContextAsync 之后调用 ConfigureAwait(false)

注意,即使这些方法是异步的,但我们仍然在 Task.Run 中调用 ProcessRequestAsync。这样就尅允许调用者马上能够处理另一个请求,而不需要同步等待方法执行结束(一直到第一个 await)。

转载于:https://www.cnblogs.com/tangge/p/6933880.html

16.网络《果壳中的c#》相关推荐

  1. 16 | 网络优化(中):复杂多变的移动网络该如何优化?

    在 PC 互联网时代,网络优化已经是一项非常复杂的工作.对于移动网络来说,弱网络.网络切换.网络劫持这些问题更加突出,网络优化这项工作也变得更加艰巨. 那作为一名移动开发者,面对复杂多变的移动网络我们 ...

  2. 《果壳中的C# C# 5.0 权威指南》 (09-26章) - 学习笔记

    <果壳中的C# C# 5.0 权威指南> ========== ========== ========== [作者] (美) Joseph Albahari (美) Ben Albahar ...

  3. 《果壳中的C# C# 5.0 权威指南》 (01-08章) - 学习笔记

    <果壳中的C# C# 5.0 权威指南> ========== ========== ========== [作者] (美) Joseph Albahari (美) Ben Albahar ...

  4. 网络编程中的关键问题总结

    网络编程中的关键问题总结 总结下网络编程中关键的细节问题,包含连接建立.连接断开.消息到达.发送消息等等: 连接建立 包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接 ...

  5. python的功能模块_Python的功能模块[1] - struct - struct 在网络编程中的使用

    struct模块/ struct Module 在网络编程中,利用 socket 进行通信时,常常会用到 struct 模块,在网络通信中,大多数传递的数据以二进制流(binary data)存在.传 ...

  6. iOS网络开发中的同步、异步和请求队列

    在iOS网络编程中,我们经常会遇到线程的同步和异步问题,同时为了对异步请求更加精准丰富的控制,我们还常常在iOS中使用请求队列,下面就来谈谈iOS开发中同步.异步以及请求队列的使用方法. 1. 同步意 ...

  7. Android网络传输中必用的两个加密算法:MD5 和 RSA (附java完成测试代码)

    MD5和RSA是网络传输中最常用的两个算法,了解这两个算法原理后就能大致知道加密是怎么一回事了.但这两种算法使用环境有差异,刚好互补. 一.MD5算法 首先MD5是不可逆的,只能加密而不能解密.比如明 ...

  8. java面试题32:Java网络程序设计中,下列正确的描述是()

    java面试题32:Java网络程序设计中,下列正确的描述是() A:Java网络编程API建立在Socket基础之上 B:Java网络接口只支持tcP以及其上层协议 C:Java网络接口只支持UDP ...

  9. 数据库如何处理数据库太大_网络数据库中的数据处理

    数据库如何处理数据库太大 Before learning the data manipulation in a network model, we are discussing data manipu ...

  10. 用java网络编程中的TCP方式上传文本文件及出现的小问题

    自己今天刚学java网络编程中的TCP传输,要用TCP传输文件时,自己也是遇到了一些问题,抽空把它整理了一下,供自己以后参考使用. 首先在这个程序中,我用一个客户端,一个服务端,从客户端上传一个文本文 ...

最新文章

  1. Nginx搭建负载均衡集群
  2. actionscript代码练习作品
  3. “vector”: 不是“std”的成员_libcxx 的 std::function 源码分析
  4. 判断浏览器版本语句大全
  5. java的equals方法_Java Vector equals()方法与示例
  6. 从SQL Server数据库转到Oracle数据库的数据脚本处理
  7. 蓝桥杯 ADV-14 算法提高 卡勒沃夫之弱水路三千(提高型)
  8. 为tomcat分配内存
  9. “能耗双控”,汽车行业如何应对——安科瑞汽车工业能效管理系统为您解决
  10. 企业微信自建应用 网页授权登录 获取用户信息
  11. 本科生学习的一些心得
  12. 错误No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbala
  13. 计算机等级考试ppt怎么做,计算机等级考试PPT教学.ppt
  14. ppt文字磨砂玻璃效果制作教程
  15. 怎么将excel表格转换成word文档
  16. 简易的MySQL主从复制
  17. jQuery事件使用
  18. 新华三面试总结(二)
  19. 计算机科学个人陈述中文,计算机专业个人陈述二十(计算机科学)
  20. October 12th 2017 Week 41st Thursday

热门文章

  1. 电机与matlab突然,电机与MATLAB(第2版)简介,目录书摘
  2. ARM开发板系统移植-----rootfs的制作
  3. 进行软件评分的功能实现
  4. 经典论文推导: As-Rigid-As-Possible(ARAP) Surface Modeling
  5. linux查看日志文件内容命令tail、cat、tac、head、echo
  6. FM调制的FPGA实现
  7. UML类图以及类与类之间的关系
  8. mel滤波matlab,MFCC(Mel Frequency Cepstral Coefficient)提取过程详解
  9. R语言笔记⑧——数据挖掘算法
  10. 第二十四章 Caché 变量大全 $ZA 变量