

现在需要实现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():
-
服务端nsocket()。
- 分配fd
- malloc serverentry
- 确定协议
- 分配rbuf,sbuf
- 初始化条件变量和互斥锁
- 添加到全局变量entry table
-->nsocket() 内部创建一个serverentry,不是使用fun_tcp_entry_create创建的,指明entry的fd,protocol,创建recvbuf与sendbuf。把serverentry加入tcptable。
-
初始化serveraddr,进行nbind。
- 根据serverfd获取server entry
- 根据函数参数serveraddr,配置server entry的sport,sip,smac
- tcp状态为CLOSED
-->nbind内部:初始化server entry的一些dip,dport 为tcphdr的sip,sport。与serveraddr进行匹配,状态到CLOSED。
-
nlisten。内部实现状态转移到LISTEN。
- 根据serverfd获取serverentry
- 将serverentry状态设置为LISTEN
-
执行accept。
- 根据serverfd获取serverentry
- 阻塞等待get_accept_tcb(serverentry->sport)
- 分配clientfd
- 初始化clientaddr的ip,port
serverentry中的sip,sport是本端地址,所以使用sport。
进入阻塞等待。直到建立第三次握手到来。
-
使用nrecv进行接收数据。
- 根据clientfd获取cliententry
- 条件等待cliententry->mutex. 直到cliententry->recvbuf有数据
- 从clientdata中取出data
- 如果datalen大于tcp server读取的len,就将data前移len,再加入recvbuf
- 释放资源
这里获取的就是cliententry。
-
从nrecv中取出数据,然后通过nsend发送。
- 根据clientfd获取cliententry
- malloc 发送的数据包clientdata
- 初始化clientdata的参数
- 加入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。
处理第三次握手
- cliententry状态到NG_TCP_STATUS_ESTABLISHED
- 通过cliententry->dport 与 entrytable中的sport 比对查找 serverentry
- 通过serverentry唤醒处于accept等待的tcp server线程
因为serverentry只初始化了sip,sport,所以只能通过sport来查找serverentry。为了唤醒tcp server在accept等待的线程。
客户端有数据包到来,可以根据四元组查找到cliententry,再根据cliententry状态为NG_TCP_STATUS_ESTABLISHED,执行fun_tcp_handle_established_rcvd。
- 如果是PSH标记
- 组织一个数据包加入到cliententry的recvbuf
- 更新cliententry的seq,ack
- 组织一个ACk包加入到cliententry的sendbuf
- 如果是FIN标记
- 组织一个数据包加入到cliententry的recvbuf,这个数据包是一个空包用来使tcp nrecv 接收到0,从而判断对方主动断开连接
- 更新cliententry的seq,ack
- 组织一个ACk包加入到cliententry的sendbuf
- cliententry状态到CLOSE_WAIT
客户端主动断开连接,发送FIN。
先执行上面的5,6,7,8,9。
tcp server通过空包,nrecv接收到0,判断对方断开连接,执行nclose(clientfd);
- 通过sockfd查找entry
- 如果不是LISTEN状态
- 组织一个数据包tcpdata,标记为FIN | ACK
- 把tcpdata加入entry的sendbuf
- entry状态到LAST_ACK
- 回收sockfd
- 如果是LISTEN状态,表明 三次握手还没成功就关闭连接
- 从entrytable中移除,并释放内存
pkt process判断是LAST_ACK状态,执行ng_tcp_handle_last_ack。
- cliententry状态到CLOSED
- 从table中移除clietnentry
- 释放资源
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