首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >dotnet C# 入门示例 用底层的 Socket 进行 HTTP 网络请求

dotnet C# 入门示例 用底层的 Socket 进行 HTTP 网络请求

作者头像
林德熙
发布于 2025-06-17 01:27:30
发布于 2025-06-17 01:27:30
15900
代码可运行
举报
文章被收录于专栏:林德熙的博客林德熙的博客
运行总次数:0
代码可运行

本文属于基础入门博客,将和大家介绍如何在 dotnet C# 代码里面使用底层的 Socket 进行 HTTP 网络请求

本文将使用向百度发送 HTTP 和 HTTPS 请求作为示例,来和大家样式如何使用底层的 Socket 进行 HTTP 和 HTTPS 网络请求

本文开始之前,希望大家对基础的网络知识有所了解

回顾进行 HTTP 请求的基本流程。拿到 https://www.baidu.com 之后,需要先经过 DNS 解析,获取到对应的 IP 地址。接着通过 TCP 协议和服务器建立连接,发送 HTTP 请求内容,等待服务器响应内容

尽管通过 Socket 也能通过 DnsEndPoint 进行网络请求,但是为了尽可能展示更多的细节,本文将调用 Dns.GetHostAddressesAsync 方法对域名进行解析,获取到对应的 IP 地址。接着通过 Socket 类和服务器建立连接,发送 HTTP 请求内容,等待服务器响应内容

调用 Dns.GetHostAddressesAsync 对给定的域名进行解析的代码如下。本文内容里面只给出关键代码片段,如需要全部的项目文件,可到本文末尾找到本文所有代码的下载方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 一个域名可以有多个 IP 地址,利用此特性,可以实现 IP 级域名备份,也能利用此特性实现寻找距离自己最近的 IP 地址
IPAddress[] ipAddresses = await Dns.GetHostAddressesAsync("www.baidu.com");

在我当前的网络环境下,能够获取到两个百度的地址。一般而言,取其首个满足条件的 IP 地址即可。有些时候需要禁用 Ip v6 地址的情况,则请大家自行判断和处理,本文不再赘述

额外说明一点,从 Dns.GetHostAddressesAsync 里面获取到的 IP 地址是不保证顺序的。从 DNS 提供商的角度上讲,本身也是不保证顺序的,大家可以试试去找一些 CDN 厂商的域名试试看。为什么说去找 CDN 厂商的域名呢?因为 CDN 厂商的域名一般都是有多个 IP 地址的。大家可以试试在 CMD 命令行里面不断输入 ipconfig /flushdns 命令来清除 DNS 缓存,然后再调用 Dns.GetHostAddressesAsync 获取 IP 地址,测试每次获取到的地址是否顺序相同

为了演示方便,我将遍历百度的所有 IP 地址,依次进行 HTTP 请求。进行 HTTP 请求时,获取到了 IP 地址,就可以开始使用 Socket 类进行连接了。在连接之前,先从共享池中租用一个字节数组,为 1MB 大小。网络请求过程中也是可以做到低分配的,合理使用内存池可以减少 GC 压力。代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);

try
{
    foreach (var ipAddress in ipAddresses)
    {
        ... // 忽略其他代码
    }
}
finally
{
    // 别忘了归还共享池中的字节数组
    ArrayPool<byte>.Shared.Return(buffer);
}

开始创建 Socket 对象,创建完成之后,调用 ConnectAsync 方法进行连接。默认情况下的 HTTP 应该连接的是 80 端口

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    foreach (var ipAddress in ipAddresses)
    {
        Console.WriteLine($"开始连接 IP:{ipAddress}");

        using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        await socket.ConnectAsync(ipAddress, 80);

        ... // 忽略其他代码
    }

预期能够连接成功,此时就完成了 HTTP 连接中的 TCP 建立过程了。下一步是发送 HTTP 请求内容。这里的请求内容是一个简单的 HTTP GET 请求。在 dotnet 里面封装了 NetworkStream 类,可以简化一些网络通讯代码。可以像一个 Stream 一样进行网络通讯,整个代码写起来还是比较舒服的。咱就在此基础上构建一个 NetworkStream 对象,然后通过 Stream 的方式写入请求内容,注:后面要带两个换行哦。代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        using var networkStream = new NetworkStream(socket);

        Console.WriteLine($"连接完成,开始发送请求");

        // 这里的请求内容是一个简单的 HTTP GET 请求,注:后面要带两个换行哦
        ReadOnlySpan<byte> content = """
                                     GET https://www.baidu.com HTTP/1.1
                                     Host: www.baidu.com


                                     """u8;
        content.CopyTo(buffer); // 将请求内容复制到租用的字节数组中。异步请求不能传入 Span 类型,只能传入 Memory 类型。将 Span 转换为 Memory 的方式是先写入到 buffer 中,然后再将其当成 ReadOnlyMemory 或 Memory 类型
        ReadOnlyMemory<byte> writeBuffer = buffer.AsMemory(0, content.Length);
        await networkStream.WriteAsync(writeBuffer);

