LWIP之TCP层发送相关 (转)

标签:
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。 http://bluefish.blog.51cto.com/214870/158415
2009-5-11 LWIP TCP 层发送相关
 
       现在我们正式开始进入对 TCP 的研究,它属于传输层协议,它为应用程序提供了可靠的字节流服务。在 LWIP 中基本的 TCP 处理过程被分割为六个功能函数的实现: tcp_input(), tcp_process(), tcp_receive() 【与 TCP 输入有关】 , tcp_write(), tcp_enqueue(), tcp_output() 【用于 TCP 输出】。这些是从大的方面来划分的。
 
       现在先从小部 tcp.c 文件来分析一下:我们知道这里的函数都是被 socket 那一层的最终调用的。为了利于分析,我选择 lwip_send 函数来分析,具体不多说,最终调用到了
static err_t do_writemore(struct netconn *conn) 这个函数,当然这期间也做了不少工作,最主要的就是把发送数据的指针放到了 msg 的指定变量中
msg.msg.msg.w.dataptr = dataptr;// 指针
msg.msg.msg.w.len = size;      // 长度
这些又经过转化放到了 netconn write_msg
最后就是对 do_writemore 的调用了,下面详细分析这个函数。
 
       这个函数的最直接调用有以下几个:
available = tcp_sndbuf(conn->pcb.tcp);
err = tcp_write(conn->pcb.tcp, dataptr, len, conn->write_msg->msg.w.apiflags);
err = tcp_output_nagle(conn->pcb.tcp);
err = tcp_output(conn->pcb.tcp);
 
好,先看 tcp_sndbuf 这个。从其参数,我们应该想像 pcb 的重要性。
#define          tcp_sndbuf(pcb)   ((pcb)->snd_buf) // 由下面分析得这里直接返回 buf 大小
那就继续跟踪这个 pcb 好了。是的,这还得从 lwip_socket 开始,在创建连接 netconn 的时候调用了 do_newconn ,它接着调用了 pcb_new ,在这个函数中如果是 tcp 的话有以下代码
msg->conn->pcb.tcp = tcp_new();
哈哈,还记得吧,在前面我讨论到了这里,就没有再讨论了。嗯,现在开始吧。
* Creates a new TCP protocol control block but doesn't place it on
* any of the TCP PCB lists.
* The pcb is not put on any list until binding using tcp_bind().
struct tcp_pcb * tcp_new(void)
{
  return tcp_alloc(TCP_PRIO_NORMAL);
}
也许注释的意义更大一些吧,哈哈,至此,我们从注释可以知道, tcp_bind 的作用是把 tcp pcb 放入 list 中,至少是某一个 list
××××××××××××××××××××××××××××××××××××××× 既然都提到了,似乎不说说有点过意不去啊。 Lwid_bind 函数最终会调用到 tcp_bind 【当然是 tcp 为例进行分析】。这个函数也比较有意思,在进入正题之前先来了下面这么个调用
if (port == 0)
{
    port = tcp_new_port();
}
意思很明显,就是分配个新的端口号,有意思的就是这个端口号的分配函数 tcp_new_port
static u16_t
tcp_new_port(void)
{
  struct tcp_pcb *pcb;
#ifndef TCP_LOCAL_PORT_RANGE_START
#define TCP_LOCAL_PORT_RANGE_START 4096
#define TCP_LOCAL_PORT_RANGE_END   0x7fff
#endif
  static u16_t port = TCP_LOCAL_PORT_RANGE_START;
 
 again:
  if (++port > TCP_LOCAL_PORT_RANGE_END) {
    port = TCP_LOCAL_PORT_RANGE_START;
  }
 
  for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
    if (pcb->local_port == port) {
      goto again;
    }
  }
  for(pcb = tcp_tw_pcbs; pcb != NULL; pcb = pcb->next) {
    if (pcb->local_port == port) {
      goto again;
    }
  }
  for(pcb = (struct tcp_pcb *)tcp_listen_pcbs.pcbs; pcb != NULL; pcb = pcb->next) {
    if (pcb->local_port == port) {
      goto again;
    }
  }
  return port;
} 分别检查了 3 pcb 链表,本来我不想把这个函数的实现列在这里的,但是这里告诉了我们一些东西,至少我们知道有 3 pcb 的链表,分别是 tcp_active_pcbs 【处于接受发送数据状态的 pcbs 】、 tcp_tw_pcbs 【处于时间等待状态的 pcbs 】、 tcp_listen_pcbs 【处于监听状态的 pcbs 】。
似乎 tcp_bind 函数更有意思,它分别检查了 4 pcbs 链表,除了上面的三个还有一个 tcp_bound_pcbs 【处于已经绑定但还没有连接或者监听 pcbs 】。作用是是否有与目前 pcb ipaddr 相同的 pcb 存在。不过这些都不是最重要的, TCP_REG(&tcp_bound_pcbs, pcb); 才是我们的目的,果然如此,直接加入到了 tcp_bound_pcbs 链表中了。
×××××××××××××××××××××××××××××××××××××××
struct tcp_pcb * tcp_alloc(u8_t prio) // 是的,你没看错,这个参数是学名是叫优先级
{
       pcb = memp_malloc(MEMP_TCP_PCB);
if (pcb != NULL)
{
           memset(pcb, 0, sizeof(struct tcp_pcb));
           pcb->prio = TCP_PRIO_NORMAL;
           pcb->snd_buf = TCP_SND_BUF; // 对的,别的可以先不管,这就是我们要找的东西
           pcb->snd_queuelen = 0;
           pcb->rcv_wnd = TCP_WND;
           pcb->rcv_ann_wnd = TCP_WND;
           pcb->tos = 0;
           pcb->ttl = TCP_TTL;
           /* The send MSS is updated when an MSS option is received. */
           pcb->mss = (TCP_MSS > 536) ? 536 : TCP_MSS;
           pcb->rto = 3000 / TCP_SLOW_INTERVAL;
           pcb->sa = 0;
           pcb->sv = 3000 / TCP_SLOW_INTERVAL;
           pcb->rtime = -1;
           pcb->cwnd = 1;
           iss = tcp_next_iss();
           pcb->snd_wl2 = iss;
           pcb->snd_nxt = iss;
           pcb->snd_max = iss;
           pcb->lastack = iss;
           pcb->snd_lbb = iss;  
           pcb->tmr = tcp_ticks;
      
            pcb->polltmr = 0;
 
#if LWIP_CALLBACK_API
           pcb->recv = tcp_recv_null;
#endif /* LWIP_CALLBACK_API */ 
   
           /* Init KEEPALIVE timer */
           pcb->keep_idle  = TCP_KEEPIDLE_DEFAULT;
   
#if LWIP_TCP_KEEPALIVE
           pcb->keep_intvl = TCP_KEEPINTVL_DEFAULT;
           pcb->keep_cnt   = TCP_KEEPCNT_DEFAULT;
#endif /* LWIP_TCP_KEEPALIVE */
 
           pcb->keep_cnt_sent = 0;
      }
      return pcb;
} 就是一个 tcp_pcb 的结构体初始化过程,使用默认值填充该结构
 
       好了,下面接着走,该轮到 tcp_writ e 了吧
