UIP源码之ARP过程分析

之前我们使用UIP实现了tcp和udp通讯今天来说说UIP的实现流程,当然,这篇文章里面只会涉及tcp和udp,暂时还没办法说DHCP,因为UIP的DHCP实现使用了协程的概念,下一章将协程之后再说DHCP.

void uip_init(void)
{
  for(c = 0; c < UIP_LISTENPORTS; ++c) {
    uip_listenports[c] = 0;    //首先,清零监听的全部tcp端口
  }
  for(c = 0; c < UIP_CONNS; ++c) {
    uip_conns[c].tcpstateflags = UIP_CLOSED;//将全部的tcp连接设置为关闭
  }
#if UIP_ACTIVE_OPEN
  lastport = 1024;//设置用于tcp链接的端口(tcp客户端连接服务器的时候,客户端这边应该有一个端口,这个端口不需要用户手动指定)
#endif /* UIP_ACTIVE_OPEN */

#if UIP_UDP
  for(c = 0; c < UIP_UDP_CONNS; ++c) {
    uip_udp_conns[c].lport = 0;//如果使能了UDP,将UDP监听端口清零
  }
#endif /* UIP_UDP */

  /* IPv4 initialization. */
#if UIP_FIXEDADDR == 0
  /*  uip_hostaddr[0] = uip_hostaddr[1] = 0;*/
#endif /* UIP_FIXEDADDR */
}

    系统启动之后就可以进入循环过程中,循环过程就是uip_polling,我们的接下来的处理应该按照网络的分层结构来,首先肯定是要实现arp的请求,包括四个主要函数

    uip_arp_ipin(); //这个函数是一个空宏,不重要哈

    uip_arp_arpin();//处理arp输入

    uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求

    uip_arp_timer();//定时更新arp缓存

 

    首先是uip_arp_out,当我们的设备接入互联网的时候,本地机器会构造ip包发出去,在构造ip包之前要先构造arp包,说明如下

首先,uip启动的时候先要进行初始化,初始化代码如下

void uip_arp_out(void)
{
  struct arp_entry *tabptr;

    // 在ARP表中找到目的IP地址,构成以太网头.如果目的IP地址不在局域网中,则使用默认路由的IP.
    // 如果ARP表中找不到,则将原来的IP包替换成一个ARP请求.
    // 首先检查目标是不是广播
  if(uip_ipaddr_cmp(IPBUF->destipaddr, broadcast_ipaddr)) {
    memcpy(IPBUF->ethhdr.dest.addr, broadcast_ethaddr.addr, 6);
  } else {
    //检查发送的目标地址是否在局域网内
    if(!uip_ipaddr_maskcmp(IPBUF->destipaddr, uip_hostaddr, uip_netmask)) {
      //如果不在局域网内,那么使用默认的路由器IP地址取代目标地址
      uip_ipaddr_copy(ipaddr, uip_draddr);
    } else {
     //在局域网内,使用目标的ip地址
      uip_ipaddr_copy(ipaddr, IPBUF->destipaddr);
    }

     //此时,遍历ARP缓存表,比较目标IP和ARP缓存表中的IP
    for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
      tabptr = &arp_table[i];
      if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr)) {
    break;
      }
    }

    //如果遍历到表头也没有找到目标ip在arp缓存表中的缓存
    if(i == UIP_ARPTAB_SIZE) {
      //将原IP请求替换成ARP请求并返回

      memset(BUF->ethhdr.dest.addr, 0xff, 6);//以太网目的地址
      memset(BUF->dhwaddr.addr, 0x00, 6);//目的以太网地址
      memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);
      memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);//源以太网地址

      uip_ipaddr_copy(BUF->dipaddr, ipaddr);//目的IP地址
      uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);//源IP地址
      BUF->opcode = HTONS(ARP_REQUEST); //在这里将请求切换为ARP请求
      BUF->hwtype = HTONS(ARP_HWTYPE_ETH);//硬件类型ARP_ETH
      BUF->protocol = HTONS(UIP_ETHTYPE_IP);//0x0800表示IP类型,这个字段标识协议类型
      BUF->hwlen = 6;
      BUF->protolen = 4;
      BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);

      uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];

      uip_len = sizeof(struct arp_hdr);
      return;//可以看到,在此处返回了
    }

    //建立最终的以太网头
    memcpy(IPBUF->ethhdr.dest.addr, tabptr->ethaddr.addr, 6);
  }
  //如果在局域网中并且arp缓存表中有,那么直接构造以太网投
  memcpy(IPBUF->ethhdr.src.addr, uip_ethaddr.addr, 6);

  IPBUF->ethhdr.type = HTONS(UIP_ETHTYPE_IP);

  uip_len += sizeof(struct uip_eth_hdr);
}

