首页 > 编程语言 >C#笔记7 网络通信抽象,Socket类的介绍和简单使用

C#笔记7 网络通信抽象,Socket类的介绍和简单使用

时间:2024-09-09 23:49:49浏览次数:12  
标签:网络通信 Socket C# ip IPEndPoint 地址 连接 socket

一、背景介绍

在前面不算详细的基础知识和基本编程背景下,我们开始了今天重头菜,也就是开始与远方的计算机建立起一个连接,正式打通计算机与计算机之间的桥梁。

C#笔记6 网络编程基础,解释端口套接字,代码实例分析DNS,IPAddress等类-CSDN博客

前文我们讲了计算机中间运行着怎么样的连接,介绍了C#中使用什么类和方法获取IP和主机名,并且获取一系列特殊的ip地址。

今天之后要学习的是网络通信的内容,后续使用的几个类:socket,TcpClient,TcpListener,UDPClient。

二、基础概念了解

首先这是一种涉及到网络内容的编程,不同设备上底层读取网络或许有所不同,实际的代码在设备上运行对应的底层实现也不会完全相同,不去了解底层,只有浅薄的知识,那就只能在这个方面浅尝辄止。

服务端与客户端

这就是我们要讲的概念了,如果你是初学者你也许到目前为止所有的程序都在同一个文件中编写,乃至于使用多文件但是在同一个程序中运行,但是在我们这一类型的编程中 我们其实是在两台计算机上运行的程序中间传递数据。也就是我们编写运行的程序分两个部分:服务端程序和客户端程序。

多线程

这也是网络编程中常见的概念,尤其是对于服务端而言,可能需要主动使用多线程来处理客户端的连接。我们在本次编程中需要使用新的线程处理连接或者监听端口,而不能让此类行为阻塞我们整个服务端程序。

socket和tcp,ip以及HTTP等的关系

Socket 是一种位于 TCP 层之上的抽象,它通过提供简单的 API,让应用层开发者能够使用 TCP 协议来进行可靠的网络通信。所以你可以理解为,Socket 在上层,对应的是 TCP(或 UDP),它负责将应用层与传输层连接起来。

很多人以为tcplistener中包含对socket类的封装,就认为是不是TCP的层次要高于socket,事实上并不是,Tcp只是socket的一个传输方法,socket的意义是建立一个抽象的管道提供给应用层用于传递数据,底层是udp还是tcp其实不是一定的,并且不止这两种协议,事实上还有其他协议通过这一抽象接口提供给上层应用。后面我们构造socket的时候就会看到了。

所以TCPlistener虽然包含对socket类的封装,但是它本质上不代表tcp协议,只是代表这一类使用socket接口对应的底层协议是tcp,在我看来,它的地位和socket是不同的,是一种被限制的socket的使用方式的封装。

socket为什么说是一种位于传输层上的抽象,因为他的本质就是提供一个接口给应用传递数据,甚至于可以使用tcp,udp,unix域套接字,甚至是直接接入ip这一级别,构建自己的传输层协议。也就是说,你甚至可以自定义一种包的格式来作为传输格式,设立一系列的报文规范。

HTTP是应用层的协议,我们在建立起socket连接之后发送的数据包的内容中可能就包含某种应用层协议的请求。

地址族

前文中已经提到过这一概念, 也就是在IPAddress类的字段中存在这一个概念,其为一个枚举变量,对应几十种地址的分类,上次我们检测到我们的ip地址族为internetwork,有的老师会解释成内网,但是实际上,这意思就是指ipv4地址。

三、socket通信

Socket 类 (System.Net.Sockets) | Microsoft Learn

首先介绍Socket类,关于这一类的讨论很多,有人讨论到底能建立多少个socket连接啦,到底怎么监听新建连接之类的话题。

我们首先来学习怎么建立一个TCP协议的socket连接。

准备socket连接

构造函数:

我们在代码中首先要使用指定的地址族、套接字类型和协议初始化 Socket 类的新实例。

Socket(AddressFamily, SocketType, ProtocolType)
Socket(SocketType, ProtocolType)

可以看到第二个构造函数给出了两个参数的构造方法,不给出地址族的socket通信默认是构造IPv4和IPv6两个协议族的双栈套接字。

​使用指定的地址族、套接字类型和协议初始化 Socket 类的新实例。 如果操作系统支持 IPv6,则此构造函数将创建双模式套接字;否则,它将创建 IPv4 套接字。

 IPEndPoint意为网络终端点,我们既然要建立连接,自然是有发送方和接收方,或者说服务端和客户端,即使将两边看做相等地位的pc也需要知道两个端点,之前我们说了,ip是一个电脑在网络上的地址,这里的IPEndPoint是EndPoint的子类,我们之前也说了,socket不一定就是和ip打交道,乃至于本地的进程通信和蓝牙通信都可能用到socket。这之后需要的就不是IPEndPoint了,而是会用到EndPoint抽象类的其他子类。

实质上,IPEndPoint就是ip地址和一个端口的组合。