* Write data for sending (but does not send it immediately).
 * It waits in the expectation of more data being sent soon (as
 * it can send them more efficiently by combining them together).
 * To prompt the system to send data now, call tcp_output() after
 * calling tcp_write().
src/core/tcp_out.c
err_t tcp_write(struct tcp_pcb *pcb, const void *data, u16_t len, u8_t apiflags)
{
  /* connection is in valid state for data transmission? */
  if (pcb->state == ESTABLISHED ||
     pcb->state == CLOSE_WAIT ||
     pcb->state == SYN_SENT ||
     pcb->state == SYN_RCVD)
{
if (len > 0)
{
      return tcp_enqueue(pcb, (void *)data, len, 0, apiflags, NULL, 0);
    }
    return ERR_OK;
  }
else
{
    return ERR_CONN;
  }
}
这个函数确实够简单的了,检查 pcb 状态,直接入队列。嗯,还是觉得看注释比看函数实现过瘾。
       下面的这个 tcp_enqueue 才是个大头函数, lwip 协议栈的设计与实现文档中是这么介绍的:应用层调用 tcp_write ()函数以实现发送数据,接着 tcp_write ()函数再将控制权交给 tcp_enqueue (),这个函数会在必要时将数据分割为适当大小的 TCP 段,然后再把这些 TCP 段放到所属连接的传输队列中【 pcb->unsent 】。函数的注释是这样写的:
* Enqueue either data or TCP options (but not both) for tranmission
 * Called by tcp_connect(), tcp_listen_input(), tcp_send_ctrl() and tcp_write().
 
       咱们接着上面往下看, tcp_output_nagle 这个函数实际上是个宏,通过预判断以决定是否调用 tcp_output 。也就是说最后的执行是 tcp_output 来实现的。这个函数或许比 tcp_enqueue 还要恐怖,从体积上来看。这里也只做一个简单介绍,具体请参阅 tcp_out.c 文档。该函数的注释是这么说的: Find out what we can send and send it 。文档是这么解释的: tcp_output 函数会检查现在是不是能够发送数据,也就是判断接收器窗口是否拥有足够大的空间,阻塞窗口是否也足够大,如果条件满足,它先填充未被 tcp_enqueue 函数填充的 tcp 报头字段,接着就使用 ip_route 或者 ip_output_if 函数发送数据。
 

本文出自 “bluefish” 博客,请务必保留此出处http://bluefish.blog.51cto.com/214870/158415

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值