这里我们可以看到,uip里面自建了一个arp的缓存表,定义如下

struct arp_entry {
  u16_t ipaddr[2];//IP地址
  struct uip_eth_addr ethaddr;//网卡信息
  u8_t time;
};
struct uip_eth_addr {
  u8_t addr[6];//mac地址
};

那么这个缓存表在什么时候用,就要看arp的响应,也就是uip_arp_arpin这个函数了,

void uip_arp_arpin(void)
{
  //检测ARP的头长度对不对
  if(uip_len < sizeof(struct arp_hdr)) {
    uip_len = 0;
    return;
  }
  uip_len = 0;
  //检测操作码
  switch(BUF->opcode) {
  case HTONS(ARP_REQUEST)://如果是一个arp请求,则发送应答
    if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr)) {
      //首先我们将发送ARP请求的主机更新到ARP缓存表中,因为接下来和可能有更多的交流
      uip_arp_update(BUF->sipaddr, &BUF->shwaddr);

      //ARP请求回应的操作码为2
      BUF->opcode = HTONS(2);
      //将受到的ARP包的源以太网地址当成我们这次回应的目的以太网地址
      memcpy(BUF->dhwaddr.addr, BUF->shwaddr.addr, 6);
      //加入自身的以太网地址做源以太网地址
      memcpy(BUF->shwaddr.addr, uip_ethaddr.addr, 6);
      //对应以太网源地址
      memcpy(BUF->ethhdr.src.addr, uip_ethaddr.addr, 6);
      //对应以太网目的地址
      memcpy(BUF->ethhdr.dest.addr, BUF->dhwaddr.addr, 6);

      BUF->dipaddr[0] = BUF->sipaddr[0];
      BUF->dipaddr[1] = BUF->sipaddr[1];
      BUF->sipaddr[0] = uip_hostaddr[0];
      BUF->sipaddr[1] = uip_hostaddr[1];
      //构造回应包之后等待下一次的发送
      BUF->ethhdr.type = HTONS(UIP_ETHTYPE_ARP);
      uip_len = sizeof(struct arp_hdr);
    }
    break;
    //如果现在我们接受到的是一个ARP回应,那么将这个回应的ip加入arp缓存中
  case HTONS(ARP_REPLY):
    if(uip_ipaddr_cmp(BUF->dipaddr, uip_hostaddr)) {
      uip_arp_update(BUF->sipaddr, &BUF->shwaddr);
    }
    break;
  }

  return;
}

    这其中我们使用了uip_arp_update来更新缓存,基本原理就是先找找已经存在的arp缓存中有没有这个ip的主机,没有加进去,有不加进去,有一半更新缓存表

接下来是arp的周期性处理函数uip_arp_timer,因为arp协议规定每过多长时间更新一次arp表,所以有这个操作,代码如下

void uip_arp_timer(void)
{
  struct arp_entry *tabptr;

  ++arptime;
  //遍历arp表
  for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {
    tabptr = &arp_table[i];
    //检测缓存时间有没有超时
    if((tabptr->ipaddr[0] | tabptr->ipaddr[1]) != 0 &&
       arptime - tabptr->time >= UIP_ARP_MAXAGE) {
           //超时清空这个arp条目
      memset(tabptr->ipaddr, 0, 4);
    }
  }
}

此时ARP的过程基本上就OK了,

然后就是UIP的数据处理了,主要是一个函数

uip_process(),该函数很复杂,还没弄明白,但是不想去改源码的也不用看明白只需要知道,在uip_process中调用了叫做UIP_APPCALL();的宏,这是一个空宏,需要用户实现,当使用uip的时 候,实际上的协议uip_process实现,然后实际性的数据有uip_appcall宏实现,当使用udp的时候还有一个宏uip_udp_periodic中的UDP_APPCALL(),用户实现该宏(实质上是一个函数),进而参与到通信处理的过程中来

