socket网络实现

对于无连接协议,例如UDP,多个连接是允许的,但是每一个新的请求中的外部地址会取代原来的外部地址。对于其它的协议,如OSI的TP4,只要一个连接请求到达,tsleep就返回。开发中文网致力于整理收录dpdk,spdk,ovs,vpp,dpvs,virtiohost,sdn/ovn,qemu等方向 的github开源项目,资料文档,书籍,讲解视频,各大企业招聘信息。soclose函数取消socket上所有未完成的连接(即,还没有完全被进程接受的连接),等待数据被传输到外部系统,释放不需要的数据结构。
摘要由CSDN通过智能技术生成

socket是对协议栈的封装,支持TCP,UDP. protocol+src_addr+dst_addr+src_port+dst_port是socket五元组。Linux支持地址复用和端口复用,需要打开SO_REUSEADDR和SO_REUSEPORT选项。


网络实现架构

4.4BSD通过同时对多种通信协议的支持来提供通用的底层基础服务。4.4BSD支持四种不同的通信协议簇:

  • TCP/IP(互联网协议簇)
  • XNS(Xerox网络系统)
  • OSI协议
  • Unix域协议
    从通信协议是用来在不同的系统之间交换信息的意义上来说,它还不算是一套真正的协议,但它提供了一种进程间通信(IPC)的形式。

4.4BSD内核中的联网代码组织成三层,如下图所示

  • Socket层是一个到下面协议相关层的协议无关层所有系统调用从协议无关的Socket开始
    例如:在Socket层中的bind()系统调用的协议无关代码包含几十行代码,它们验证第一个参数是一个有效的socket描述符,并且第二个参数是一个进程中的有效指针。然后调用下层的协议相关代码,协议相关代码可能包含几百行代码。
  • 协议层包括我们提到的四种协议簇(TCP/IP,XNS,OSI和Unix域)的实现。
    每个协议簇可能包含自己的内部结构。
  • 接口层
    接口层包括同网络设备通信的设备驱动程序。

数据传递

  • Socket层中的每一个Socket都具有一个输入队列和一个输出队列
  • 协议层中的每一个协议都具有一个输入队列和输出队列
  • 接口层中的每个接口(以太网、回环、SLIP、PPP等)都有一个输入队列和输出队列

输入处理

输入处理与输出处理不同,因为输入处理是异步的。就是说,它是通过一个接收完成中断驱动以太网设备程序来接收一个输入分组,而不是通过进程的系统调用。内核处理这个设备中断,并调度设备驱动程序进入运行状态

接口层-以太网输入

以太网设备驱动程序处理这个中断。
假定它表示一个正常的接收已完成,数据从以太网设备读取到一个mbuf链表中。设备驱动程序把mbuf传给一个通用以太网输入例程,它通过以太网帧中的类型字段来确定哪个协议层接收此分组。

协议层——IP输入

IP输入是异步的,并且通过一个软中断来执行。
当接口层在系统的一个接口上收到一个IP数据报时,它就设置这个软中断。当IP输入例程执行它时,循环处理在它的输入队列中的每一个IP数据报,并在整个队列被处理完后返回。

输入层-UDP输入

IP输入历程可能会调用UDP输入例程去处理UDP数据报。
UDP输入例程验证UDP首部中的各字段(长度与可选的校验和),然后确定是否一个进程应噶接收次数据报。
UDP输入例程从一个全局变量udb开始,查看所有UDP协议控制块链表PCB,寻找一个本地端口号与接收的UDP数据报的目标端口号相匹配的协议块。(这个PCB是由我们调用socket()创建的,它的成员inp_socket指向相

socket接收,并允许接收的数据在此socket排队).

因为这个UDP数据报要传送给我们的进程,发送方的IP地址和UDP端口号放置到一个mbuf中,这个mbuf和数据被追加到此socket的接收队列中。
最后,接收进程被唤醒。如果进程处于睡眠状态等待数据的到达,进程将标志为可运行状态等待内核的调度。也可以通过select系统调用或SIGIO信号来通知进程数据的到达。

