dpdk实现tcpserver

仓库:wait/1010-dpdk


现在需要实现tcp serve。

和实现udp server的结构一样,也需要开辟一个线程TCPserver。这个tcp server线程中是网络io的流程,比如socket(), bind(), listen(), accept(),recv(),send(),close(),所以就需要实现这些网络io的api。recv 就是从连接的recvbuf中取出数据,send就是把数据写入sendbuf中。

listen中实现三次握手。close中实现四次挥手。

并且完善状态机的回调函数。



udp的pkt process是把接收到的数据放入entry的recvbuf。udp server中的recvfrom只需要从entry的recvbuf中读取数据即可。

但是tcp不一样,tcp 的 pkt process 实现的是整个状态机,那也应该是把解析好的数据给entry的recvbuf才对啊。为什么nrecv还需要自己组织datagram?组织好了后加入entry的recvbuf,那么谁来读取entry的recvbuf呢?

写错了,tcp也是和udp一样,从entry的recvbuf中读取数据。


tcp server线程,入口函数tcp_server_entry():

  1. 服务端nsocket()。

    1. 分配fd
    2. malloc serverentry
    3. 确定协议
    4. 分配rbuf,sbuf
    5. 初始化条件变量和互斥锁
    6. 添加到全局变量entry table

    -->nsocket() 内部创建一个serverentry,不是使用fun_tcp_entry_create创建的,指明entry的fd,protocol,创建recvbuf与sendbuf。把serverentry加入tcptable。

  2. 初始化serveraddr,进行nbind。

    1. 根据serverfd获取server entry
    2. 根据函数参数serveraddr,配置server entry的sport,sip,smac
    3. tcp状态为CLOSED

    -->nbind内部:初始化server entry的一些dip,dport 为tcphdr的sip,sport。与serveraddr进行匹配,状态到CLOSED。

  3. nlisten。内部实现状态转移到LISTEN。

    1. 根据serverfd获取serverentry
    2. 将serverentry状态设置为LISTEN
  4. 执行accept。

    1. 根据serverfd获取serverentry
    2. 阻塞等待get_accept_tcb(serverentry->sport)
    3. 分配clientfd
    4. 初始化clientaddr的ip,port

    serverentry中的sip,sport是本端地址,所以使用sport。

    进入阻塞等待。直到建立第三次握手到来。

  5. 使用nrecv进行接收数据。

    1. 根据clientfd获取cliententry
    2. 条件等待cliententry->mutex. 直到cliententry->recvbuf有数据
    3. 从clientdata中取出data
    4. 如果datalen大于tcp server读取的len,就将data前移len,再加入recvbuf
    5. 释放资源

    这里获取的就是cliententry。

  6. 从nrecv中取出数据,然后通过nsend发送。

    1. 根据clientfd获取cliententry
    2. malloc 发送的数据包clientdata
    3. 初始化clientdata的参数
    4. 加入cliententry->sendbuf

tcp pkt process线程:

客户端发来第一次握手,tcp pkt process线程通过ip,port查找entry,如果没找到就返回-2。在serarch的内部根据四元组查不到后,会根据dport和状态判断,来获取serverentry。

这里获取的是serverentry。

然后执行fun_tcp_handle_listen()

这个时候的serverentry属于半连接。通过fun_tcp_entry_create创建synClientEntry,并且把synClientEntry加入entry table,synClientEntry指明了sip,dip,seq等,status=SYN_RCVD ,没有分配fd,fd=-1。

组织第二次握手的数据包dataClientTcp。

复制代码
dataClientTcp->seqnum = synClientEntry->seq_next;
dataClientTcp->acknum = ntohl(tcphdr->sent_seq) + 1;

然后把dataClientTcp加入synClientEntry的sendbuf。

第三位握手到来,可以根据四元组查找到cliententry。执行fun_tcp_handle_syn_rcvd。

处理第三次握手

  1. cliententry状态到NG_TCP_STATUS_ESTABLISHED
  2. 通过cliententry->dport 与 entrytable中的sport 比对查找 serverentry
  3. 通过serverentry唤醒处于accept等待的tcp server线程