时间: 2024-12-06 20:56:31

UIP源码之ARP过程分析的相关文章

AppWidget源码分析---updateAppWidget过程分析

转[原文] 前面一篇文章,分析了AppWidgetProvider和RemoteView的源码,从中我们可以知道它们的实现原理,AppWidgetProvider是一个BroadcastReceiver,所以它是通过广播接收通知的,收到更新通知后,AppWidgetProvider需要去提供View供远程进程显示,而提供的View则是使用RemoteView来代替,通过RemoteView(是一个Parcelable,可跨进程传输数据类型)来作为媒介去传递给远程进程.由远程进程解析RemoteV

Fresco源码解析 - 初始化过程分析

使用Fresco之前,一定先要进行初始化,一般初始化的工作会在Application.onCreate()完成,当然也可以在使用Drawee之前完成. Fresco本身提供了两种初始化方式,一种是使用使用默认配置初始化,另一种是使用用户自定义配置. 如下代码是Fresco提供的两个初始化方法.第一个只需要提供一个Context参数,第二个还需要提供 ImagePipeline 的配置实例 - ImagePipelineConfig. /** Initializes Fresco with the

spark源码阅读--shuffle过程分析

ShuffleManager(一) 本篇,我们来看一下spark内核中另一个重要的模块,Shuffle管理器ShuffleManager.shuffle可以说是分布式计算中最重要的一个概念了,数据的join,聚合去重等操作都需要这个步骤.另一方面,spark之所以比mapReduce的性能高其中一个主要的原因就是对shuffle过程的优化,一方面spark的shuffle过程更好地利用内存(也就是我们前面在分析内存管理时所说的执行内存),另一方面对于shuffle过程中溢写的磁盘文件归并排序和引

Tomcat源码解析-启动过程分析之主干流程

Tomcat启动入口就在脚本startup.sh中,具体脚本可以看tomcat的源码,这个启动脚本主要用来判断环境,找到catalina.sh脚本路径,将启动参数传递给catalina.sh执行.catalina.sh start 最终会执行org.apache.catalina.startup.Bootstrap中的main方法,并把start参数传入.以后分析Tomcat关闭的时候,也是一个套路,最终都会调用到org.apache.catalina.startup.Bootstrap的mai

【转】Android 4.0 Launcher2源码分析——启动过程分析

Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.launcher">  <original-package android:name="com.android.launcher2" /> .

Fresco源码解析 - 创建一个ImagePipeline(一)

在Fresco源码解析 - 初始化过程分析章节中,我们分析了Fresco的初始化过程,两个initialize方法中都用到了 ImagePipelineFactory类. ImagePipelineFactory.initialize(context);会创建一个所有参数都使用默认值的ImagePipelineConfig来初始化ImagePipeline. ImagePipelineFactory.initialize(imagePipelineConfig)会首先用 imagePipelin

AppWidget源码分析(2)---updateAppWidget过程分析.md

前面一篇文章,分析了AppWidgetProvider和RemoteView的源码,从中我们可以知道它们的实现原理,AppWidgetProvider是一个BroadcastReceiver,所以它是通过广播接收通知的,收到更新通知后,AppWidgetProvider需要去提供View供远程进程显示,而提供的View则是使用RemoteView来代替,通过RemoteView(是一个Parcelable,可跨进程传输数据类型)来作为媒介去传递给远程进程.由远程进程解析RemoteView,然后

Netty3 源码分析 - NIO server绑定过程分析

一个框架封装的越好,越利于我们快速的coding,但是却掩盖了很多的细节和原理,但是源码能够揭示一切.服务器端代码在指定好ChannelFactory,设定好选项,而后Bootstrap.bind操作就会开启server,接受对端的连接.所以有必要对这后面的过程分析清楚,下图是关键流程.先是构建一个默认的Pipeline,为我们接下来要创建的监听通道服务,这个Pipeline里面会加入一个Binder的上行事件处理器:接下来创建了至关中的NioServerSocketChannel,在构造的过程

Android中将xml布局文件转化为View树的过程分析(下)-- LayoutInflater源码分析

在Android开发中为了inflate一个布局文件,大体有2种方式,如下所示: // 1. get a instance of LayoutInflater, then do whatever you want LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); // 2. you're in some View class, then jus