进程输入
进程可以调用socket 的输入函数将mbuf从socket的接收队列复制到我们程序的缓存中。

存储器缓存

在BSD联网代码设计中的一个基本概念就是存储器缓存,称作为一个mbuf(memory buffer),在整个联网代码中用于存储各种信息。

网络协议对内核的存储器管理能力提出了很多要求。这些要求包括能方便地操作可变长缓存,能在缓存头部和尾部添加数据(如底层封装来自高层的数据),能从缓存中移去数据(如,当数据分组向上经过协议栈时要去掉首部),并尽量减少为这些操作所做的数据复制。内核中的存储器管理调度直接关系到联网协议的性能。

mbuf的主要用途是保存在进程和网络接口间互相传递的用户数据。但mbuf也用于保存其它各种数据:源与目地址、Socket选项等等。

  • 指针m_nextmbuf连接在一起,把一个分组形成一条mbuf链表。
  • 指针m_nextpkt把多个分组链接成一个mbuf链接成一个mbuf链表队列。在队列的每个分组可以是一个单独的mbuf,也可以是一个mbuf链表。每个分组的第一个mbuf包含一个分组首部。如果多个mbuf定义一个分组,只有第一个mbuf的成员m_nextpkt被使用——链表中其它mbuf的成员m_nextpkt全是空指针。

m_get函数

struct mbuf * m_get(int nowait,int type) { struct mbuf * m; MGET(m,nowait,type); return m; }

  • nowait的值为M_WAITM_DONTWAIT,它取决于在存储器不可用时是否要求等待。
    例如,当Socket层请求分配一个mbuf来存储sendto系统调用的目的地址时,它指定M_WAIT,因为在此阻塞是没有问题的。但是当以太网设备驱动程序请求分配一个mbuf来存储一个接收的帧时,它指定M_DONTWAIT,因为它是作为一个设备中断处理来执行的,不能进入睡眠状态来等待一个mbuf。在这种情况下,若存储器不可用,设备驱动程序丢弃这个帧比较好。
  • type 指定mbuf的类型

系统调用

所有的操作系统都提供服务访问点,程序可以通过它们请求内核中的服务。各种UNIX都提供精心定义的有限个内核入口点,即系统调用。我们不能改变系统调用,除非我们有内核的源代码。

在各种Unix系统中,每个系统调用在标准C函数库中都有一个相同名字的函数。一个应用程序用标准C的调用序列来调用此函数。这个函数再调用相应的内核服务,所使用的技术依赖于所在的系统。例如,函数可能把一个或多个C参数放到通用寄存器中,并执行几条机器指令产生一个软件中断进入内核。对我们来说,我们可以把系统调用看成C函数。

从进程到内核的受保护的环境的转换是与机器和实现相关的。

BSD内核中,每一个系统调用均被编号,当进程执行一个系统调用时,硬件被配置成仅传送控制给一个内核函数,即将CPU的使用权转给一个内核函数。将标志系统调用的整数作为参数传送给此内核函数。在i386实现中,此内核函数为syscall(),syscall()利用系统调用的编号在系统调用表中找到请求的系统调用的sysent结构.表中的每一单元均为一个sysent结构。

struct sysent{
    int sy_narg;  //参数个数
    int (*sy_call)();//系统调用的实现函数
};

表中有几个项是从sysent数据中来的,概述组是在kern/init_sysent.c中定义的:

struct sysent sysent[] = {
  {3,recvmsg},    /* 27 = recvmsg */
  {3,sendmsg},    /* 28 = sendmsg */
  {6,recvfrom},   /* 29 = recvfrom */
  {3,accept},     /* 30 = accept */
  {3,getpeername},/* 31 = getpeername */
  {3,getsockname},/* 32 = getsockname */
};

例如,recvmsg系统调用在系统调用表中的第27个项,它有2个参数,利用内核中的recvmsg函数实现。