我们介绍它的目的是引出socket建立连接的第二步,就是绑定端口和 ip到我们的socket的一端。如果你是服务端,这意味着你只要选好自己要用来接待的房子和用来进房子的门(对应ip地址和电脑的端口,ip地址就是你房子的地址,端口就是你房子的窗口,服务端需要选一个窗口在那里等待属于你这个程序的链接和数据。)

无论是作为服务器还是客户端,都需要知道两个端点的信息才能进行通信,那么其中一端我们知道是本机,另外一端呢?事实上可以是很多种设备,甚至是设备本地,如何做到,也许你忘记了我们网络地址中特殊的地址:环回地址。

环回地址

127.0.0.1事实上任何发送到该端口的数据包都会被立刻转发到本地,也就是如果你发送数据,目标选择这一地址,最后收到数据的会是本机。

        // 创建服务端的 IPEndPoint(监听在本地 127.0.0.1 的 8080 端口)
        IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8080);

        // 创建服务器 Socket
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(serverEndPoint); // 绑定到终端点
        serverSocket.Listen(10); // 开始监听连接请求

        Console.WriteLine("服务器正在监听...");

我们这里的操作,可以说就是把一端设为本地这么简单,但是,这和设置客户端是不同的,服务端设置了本地监听为环回地址,而这个地址只有本地能够访问,你应该意识到问题了。

也就是说,这里的服务端程序只会和自己本地跑的客户端程序能够建立连接。常用于测试程序。

如果在实际生产环境中一般使用全0地址,或者说监听所有ip地址,也有针对对方访问的ip来限制连接的,比如指定内外网访问的资源不同,使用内网ip访问和外网就不一样。毕竟一个设备可能有很多ip,使用什么ip监听连接,就决定了应用程序在什么地址的房子里等待连接,哪怕这些IP地址(房子的地址)都是你设备的地址(可以理解成一个电脑有多个ip地址是因为作为一个房子有多个地址可以填写,比如前后门两个街道都可以抵达,但是这个房子前后门不是一家公司或者说一个用处,比如我们后门只接待内网用户vip,外网就无法访问了,对方必须先访问我们的内网才能到达我们后门)。

代码:获取本机主机名和ip地址实例化IPEndPoint

var hostName = Dns.GetHostName();
IPHostEntry localhost = await Dns.GetHostEntryAsync(hostName);
// This is the IP address of the local machine
IPAddress localIpAddress = localhost.AddressList[0];

IPEndPoint ipEndPoint = new(localIpAddress, 5151);

 这里就用到我们前文的知识了。

开始监听

        // Socket.Listen函数的摘要:
        //     Places a System.Net.Sockets.Socket in a listening state.
        //
        // 参数:
        //   backlog:
        //     The maximum length of the pending connections queue.
        //

关于服务端的监听,这里使用Listen来创建了一个最多建立十个TCP连接的监听序列。

        // 创建服务端的 IPEndPoint(监听在本地 127.0.0.1 的 8080 端口)
        IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Loopback, 8080);

        // 创建服务器 Socket
        Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        serverSocket.Bind(serverEndPoint); // 绑定到终端点
        serverSocket.Listen(10); // 开始监听连接请求

到这里已经服务端完成了三个操作:

创建了一个socket(插座),插座一端插在预设的端点,开启一个长度为10的监听序列。

现在轮到我们的客户端了。

发起请求

客户端也需要新建一个socket。接着调用connect方法,去连接一个网络终点

Socket clsoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clsoc.Bind(new IPEndPoint(IPAddress.Loopback,5150));
clsoc.Connect(new IPEndPoint(IPAddress.Loopback, 5151));

while(true)
{
    byte[] buffer = new byte[1024];
    int bytesReceived = clsoc.Receive(buffer);
    Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, bytesReceived));
}

连接之后进入一个死循环,我们这里没有使用异步方法,只是简单的使用了一个连接请求,如果没有请求成功线程就会阻塞在这里。

服务端也是类似,请看下面代码:

using System.Net.Sockets;
using System.Net;
using System.Text;

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, 5151)); 
socket.Listen(10);


//暂停程序直到有人连接
while (true)
{
    Socket clientsocket = socket.Accept();
    Console.WriteLine("连接成功");
    byte[] msg = Encoding.UTF8.GetBytes("connected!");
    clientsocket.Send(msg);
    Console.WriteLine("可以继续发送,按回车发送");
    //string ch=Console.ReadKey().ToString();
    string order = Console.ReadLine();
    byte[] msg2 = Encoding.UTF8.GetBytes(order);
    clientsocket.Send(msg2);

}

我们也是在创建了队列之后开始循环,可以这么理解,每一个客户端的请求到达之后会在我们Listen设定的队列中等待,如果超过10个就会被我们拒绝或者说抛弃。

在等待队列中的请求会被我们的while循环中的accept方法所处理(接收),到这就是连接建立成功。

此后可以开始发送数据或者接收数据,值得注意的是,我们调用的发送方法是发送一个字节数组,也就是二进制数据,我们需要使用Encoding的方法转化我们的文本为二进制数据,同样接收时也要重新转化回来。

