DIY TCP/IP TCP模块的实现9

上一篇:DIY TCP/IP TCP模块的实现8
9.11 TCP滑动窗口的实现3
9.10节的DIY TCP/IP已经可以正确接收TCP数据帧了。9.10节只是解析一下收到的TCP数据帧携带的数据长度,没有对接收到的数据部分做任何处理。在实现将接收到的TCP数据帧携带的数据放入滑动窗口之前,本节先来实现滑动窗口的初始化。DIY TCP/IP没有编译成动态库被其他应用程序调用,所以需要模拟一个读取线程从滑动窗口中读取已经确认过的TCP数据。
参照9.9节给出的滑动窗口数据结构图,在tcp.c文件中定义TCP滑动窗口数据结构tcp_slide_window_t

   typedef struct _tcp_slide_window {
           unsigned char *buf;
           /* receive buffer size */
           unsigned int buf_sz;
           /* receive buffer start offset */
           unsigned int buf_start;
           /* acknowledged frame pointer (first valid byte) */
           unsigned int acked_start;
           /* acknowledged frame pointer (first invalid byte) */
           unsigned int acked_end;
           /* received frame (unacked) start pointer (first valid byte )*/
           unsigned int recv_start;
           /* received frame (unacked) end pointer (first invalid byte )*/
           unsigned int recv_end;
           /* indicate data available to user */
           pthread_cond_t data_available_cond;
           /* data available mutex */
           pthread_mutex_t data_available_mutex;
           /* receiver simulator */
           pthread_t usr_simulator;
           /* stop simulator flag */
           unsigned char stop_simulator;
   } tcp_slide_window_t;

buf指向接收缓存的内存空间,buf_sz为接收缓存的大小。将buf看成字节数据组,buf_start, acked_start,acked_end,recv_start和recv_end均为字节数组buf的下标。便于描述将这些下标值称为“指针”。
buf_start指向接收缓存的第一个有效的字节,recv_start指向接收但尚未确认的数据的第一个字节,recv_end指向接收但尚未确认的数据的末尾字节的下一个字节,即接收到的数据按字节顺序从recv_end处开始存放。
acked_start指向可以被读取线程提取的第一个字节,acked_end指向可被提取的末尾字节的下一个字节,即receive start字节处。这些数据组下标随着TCP数据的接收和读取移动,当移动位置超过buf + 16M后,均回绕到buffer start继续开始。回复ack时,16M – (recv_end – acked_start)或16M – (recv_end + 16M – acked_start)即为接收缓存中剩余空间的大小。Sliding window 初始化时,这些数组下标均初始化为0。
应用程序读取TCP接收的数据时,也是读取的某个TCP连接的接收缓存,因此将模拟的读取线程的数据结构放在tcp_slide_window_t数据结构中。data_available_cond是读取线程的睡眠等待条件,data_available_mutex是保护接收缓存的互斥量,避免读取线程与DIY TCP/IP同时修改滑动窗口的“指针”。usr_simulator是模拟的读取线程的pthread_t数据结构,stop_simulator是读取线程退出的条件。
TCP接收缓存与TCP连接对应,即每个TCP连接都有自己的接收缓存。所以还需要修改tcp_conn_t数据结构,添加tcp_slide_window_t成员,即每个TCP连接都有自己的滑动窗口。

  typedef struct _tcp_conn {
          /* remote port */
          unsigned short r_port;
          /* local port */
          unsigned short l_port;
          /* remote ip */
          unsigned char r_ip[4];
          /* local ip */
          unsigned char l_ip[4];
          /* remote max segment size */
          unsigned short r_mss;
          /* local mss */
          unsigned short l_mss;
          /* remote window size */
          unsigned short r_wnd_sz;
          /* local receive window size */
          unsigned short l_wnd_sz;
…
          /* connection state */
          tcp_conn_state_t state;
          /* sliding window */
          tcp_slide_window_t sw;
  } tcp_conn_t;