写入之后,依然可以通过 NetworkStream 读取百度服务器响应的内容,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        // 读取响应内容
        var length = await networkStream.ReadAsync(buffer); // 注: 很多时候,这里都是没有完全读取到完整的响应内容的,可能需要多次读取才能获取完整的响应内容。读取多长的数据需要从返回的 Header 里面获取 Content-Length 字段的值
        var text = Encoding.UTF8.GetString(buffer, 0, length);

        Console.WriteLine($"收到百度的响应内容。内容长度 {length},内容摘要:{text.Substring(0, Math.Min(50, text.Length))}...");

        Console.WriteLine();

以上的代码没有去读取 Content-Length 字段的值,也没有处理一次性读取没有读取完的问题。如果大家想要制作一个合理的 HTTP 请求客户端,则需要自行处理这些问题。本文只是一个简单的示例,演示如何使用底层的 Socket 进行 HTTP 请求,没有处理这些细节

以上代码就完成了 HTTP 的请求了。那么如何进行 HTTPS 的请求呢?其实和 HTTP 的请求是类似的,只不过需要在 NetworkStream 的基础上,使用 SslStream 进行封装。依然是和 HTTP 一样的前置过程,获取 IP 地址,创建 Socket 对象,连接到服务器。相同部分的代码如下,当然别忘了改一下端口号哦

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        await socket.ConnectAsync(ipAddress, 443); // 443 是 HTTPS 的默认端口

        using var networkStream = new NetworkStream(socket);

完成连接之后,接下来就是其不同的部分。不同的是,连接到服务器之后,需要进行 SSL/TLS 握手,使用 SslStream 包装 NetworkStream,然后进行认证。接下来后续的所有读写操作都将通过 SslStream 进行。代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        // 进行 SSL/TLS 握手,使用 SslStream 包装 NetworkStream 然后进行认证
        // 接下来后续的所有读写操作都将通过 SslStream 进行
        using var sslStream = new SslStream(networkStream);
        await sslStream.AuthenticateAsClientAsync("www.baidu.com");

在 dotnet 里面,无论是 NetworkStream 还是 SslStream 都是继承自 Stream 类的,所以可以使用相同的方式进行读写操作。接下来就是发送请求内容了,和 HTTP 的请求内容一样,只不过需要使用 SslStream 来发送请求内容。代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        // 这里的请求内容是一个简单的 HTTP GET 请求,注:后面要带两个换行哦
        ReadOnlySpan<byte> content = """
                                     GET https://www.baidu.com HTTP/1.1
                                     Host: www.baidu.com


                                     """u8;
        content.CopyTo(buffer); // 将请求内容复制到租用的字节数组中。异步请求不能传入 Span 类型,只能传入 Memory 类型。将 Span 转换为 Memory 的方式是先写入到 buffer 中,然后再将其当成 ReadOnlyMemory 或 Memory 类型
        ReadOnlyMemory<byte> writeBuffer = buffer.AsMemory(0, content.Length);
        await sslStream.WriteAsync(writeBuffer); // 这里要用 SslStream 来发送请求内容

可以对比一下 HTTP 请求的代码,只会发现 WriteAsync 的对象从 NetworkStream 类型换成 SslStream 类型而已

读取百度响应的代码也是类似,代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        // 读取响应内容
        var length = await sslStream.ReadAsync(buffer); // 这里要用 SslStream 来读取响应内容
        var text = Encoding.UTF8.GetString(buffer, 0, length);

        Console.WriteLine($"收到百度的响应内容。内容长度 {length},内容摘要:{text.Substring(0, Math.Min(50, text.Length))}...");

至少在 dotnet 的封装基础之下,即使使用十分底层的 Socket 方式进行 HTTP 或 HTTPS 通讯,其代码也是十分简洁的。以上就是本文的全部演示内容了,网络通讯是一个知识量比较庞大的领域,本文只是一个简单的示例,演示如何使用底层的 Socket 进行 HTTP 和 HTTPS 网络请求。希望能够对大家有所帮助