我们到这就知道怎么建立一个socket连接了,但是你会想,这样循环处理请求,如果有多个连接建立,意味着会有更复杂的情况出现,比如:

服务端怎么根据请求的信息对每个请求执行不同的代码?

怎么使用连接发送文件这一类的数据呢?

怎么知道客户端是不是断开连接了?

怎么群发消息给所有客户端?

事实上如果只是实现这些会比你想的简单,但是有一些令人头疼的问题等着你,如果你和我一样是个初学者或者多线程和同步异步的门外汉的话。

标签:网络通信,Socket,C#,ip,IPEndPoint,地址,连接,socket
From: https://blog.csdn.net/m0_54138660/article/details/141832645

相关文章

  • describe、portray和depict的区别
    portray和depict都表示对客观情况进行主观的、感性的描述。他们的区别很细微。portray暗示写这句话的人认为这段描写只是作者的主观认识,不一定代表客观事实。(Thenovel portraysastrongfemalecharacter.小说塑造了一个坚强的女性角色。这是在说,这个女性角色坚强是作者的观......
  • 【LLM训练系列】从零开始训练大模型之Phi2-mini-Chinese项目解读
    一、前言本文主要是在复现和实践Phi2-mini-Chinese后,简要分析下Phi2-mini-Chinese这个项目,做一个学习实战总结。原文发布于知乎:https://zhuanlan.zhihu.com/p/718307193,转载请注明出数。Phi2-mini-Chinese简介Phi2-Chinese-0.2B从0开始训练自己的Phi2中文小模型,支持接入langc......
  • Luogu P11036 GCD 与 LCM 问题 [ 蓝 ] [ 构造 ] [ 数论 ] [ adhoc ]
    LuoguP11036GCD与LCM问题:梦熊的题真是又神又逆天。思路观察到有个奇数的特殊性质,我们尝试从奇数构造入手。先来尝试带入极端数据,对于\(\gcd\),我们可以把\(b=1\)的情况先带进去看看。\[a+b+c+d=\gcd(a,b)+\operatorname{lcm}(c,d)\]\[a+1+c+d=1+\operatorname{lcm}(c,......
  • C#可空类型
    C#可空类型在C#中,可空类型(nullabletypes)是指那些可以被赋予null值的值类型。通常,值类型(如int,float,char等)不能被赋值为null,因为它们在堆栈上直接存储数据,并且null值通常用于引用类型来表示“没有对象”。然而,在某些情况下,您可能需要能够表示一个值类型的缺失值或不确定值,这......
  • 中文关键字检索分析-导出到csv或者excel-多文件或文件夹-使用python和asyncio和pandas
    1.02版本把原来的tab一个个拼接成文件输出,改成pandas的dataframe使用asyncio库来使用协程,但是测试下来速度好像是差不多的。可能速度太快了,没能很好的测出来差异。原来的最初的代码是java版本的,现在用python重写一遍java版本使用completableFuture来异步IO,主要是文件输......
  • C++ 之 perf+火焰图分析与调试
    简介在遇到一些内存异常的时候,经常这部分的代码是很难去进行分析的,最近了解到Perf这个神器,这里也展开介绍一下如何使用Perf以及如何去画火焰图。1.Perf基础1.1Perf简介perf是Linux下的一款性能分析工具,能够进行函数级与指令级的热点查找。利用perf剖析程序性能时,需要指定当前测......
  • day04(网络编程基础)tcp编程
    目录tcp编程流程服务器客户端函数接口socketbindlistenaccept​​​​​​​recv​​​​​​​connect​​​​​​​send初始版服务器客户端 加功能:1.客户端连接成功后进入循环发送状态,从终端获取用户输入并发送,当用户输入“quit”字符后退出循环并关闭客......
  • c++1067: 有问题的里程表
    问题:题目描述某辆汽车有一个里程表,该里程表可以显示一个整数,为该车走过的公里数。然而这个里程表有个毛病:它总是从3变到5,而跳过数字4,里程表所有位(个位、十位、百位等)上的数字都是如此。例如,如果里程表显示339,汽车走过1公里之后,该里程表显示350。输入输入一个整数num,表示......
  • qt5.15.2+opencv4.10+VS2019_64 均值滤波,高斯滤波算法详细分析
    目录 一.加载图像二.灰度图像三.均值滤波1.均值滤波均值滤波算法(MeanFiltering):    参数含义:    一句话总结:均值滤波特点:应用场景:缺点:2.高斯滤波高斯滤波算法(GaussianBlur):        高斯滤波计算过程:    参数含义:    ......
  • 大模型的两个重要能力 (IF + FC)
    MiniCPMhttps://github.com/OpenBMB/MiniCPM面壁智能推出的大模型,在如下方面支持能出众。推理长文本RAG都是常见的能力。其中指令遵从(IF=instructionfollow)和工具调用(FC=functioncall),威力强大,可以用作很多语音控制场景。 MiniCPM3.0MiniCPM3.0是一......