sw是tcp_conn_t的新增滑动窗口成员,tcp_conn_t数据结构的其余成员与9.4的定义一致,这里不再给出tcp_conn_t数据结构的完整定义。
数据结构修改完后,在tcp.c文件中新增tcp_init_slide_window函数,初始化TCP滑动窗口,该函数在初始化TCP连接的函数tcp_conn_open中被调用。

  static int tcp_init_slide_window(tcp_conn_t *conn)
  {
          int ret = 0;
          unsigned int recv_buf_sz = 0;
          tcp_slide_window_t *sw = NULL;
  
          if (conn == NULL)
                  return -1;
  
          recv_buf_sz = conn->l_wnd_sz << conn->l_shift_cn;
          sw = &conn->sw;
          sw->buf = (unsigned char *)malloc(recv_buf_sz);
          if (sw->buf == NULL) {
                  log_printf(DEBUG, "No memory "
             "for tcp receive buffer, %s (%d)\n",
             strerror(errno), errno);
                  ret = -1;
                  goto out;
          }
          log_printf(DEBUG, "TCP init slide window\n");
          sw->buf_sz = recv_buf_sz;
          sw->buf_start = 0;
          sw->acked_start = 0;
          sw->acked_end = 0;
          sw->recv_start = 0;
          sw->recv_end = 0;
          pthread_cond_init(&sw->data_available_cond, NULL);
          pthread_mutex_init(&sw->data_available_mutex, NULL);
  
          pthread_create(&sw->usr_simulator,
             NULL, simulate_read_routine, (void *)sw);
  
  out:
          return ret;
  }

tcp_init_slide_window是TCP模块的静态函数,只在TCP模块内部使用。函数的入参是tcp_conn_t指针conn,指向与滑动窗口对应的TCP连接数据结构。成功返回0,出错返回非0。
Line 10-20: 计算本地TCP连接的接收缓存大小,本地TCP连接的l_wnd_sz和l_shift_cn在tcp_conn_open函数中被初始化为65535和8。此处计算得到接收缓存的大小为16M。申请16M的内存空间,设置滑动窗口sw->buf指向申请到的内存空间。如果申请内存空间失败直接返回。
Line 21-26: 初始化滑动窗口的buf_sz,buf_start,acked_start,acked_end,recv_start和recv_end”指针”,均”指向”字节数组的开始字节。
line 27-35: 初始化模拟读取进程的睡眠等待条件data_available_cond,保护接收缓存的互斥量data_available_mutex,模拟读取进程的函数体simulate_read_routine,读取进程函数体的入参为指向滑动窗口的指针sw。

  static void * simulate_read_routine(void *arg)
  {
  #define EXPECT_SZ 1460 * 16
          unsigned char *data_buf = NULL;
          tcp_slide_window_t *sw = NULL;
  
          if (arg == NULL)
                  return NULL;
  
          sw = (tcp_slide_window_t *)arg;
          data_buf = (unsigned char *)malloc(EXPECT_SZ);
          if (data_buf == NULL) {
                  log_printf(DEBUG, "No memory "
             "for user data, %s (%d)\n",
             strerror(errno), errno);
                  return NULL;
          }
  
          log_printf(DEBUG, "User simulator started\n");
          while(sw->stop_simulator != 1) {
                  pthread_mutex_lock(&sw->data_available_mutex);
                  pthread_cond_wait(&sw->data_available_cond,
                     &sw->data_available_mutex);
                  pthread_mutex_unlock(&sw->data_available_mutex);
  
                  if (sw->stop_simulator)
                          break;
                  memset(data_buf, 0, EXPECT_SZ);
                  //tcp_export_api_recv(sw, data_buf, EXPECT_SZ);
                  //printf("%s\n", data_buf);
          }
  
          if (data_buf)
                  free(data_buf);
          log_printf(DEBUG, "User simulator stopped\n");
          return NULL;
  }

Line 1-19: 将读取线程函数体的参数转换为滑动窗口tcp_slide_window_t类型,为读取线程申请内存空间,存放读取到的TCP数据,申请内存空间失败时返回NULL。
Line 20-31: 读取线程进入循环体,stop_simulator为1时,退出循环体。进入循环体后,先获取互斥量data_available_mutex,如果接收缓存中没有可用的数据,读取线程睡眠等待在data_available_cond条件的等待队列上。读取线程被唤醒后,释放互斥量,检查是否是要退出循环,如果不是,则读取本地TCP连接的接收缓存中的数据。读取TCP接收缓存数据的代码在9.12节扩展实现,本节只是将读取线程中存放数据的内存空间清零。
line 33-37: 读取线程退出循环体后,释放存放接收TCP数据的内存空间,返回。
tcp_init_slide_window用于初始化滑动窗口,在tcp_conn_open函数中被调用。与其对应的新增函数还有tcp_deinit_slide_window用于销毁滑动窗口,在tcp_deinit函数中销毁TCP连接之前被调用。

 static void tcp_deinit_slide_window(tcp_conn_t *conn)
 {
    tcp_slide_window_t *sw = NULL;
    log_printf(DEBUG, "TCP deinit slide window\n");
    if (conn == NULL)
            return;
    sw = &conn->sw;
    sw->stop_simulator = 1;
    /* wakeup simulator */
    pthread_cond_signal(&sw->data_available_cond);
    pthread_join(sw->usr_simulator, NULL);
    dump_slide_window(conn);
    if(sw->buf)
            free(sw->buf);
 }