全部的 Program.cs 文件的代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// See https://aka.ms/new-console-template for more information

using System.Buffers;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;

// 一个域名可以有多个 IP 地址,利用此特性,可以实现 IP 级域名备份,也能利用此特性实现寻找距离自己最近的 IP 地址
IPAddress[] ipAddresses = await Dns.GetHostAddressesAsync("www.baidu.com");

// 从共享池中租用一个字节数组,大小为 1MB
// 网络请求过程中也是可以做到低分配的,合理使用内存池可以减少 GC 压力
var buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);

try
{
    foreach (var ipAddress in ipAddresses)
    {
        Console.WriteLine($"开始连接 IP:{ipAddress}");

        using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        await socket.ConnectAsync(ipAddress, 80);

        using var networkStream = new NetworkStream(socket);

        Console.WriteLine($"连接完成,开始发送请求");

        // 这里的请求内容是一个简单的 HTTP GET 请求,注:后面要带两个换行哦
        ReadOnlySpan<byte> content = """
                                     GET https://www.baidu.com HTTP/1.1
                                     Host: www.baidu.com


                                     """u8;
        content.CopyTo(buffer); // 将请求内容复制到租用的字节数组中。异步请求不能传入 Span 类型,只能传入 Memory 类型。将 Span 转换为 Memory 的方式是先写入到 buffer 中,然后再将其当成 ReadOnlyMemory 或 Memory 类型
        ReadOnlyMemory<byte> writeBuffer = buffer.AsMemory(0, content.Length);
        await networkStream.WriteAsync(writeBuffer);

        // 读取响应内容
        var length = await networkStream.ReadAsync(buffer); // 注: 很多时候,这里都是没有完全读取到完整的响应内容的,可能需要多次读取才能获取完整的响应内容。读取多长的数据需要从返回的 Header 里面获取 Content-Length 字段的值
        var text = Encoding.UTF8.GetString(buffer, 0, length);

        Console.WriteLine($"收到百度的响应内容。内容长度 {length},内容摘要:{text.Substring(0, Math.Min(50, text.Length))}...");

        Console.WriteLine();
    }
}
finally
{
    // 别忘了归还共享池中的字节数组
    ArrayPool<byte>.Shared.Return(buffer);
}

// 以下是进行 https 请求的代码
// 上面的缓存已经被归还了,为了继续使用,就开始重新申请好了
buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);

try
{
    foreach (var ipAddress in ipAddresses)
    {
        Console.WriteLine($"开始连接 IP:{ipAddress}");

        using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
        await socket.ConnectAsync(ipAddress, 443); // 443 是 HTTPS 的默认端口

        using var networkStream = new NetworkStream(socket);

        Console.WriteLine($"连接完成,开始进行 https 通讯");

        // 进行 SSL/TLS 握手,使用 SslStream 包装 NetworkStream 然后进行认证
        // 接下来后续的所有读写操作都将通过 SslStream 进行
        using var sslStream = new SslStream(networkStream);
        await sslStream.AuthenticateAsClientAsync("www.baidu.com");

        Console.WriteLine($"开始发送请求");

        // 这里的请求内容是一个简单的 HTTP GET 请求,注:后面要带两个换行哦
        ReadOnlySpan<byte> content = """
                                     GET https://www.baidu.com HTTP/1.1
                                     Host: www.baidu.com


                                     """u8;
        content.CopyTo(buffer); // 将请求内容复制到租用的字节数组中。异步请求不能传入 Span 类型,只能传入 Memory 类型。将 Span 转换为 Memory 的方式是先写入到 buffer 中,然后再将其当成 ReadOnlyMemory 或 Memory 类型
        ReadOnlyMemory<byte> writeBuffer = buffer.AsMemory(0, content.Length);
        await sslStream.WriteAsync(writeBuffer); // 这里要用 SslStream 来发送请求内容

        // 读取响应内容
        var length = await sslStream.ReadAsync(buffer); // 这里要用 SslStream 来读取响应内容
        var text = Encoding.UTF8.GetString(buffer, 0, length);

        Console.WriteLine($"收到百度的响应内容。内容长度 {length},内容摘要:{text.Substring(0, Math.Min(50, text.Length))}...");

        Console.WriteLine();
    }
}
finally
{
    // 别忘了归还共享池中的字节数组
    ArrayPool<byte>.Shared.Return(buffer);
}