因为serverentry只初始化了sip,sport,所以只能通过sport来查找serverentry。为了唤醒tcp server在accept等待的线程。

客户端有数据包到来,可以根据四元组查找到cliententry,再根据cliententry状态为NG_TCP_STATUS_ESTABLISHED,执行fun_tcp_handle_established_rcvd。

  1. 如果是PSH标记
  2. 组织一个数据包加入到cliententry的recvbuf
  3. 更新cliententry的seq,ack
  4. 组织一个ACk包加入到cliententry的sendbuf
  5. 如果是FIN标记
  6. 组织一个数据包加入到cliententry的recvbuf,这个数据包是一个空包用来使tcp nrecv 接收到0,从而判断对方主动断开连接
  7. 更新cliententry的seq,ack
  8. 组织一个ACk包加入到cliententry的sendbuf
  9. cliententry状态到CLOSE_WAIT

客户端主动断开连接,发送FIN。

先执行上面的5,6,7,8,9。

tcp server通过空包,nrecv接收到0,判断对方断开连接,执行nclose(clientfd);

  1. 通过sockfd查找entry
  2. 如果不是LISTEN状态
  3. 组织一个数据包tcpdata,标记为FIN | ACK
  4. 把tcpdata加入entry的sendbuf
  5. entry状态到LAST_ACK
  6. 回收sockfd
  7. 如果是LISTEN状态,表明 三次握手还没成功就关闭连接
  8. 从entrytable中移除,并释放内存

pkt process判断是LAST_ACK状态,执行ng_tcp_handle_last_ack。

  1. cliententry状态到CLOSED
  2. 从table中移除clietnentry
  3. 释放资源

flag = PUSH表示 接收到数据后不需要等后面的数据,直接交给用户层。

为了从get_hostentry_fromfd中区分tcp连接还是udp连接,可以直接读取protocol来区分这两个连接。但是需要要求udp的结构体socket_entry,tcp的结构体tcp_socket_entry,在**protocol** 字段前的所有字段保持一直,

复制代码
struct tcp_socket_entry{
    int fd;
    //unsigned int status; // 阻塞 非阻塞
    uint32_t  dip;
    uint8_t   localmac[RTE_ETHER_ADDR_LEN];
    uint16_t  dport;
    uint8_t   protocol;

    uint32_t  sip;
    uint16_t  sport;

    uint32_t seq_next;
    uint32_t ack_next;

    NG_TCP_STATUS status;

    struct rte_ring *recvbuf;
    struct rte_ring *sendbuf;

    struct tcp_socket_entry *prev;
    struct tcp_socket_entry *next;

    pthread_cond_t cond;
    pthread_mutex_t mutex;
};
struct socket_entry{
    int fd;
    //unsigned int status; // 阻塞 非阻塞
    uint32_t sockip;
    uint8_t  sockmac[RTE_ETHER_ADDR_LEN];
    uint16_t  sockport;    
    uint8_t  protocol;

    struct rte_ring *recvbuf;
    struct rte_ring *sendbuf;

    struct socket_entry *prev;
    struct socket_entry *next;

    pthread_cond_t cond;
    pthread_mutex_t mutex;

};
void *get_hostentry_fromfd(int sockfd)
{
    // serach udp
    struct socket_entry *temp = NULL;
    for(temp = gSockList;temp!=NULL;temp=temp->next)
    {
        if(temp->fd == sockfd){
            return temp;
        }
    }

    // serach tcp
    struct tcp_socket_entry *tcptemp = NULL;
    struct tcp_table *tcpt = tcp_table_instance();
    for(tcptemp = tcpt->tcpList ; tcptemp != NULL ; tcptemp = tcptemp->next){
        if(tcptemp->fd == sockfd){
            return tcptemp;
        }
    }

    return NULL;
}

这样就可以通过get_hostentry_fromfd返回的参数的第17个字节来判断是udp还是tcp。

int fd; 4字节