syscall()负责将参数从调用进程复制到内核中,并且分配一个数组来保存系统调用的结果。然后,当系统调用执行完成后,syscall将结果返回给进程。syscall将控制交给鱼系统调用相对应的内核函数。
在i386实现中,调用有点像:

struct sysent * callp;
error = (*callp->syscall)(p,args,rval);

if(error){
    errno = error;
    return -1;
}else{
    return (rval);
}

这里指针callp指向相关的sysent结构;指针p指向调用系统调用的进程的进程表项;args作为参数传给系统调用,它是一个32bit长的字数组;而rval则是一个用来保存系统调用的返回结果的数组,数组有两个元素,每一个元素是一个32bit长的字。当我们用"系统调用"这个词时,我们指的是被syscall调用的内核中的函数,而是不是应用调用的进程中的函数

syscall期望系统调用函数(即sy_call指向的函数)在没有差错时返回0,否则返回非0的差错代码。如果没有差错出现,内核将rval中的值作为系统调用(应用调用的)返回值传送给进程。如果有差错,syscall忽略rval中的值,并以与机器相关的方式返回差错代码给进程,使得进程能从外部变量errno中得到差错代码。应用调用的函数则返回-1或一个空指针表示应用应该查看errno获得差错信息

下表介绍了与网络有关的系统调用


举例

socket系统调用的函数原型是:

int socket(int domain,int type,int protocol);

实现socket系统调用的内核函数原型是:

struct socket_args{
  int domain;
  int type;
  int protocl;
};

socket(struct proc * p,struct socket_args * uap,int * retvall);

当一个应用调用socket时,进程用系统调用机制将三个独立的整数传给内核。syscall将参数复制到32bit值的数组中,并将数组指针作为第二个参数传给socket的内核版。内核版的socket将第二个参数作为指向socket_args结构的指针。下图描述了上述过程:


同socket类似,(在i386实现中)每一个实现系统调用的内核函数将args说明称一个与系统调用有关的结构指针,而不是一个指向32bit的子的数组的指针

syscall在执行内核系统调用函数之前将返回值设置为0.如果没有差错出现,系统调用函数直接返回而不需要清楚*tetvall,syscall返回0给进程。

进程、描述符和插口

Unix系统中的Socket I/O遵循其"一切皆文件"的思想,因而可以使用统一的方式对Socket 进行I/O操作。
调用sock

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于csharp实现socket网络编程,可以使用System.Net.Sockets命名空间提供的类和方法来实现。下面是一个简单的示例代码: ``` using System; using System.Net; using System.Net.Sockets; using System.Text; class Program { static void Main(string[] args) { // 创建一个新的Socket Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // 设置服务端IP地址和端口号 IPAddress ipAddress = IPAddress.Parse("192.168.0.100"); IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, 8888); try { // 绑定IP地址和端口号 serverSocket.Bind(ipEndPoint); // 开始监听客户端连接 serverSocket.Listen(10); Console.WriteLine("等待客户端连接..."); // 接收客户端连接,返回一个新的Socket Socket clientSocket = serverSocket.Accept(); Console.WriteLine("客户端已连接"); // 向客户端发送数据 byte[] bytes = Encoding.UTF8.GetBytes("欢迎使用Socket网络编程"); clientSocket.Send(bytes); // 接收来自客户端的数据 while (true) { bytes = new byte[1024]; int length = clientSocket.Receive(bytes); if (length > 0) { string message = Encoding.UTF8.GetString(bytes, 0, length); Console.WriteLine("接收到来自客户端的消息:" + message); // 如果客户端发送“exit”退出指令,则关闭连接 if (message.Equals("exit")) { clientSocket.Shutdown(SocketShutdown.Both); clientSocket.Close(); break; } } } } catch (Exception e) { Console.WriteLine(e.Message); } } } ``` 以上就是一个简单的csharp实现socket网络编程的示例代码,可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值