本文代码放在 githubgitee 上,可以使用如下命令行拉取代码。我整个代码仓库比较庞大,使用以下命令行可以进行部分拉取,拉取速度比较快

先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin cb55daad2c8e5725e7b780261939bf5728e5323a

以上使用的是国内的 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码,可以发邮件向我要代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin cb55daad2c8e5725e7b780261939bf5728e5323a

获取代码之后,进入 Workbench/HalaiheakerbarheeLayneberqarke 文件夹,即可获取到源代码

更多技术博客,请参阅 博客导航

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C# 三种方式实现Socket数据接收(经典)
public abstract int Read(byte[] buffer, int offset, int count)
zls365
2021/01/14
8K0
C# 三种方式实现Socket数据接收(经典)
超详细的TCP、Sokcket和SuperSocket入门指导
然后编写服务代码,SuperSocket的服务代码主要是配置AppServer对象,因为AppServer已经很好的封装端口监听了。
Kiba518
2020/10/23
1.1K0
C#的Sockets
在现代软件开发中,网络通信是不可或缺的一部分。C#提供了一套强大的Sockets API,允许开发者进行底层的网络通信编程。本文将深入探讨Sockets的工作原理、使用场景、最佳实践以及一些高级技巧。
Michel_Rolle
2024/10/09
3K0
你也可以写个聊天程序 C# Socket学习
我们做软件工作的虽然每天都离不开网络,可网络协议细节却不是每个人都会接触和深入了解。我今天就来和大家一起学习下Socket,并写一个简单的聊天程序。
郑子铭
2023/08/30
4000
你也可以写个聊天程序 C# Socket学习
C# Socket编程笔记
看到这个题目,是不是很眼熟?在博客园里搜下,保证会发现关于这个东东的文章实在是太多了~~~真得是没有写得必要,而且我也有点懒得去琢磨字句。(看到这,肯定得来个转折的了,不然就看不到下文了,不是吗)但是,为了自己下一篇要写的文章做参考,还是有必要先补充一下socket基础知识。
zls365
2020/08/19
1.2K0
C# Socket编程笔记
C# socket通信实现两个控制台之间聊天
1、运行效果 图1 启动服务端 图2 启动客户端 图3 客户发消息 图4 服务端发消息 图5 客户主动关闭,服务段打印异常详情 2、服务器端源码 服务端和客户端都要添加一下namespace: using System.Net; using System.Net.Sockets; using System.Threading; 源码:  class Program     {         private static string serverIP = "192.168.3.42";
用户7705674
2021/11/03
9190
面向对象(二十七)-Socket TCPListener
简介: 对Socket的封装,使其使用较为简单。 Server Code // 建立一个TCP监听对象,来监听客户端端的连接 TcpListener server = new TcpListener(IPAddress.Parse("172.26.128.1"), 8090); // 开始监听客户端的连接 server.Start(); // 接收连接上的客户端
孙寅
2020/06/02
4690
剖析.NET开源库-AlterNats是如何做到高性能发布订阅的?
在过去的一些文章里面,我们聊了一些.NET平台上高性能编程的技巧,今天带大家了解一下AlterNats这个库是如何做到远超同类SDK性能的。
InCerry
2022/11/14
6270
C# 一分钟浅谈:Socket 编程基础
在现代网络应用开发中,Socket 编程是一种非常重要的技术,它允许不同设备之间的应用程序通过网络进行通信。本文将从基础概念入手,逐步深入到 Socket 编程中的常见问题和易错点,并通过具体的代码示例来帮助读者更好地理解和掌握这一技术。
Jimaks
2024/10/18
2080
C# SOCKET发送和接收例子
Socket 客户端 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; namespace A0140_SocketClient.Sample { /// <summary> /// 这个类为一个 Socket 客户端的例子. /// 这个类简单的 连接到 Socket 服务器,并发送一段消息。 /
用户8671053
2021/11/03
2.6K0
C# 温故而知新:Stream篇(七)
NetworkStream 目录: NetworkStream的作用 简单介绍下TCP/IP 协议和相关层次 简单说明下 TCP和UDP的区别 简单介绍下套接字(Socket)的概念 简单介绍下TcpClient,TcpListener,IPEndPoint类的作用 使用NetworkStream的注意事项和局限性 NetworkStream的构造 NetworkStream的属性 NetworkStream的方法 NetwrokStream的简单示例 创建一个客户端向服务端传输图片的小示例 本章总结 1.
逸鹏
2018/04/10
1.5K0
C# 温故而知新:Stream篇(七)
C# TCP通讯示例
示例目的:使用控制台项目模板分别新建一个服务器和一个客户端,实现两两通讯 1. 新建服务器项目 using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; namespace server { class Program { static TcpClient tcpClient; static NetworkStrea
zls365
2021/01/13
2.2K0
C# TCP通讯示例
socket通信(C#)
4:接受到客户端的连接,用socket对像的Accept()方法创建新的socket对像用于和请求的客户端进行通信;
红目香薰
2022/11/29
1K0
socket通信(C#)
c#简单的socket通讯demo
代码中注释比较多 如果仍旧看不懂请看JimmyZhang的这篇文章 http://www.cnblogs.com/JimmyZhang/archive/2008/09/07/1286300.html 讲的很细很基础
liulun
2022/05/09
3590
C# 一分钟浅谈:WebSocket 协议应用
在过去的这一年里,我有幸参与了一些非常有意义的项目,其中一个让我特别引以为傲的是一个基于 WebSocket 的实时通信系统。这个系统不仅提高了我们的工作效率,还为用户带来了更好的体验。在这个过程中,我也遇到了不少挑战,但最终通过不断学习和实践,成功解决了这些问题。本文将以 WebSocket 协议在 C# 中的应用为主题,分享我的经验和心得,希望能对广大开发者有所帮助。
Jimaks
2024/10/17
3160
C#网络编程(同步传输字符串) - Part.2
在与服务端的连接建立以后,我们就可以通过此连接来发送和接收数据。端口与端口之间以流(Stream)的形式传输数据,因为几乎任何对象都可以保存到流中,所以实际上可以在客户端与服务端之间传输任何类型的数据。对客户端来说,往流中写入数据,即为向服务器传送数据;从流中读取数据,即为从服务端接收数据。对服务端来说,往流中写入数据,即为向客户端发送数据;从流中读取数据,即为从客户端接收数据。
张子阳
2018/09/30
9420
C#中Socket的简单使用
一.Socket的概念 Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口.
全栈程序员站长
2022/09/07
1.1K0
C#中Socket的简单使用
C#网络编程(异步传输字符串) - Part.3
这篇文章我们将前进一大步,使用异步的方式来对服务端编程,以使它成为一个真正意义上的服务器:可以为多个客户端的多次请求服务。但是开始之前,我们需要解决上一节中遗留的一个问题。
张子阳
2018/09/30
7570
C#网络编程(异步传输字符串) - Part.3
C#网络编程(接收文件) - Part.5
这篇文章将完成 Part.4 中剩余的部分,它们本来是一篇完整的文章,但是因为上一篇比较长,合并起来页数太多,浏览起来可能会比较不方便,我就将它拆为两篇了,本文便是它的后半部分。我们继续进行上一篇没有完成的步骤:客户端接收来自服务端的文件。
张子阳
2018/09/30
8720
您可知道如何通过HTTP2实现TCP的内网穿透???
可能有人很疑惑应用层 转发传输层?,为什么会有这样的需求啊???哈哈技术无所不用其极,由于一些场景下,对于一个服务器存在某一个内部网站中,但是对于这个服务器它没有访问外网的权限,虽然也可以申请端口访问外部指定的ip+端口,但是对于访问服务内部的TCP的时候我们就会发现忘记申请了!这个时候我们又要提交申请,又要等审批,然后开通端口,对于这个步骤不是一般的麻烦,所以我在想是否可以直接利用现有的Http网关的端口进行转发内部的TCP服务?这个时候我询问了我们的老九大佬,由于我之前也做过通过H2实现HTTP内网穿透,可以利用H2将内部网络中的服务映射出来,但是由于底层是基于yarp的一些方法实现,所以并没有考虑过TCP,然后于老九大佬交流深究,决定尝试验证可行性,然后我们的Taibai项目就诞生了,为什么叫Taibai?您仔细看看这个拼音,翻译过来就是太白,确实全称应该叫太白金星,寓意上天遁地无所不能!下面我们介绍一下具体实现逻辑,确实您仔细看会发现实现是真的超级简单的!
用户10786849
2024/04/30
1050
您可知道如何通过HTTP2实现TCP的内网穿透???
相关推荐
C# 三种方式实现Socket数据接收(经典)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验