tcp_deinit_slide_window也是TCP模块的静态函数,入参是与滑动窗口对应的TCP连接的指针,无返回值,该函数在销毁TCP模块的函数tcp_deinit中被调用。
line 8 – 15: 设置读取线程的停止条件为1,唤醒读取线程,调用pthread_join等待读取线程退出并回收线程资源。dump_slie_window打印滑动窗口的各个指针的状态,最后释放滑动窗口占用的内存空间。

  static void dump_slide_window(tcp_conn_t *conn)
  {
          unsigned int acked_start = 0;
          unsigned int acked_end = 0;
          unsigned int recv_start = 0;
          unsigned int recv_end = 0;
          unsigned int left_sz = 0;
          unsigned int acked_sz = 0;
          unsigned int recv_sz = 0;
          tcp_slide_window_t *sw = NULL;
  
          if (conn == NULL)
                  return;
          sw = &conn->sw;
          pthread_mutex_lock(&sw->data_available_mutex);
          acked_start = sw->acked_start;
          acked_end = sw->acked_end;
          recv_start = sw->recv_start;
          recv_end = sw->recv_end;
          pthread_mutex_unlock(&sw->data_available_mutex);
  
          log_printf(DEBUG, "______________________________\n\n");
          log_printf(DEBUG, "sw->buf_sz: %u\n", sw->buf_sz);
          log_printf(DEBUG, "sw->buf_start: %u\n", sw->buf_start);
          log_printf(DEBUG, "sw->acked_start: %u\n", acked_start);
          log_printf(DEBUG, "sw->acked_end: %u\n", acked_end);
          log_printf(DEBUG, "sw->recv_start: %u\n", recv_start);
          log_printf(DEBUG, "sw->recv_end: %u\n", recv_end);
          log_printf(DEBUG, "______________________________\n");
  }

dump_slide_window用于debug滑动窗口的实现,首先是获取保护滑动窗口的互斥量,得到滑动窗口的acked_start,acked_end,recv_start和recv_end”指针”位置后,打印出具体的数值。如果仅仅是tcp_deinit_slide_window调用dump_slide_window,就不必获取互斥量,该函数可以在滑动窗口的实现代码的其他函数中也被用到,所以这里需要获取互斥量。
最后修改tcp_conn_open和tcp_deinit函数,添加对tcp_init_slide_window和tcp_deinit_slide_window的调用。

  int tcp_conn_open(unsigned short port)
  {
     int ret = 0, i;
     tcp_conn_t *conn = NULL;
  
…
     conn->state = STATE_LISTEN;
     conn->l_port = port;
     conn->l_wnd_sz = 65535;
     conn->l_shift_cn = 8;
     conn->l_mss = get_ifmtu(DEFAULT_IFNAME)
                 - sizeof(iphdr_t) - sizeof(tcphdr_t);
     log_printf(INFO, "TCP listen on port: %u\n", conn->l_port);
     /* init tcp sliding window */
     tcp_init_slide_window(conn);
  out:
     return ret;
  }

tcp_conn_open监听指定端口,初始化TCP本地连接,函数的末尾调用tcp_init_slide_window初始化与本地TCP连接对应的滑动窗口。

  void tcp_deinit()
  {
     int i = 0;
     tcp_conn_t *conn = NULL;
     system("./config.sh "DEFAULT_IFNAME" restore");
     if (tcp_connections == NULL)
         return;
     log_printf(INFO, "Destroy TCP Connections\n");
     /* deinit tcp slide window */
     for (i = 0; i < tcp_conn_num; i ++) {
         conn = &tcp_connections[i];
         tcp_deinit_slide_window(conn);
     }
     free(tcp_connections);
  }

tcp_deinit函数遍历本地TCP连接表tcp_connections,调用tcp_deinit_slide_window销毁与TCP连接对应的滑动窗口,最后释放TCP连接占用的内存。
本节在9.10节的基础上修改了TCP连接数据结构,添加了TCP滑动窗口的初始化和销毁的代码,实现了模拟读取线程的函数体。本节测试只需检查DIY TCP/IP的运行log,查看读取线程是否启动,销毁时查看滑动窗口的状态信息是否打印。
DIY TCP/IP Slide Window Init
从DIY TCP/IP的打印输出可以看到,”User simulator started”说明TCP模块的tcp_conn_open函数创建本地TCP连接时成功初始化了TCP滑动窗口。键入ctrl+c结束运行时,打印出滑动窗口信息,说明tcp_deinit_slide_window被正确调用,打印出的滑动窗口的”指针”位置也是符合预期的。
下一篇:DIY TCP/IP TCP模块的实现10

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值