uint32_t sockip; 4字节

uint8_t sockmacRTE_ETHER_ADDR_LEN; 6字节

uint16_t sockport; 2字节

uint8_t protocol; 1字节,第17个字节

这两个结构体的第17个字节都是protocol,所以可以用来判断。


fun_tcp_handle_listen中创建synClientEntry

fun_tcp_entry_create(iphdr->src_addr,iphdr->dst_addr,tcphdr->src_port,tcphdr->dst_port);

LL_ADD(synClientEntry,tcpt->tcpList);

这里的iphdr,tcphdr都是客户端发送过来的。

这里synClientEntry的sip就是客户端的sip。

把dataClientTcp加入到synClientEntry->sendbuf。

在tcp_pkt_send中,rte_ring_mc_dequeue(entry->sendbuf,(void**)&datatcp);

这里的entry就是synClientEntry,datatcp就是dataClientTcp。

在进行查找mac的时候,应该是查找客户端的mac就是fun_get_dst_macaddr(entry->sip);


在没有连接到来的时候是通过服务端进行判断,连接到来以后就是客户端连接进行判断。

不对,那么状态机依靠的是谁的状态?

struct tcp_socket_entry *entrytcp = fun_tcp_entery_search(iphdr->src_addr,iphdr->dst_addr,tcphdr->src_port,tcphdr->dst_port);

这里搜索的就是客户端连接。

所以依靠的是客户端连接,在客户端连接没有到来的时候才依靠服务端连接。


服务端的连接serverentry只初始化sport和sip,都是本端地址。

组织cliententry的datagram,这个是客户端的连接,是服务端需要发送给客户端的数据,所以这个datagram的sip就是本端ip。

输入的recv都放进client recv buf。

发送的send都放进clientsendbuf。


这里实现的tcp server。

使用的是两个entry,一个是客户端cliententry,一个是服务端serverentry。

nsocket,nbind,所处理的都是serverentry。


tcphost->dport = tcphaddr->sin_port;

​ rte_memcpy(&tcphost->dip,&tcphaddr->sin_addr.s_addr,sizeof(uint32_t));

tcp server nbind中绑定serverentry时候,serverentry的dip是tcphdr的sip。搞反了d与s。

(cliententry = get_accept_tcb(tcphost->sport))

这里是服务端,所以应该是sport,但写成了dport。


tcp 连接,断开,连接,断开,连接 就会出现段错误。


文章参考<零声教育>的C/C++linux服务期高级架构系统教程学习:0voice · GitHub

相关推荐
番茄灭世神1 小时前
PN学堂GD32教程第16篇——Fatfs
单片机·嵌入式硬件
小莞尔1 小时前
【51单片机】【protues仿真】基于51单片机六路抢答器系统
c语言·单片机·嵌入式硬件·物联网·51单片机
2501_922639582 小时前
率能半导体 SS6622E直流双向马达驱动芯片:20V/2.4A,覆盖电子锁、玩具、无线充电、机器人四大应用场景
stm32·单片机·嵌入式硬件·硬件工程
pirateeee3 小时前
Yocto 构建基于官方 imx6ull-14x14-evk 的 ubuntu 镜像
linux·嵌入式硬件
恒锐丰科技林技术员3 小时前
EG2132 半桥栅极驱动芯片解析:高压半桥应用的性价比之选
经验分享·嵌入式硬件·硬件工程
尼喃3 小时前
3.7V升压5V芯片——平芯微PW5100/PW5300/PW5300A/PW6276/PW5303/PW5012
嵌入式硬件
意法半导体STM323 小时前
【官方原创】LAT6041 STM32MP1x USB OTG FIFO设置
stm32·单片机·嵌入式硬件·mcu·fifo·stm32mp1x
笨笨饿3 小时前
#145 _Codex 三层故障详细排查与修复步骤
开发语言·人工智能·stm32·单片机·嵌入式硬件
深圳老胡3 小时前
STM32C5A3ZG开发板网络接口配置流程详解
笔记·stm32·单片机·代码规范