總是認(rèn)為on MCU上實(shí)現(xiàn)TCP sever之流的應(yīng)用是扯淡,誰(shuí)會(huì)去連接呢。而且一度認(rèn)為和客戶端并沒(méi)有區(qū)別,沒(méi)什么用。直到最近才發(fā)現(xiàn)其實(shí)并沒(méi)不是那樣,最不濟(jì)做IAP嘛。我想對(duì)我來(lái)說(shuō)這都是空想,臨淵慕魚不如退而結(jié)網(wǎng),我只要實(shí)現(xiàn)一個(gè)TCP sever就算是走了一小步了吧。
1、Socket 的TCP sever 代碼(C++) SOCKET server = socket(AF_INET,SOCK_STREAM,0);
if(server == INVALID_SOCKET)
{
DWORD err1 = WSAGetLastError();
return;
}
SOCKADDR_IN sockAddr;
sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(1234); int flag1 = ::bind(server,(const sockaddr*)&sockAddr,sizeof(sockAddr));
if(flag1 == SOCKET_ERROR)
{
DWORD err2 = WSAGetLastError();
return;
}
else
int flag2 = ::listen(server,5);
if(flag2 == SOCKET_ERROR)
{
DWORD err3 = WSAGetLastError();
return;
}
int len = sizeof(sockaddr);
SOCKET s = ::accept(server,(sockaddr*)&sockAddr,&len);
if(s == INVALID_SOCKET)
{
DWORD err4 = WSAGetLastError();
closesocket(s);
WSACleanup();
}
可見(jiàn)大體上分為這么幾步:Create Socket、Bind Socket、Listen Socket 、Accept Socket。
2、分解(RAW API)
2.1 Create Socket
從應(yīng)用層看來(lái)是建立了一個(gè)socket實(shí)際上socket僅僅是個(gè)索引而已。真正起作用的是TCPIP的協(xié)議控制塊。所謂的PCB。所以分析的時(shí)候還是RAW API 創(chuàng)建的時(shí)候PCB會(huì)動(dòng)態(tài)的從內(nèi)存中分配出來(lái),這個(gè)內(nèi)存是在內(nèi)存管理的大塊內(nèi)存中切割出來(lái)一塊用于TCP PCB的一小塊內(nèi)存塊,然后再在這塊內(nèi)存塊中再次切割一塊TCP PCB 所需的大小的內(nèi)存塊。內(nèi)存已經(jīng)對(duì)齊并且是PCB的正整數(shù)倍。廣度由這個(gè)宏來(lái)定義。MEMP_NUM_TCP_PCB 。實(shí)際上是個(gè)二維數(shù)組,TCP PCB所在的地方被包含內(nèi)。于是PCB就被創(chuàng)建起來(lái),他的唯一ID是他的首地址。用一個(gè)指針來(lái)存儲(chǔ)。這樣就跑不了。
建立PCB后開始對(duì)他初始化這是老生常談了,TTL\WND\SENDBUF\TRM被初始化。之后完成了建立PCB這一重要任務(wù)。
2.2 Bind Socket
綁定端口到這個(gè)PCB中,這個(gè)簡(jiǎn)單系統(tǒng)自動(dòng)檢查一下端口是不是有效,如果有效就直接保存進(jìn)上面創(chuàng)建的PCB中,如果NULL則new_port()一個(gè)出來(lái)給他。new_port大有來(lái)頭,也就是著名的TCP_LOCAL_PORT_RANGE_END - TCP_LOCAL_PORT_RANGE_START。哈哈在這里了吧。
最后一步很重要,將這個(gè)PCB注冊(cè)到綁定表tcp_bound_pcbs。
注冊(cè)綁定表實(shí)際上僅僅是一個(gè)指針變量。他是如何做成表的呢?他并不是數(shù)組,怎么做個(gè)表,原來(lái)他是鏈表哦。哈哈明白了?
(npcb)->next = *pcbs; *(pcbs) = (npcb);
每一個(gè)PCB中有一個(gè)next ,第一步他先取得 tcp_bound_pcbs的地址并交給要注冊(cè)的PCB的(npcb)->next 中去,這樣,PCB就知道tcp_bound_pcbs的位置了,第二步,把當(dāng)前PCB的值給tcp_bound_pcbs。這樣當(dāng)前的 tcp_bound_pcbs總是指向最后注冊(cè)的一個(gè)PCB上,如果在來(lái)第三個(gè)第四個(gè)一次進(jìn)行,如果只有兩個(gè)那么當(dāng)前PCB的next剛好==NULL,這樣這個(gè)鏈表就OK了,只要netx不是NULL就表示有PCB在綁定的隊(duì)列中。
這很重要,因?yàn)閹缀跛械谋砣渴沁@樣的存儲(chǔ)方式。同時(shí)也是個(gè)坑。是個(gè)大坑。搞不好就被咬住。試想if pcbs==pcb->next。會(huì)有什么后果呢?網(wǎng)友們跑程序死機(jī)的問(wèn)題多多的線索之一啦!!! 現(xiàn)實(shí)中我也遇到了,否則也不可能去看他的結(jié)構(gòu)的。我的問(wèn)題是程序運(yùn)行一段時(shí)間后阻塞在下面
++tcp_timer_ctr
pcb = tcp_active_pcbs;
while(pcb != NULL) {<<<<<<<<<<<<<<-----------------------MARK1
if (pcb->last_timer != tcp_timer_ctr) {<<<<<<<<<<<<<<-----------------------MARK2
struct tcp_pcb *next;
pcb->last_timer = tcp_timer_ctr;
// do something .... send delayed ACKs
next = pcb->next;<<<<<<<<<<<<<<-----------------------MARK3
//do something refused
pcb = next;<<<<<<<<<<<<<<-----------------------MARK4
} 這是TCP的FAST TIME服務(wù)里面對(duì)LAST time的更新。表面上停留在一個(gè)和時(shí)間有關(guān)的項(xiàng)上,實(shí)際上和時(shí)間半毛錢關(guān)系都沒(méi)有,逐句看下去就發(fā)現(xiàn)第一tcp_timer_ctr在開始進(jìn)入的永遠(yuǎn)都比pcb->last_timer高一個(gè)數(shù),因?yàn)?+tcp_timer_ctr 的存在,所以問(wèn)題肯定不在這一上一定在下面,下面的是next;這個(gè)再配合while(pcb != NULL) 加上 pcb->last_timer = tcp_timer_ctr;他的退出的條件是是==NULL,所以咯.....跟蹤變量發(fā)現(xiàn)pcb->next正好等于當(dāng)前tcp_active_pcbs。。為了方便追蹤加了一行代碼 if(pcb==pcb->next) { // DEBUG
break; }
每次出異常都會(huì)進(jìn)入。驗(yàn)證了之前的推斷了。這證明鏈表被破壞了,他的next和當(dāng)前地址是一個(gè)地址。這是違背鏈表判斷條件的。后來(lái)查出來(lái)是第一個(gè)PCB沒(méi)有移除tcp_active_pcbs值卻釋放了PCB的內(nèi)存,這樣來(lái)了新連接又會(huì)分配了一個(gè)和他相同的地址,然后又被加入表中。在注冊(cè)中并沒(méi)有做相關(guān)的判斷保護(hù)。最后確定是應(yīng)用程序的問(wèn)題。導(dǎo)致的。
2.3 Listen Socket
如果對(duì)PCB進(jìn)行l(wèi)isten操作那么首先是再分一個(gè)PCB,把上一個(gè)PCB里面的內(nèi)容拷貝到這個(gè)剛創(chuàng)建的PCB中,之前不是綁定了么,然后解除 tcp_bound_pcbs,然后重新注冊(cè)到tcp_listen_pcbs中去。然后釋放掉最原始的那個(gè),返回當(dāng)前創(chuàng)建的,這樣PCB搖身一變。對(duì)用戶來(lái)說(shuō)是不知道的。實(shí)際上PCB的地址已經(jīng)改變,原來(lái)的PCB已經(jīng)釋放掉了,所以又返回了一個(gè)新的PCB地址。這個(gè)PCB被加入到listen表。另外PCB的狀態(tài)變成了LINSTEN 這很重要。
2.4 Accept Socket
accept實(shí)際上僅僅是個(gè)回調(diào)函數(shù)而已。如果忘記了,沒(méi)關(guān)系。系統(tǒng)早已經(jīng)考慮到忘記,早有一個(gè)空的被注冊(cè)進(jìn)去,如果這時(shí)候你accept一個(gè)僅僅是 do pcb->accept = accept 而已。
想要知道這個(gè)accept是干什么用的,就需要看TCP的狀態(tài)機(jī),看看這個(gè)在哪里被調(diào)用呢?這就引入另一個(gè)問(wèn)題。連接如何進(jìn)來(lái)?如何處理、accept 是干什么的?這就是下一點(diǎn)--連接
3、連接(TCP的3次握手)
那么要知道TCP數(shù)據(jù)怎么來(lái)就需要看IP層,找到IP層傳遞函數(shù),然后在TCPIN里查看數(shù)據(jù)如何被分配的。
話說(shuō)一個(gè)包含這個(gè)SYN的以太網(wǎng)幀進(jìn)入?yún)f(xié)議棧后被一系列的檢測(cè)過(guò)濾、直到他走到遍歷PCB是否在LINSTEN表中的時(shí)候(無(wú)疑PCB狀態(tài)是LINSTEN當(dāng)然在表中) 條件滿足
for(lpcb = tcp_listen_pcbs.listen_pcbs; lpcb != NULL; lpcb = lpcb->next)
遍歷鏈表啦。他一定在哪里等著,條件又滿足于是乎就會(huì)調(diào)用 tcp_listen_input(lpcb);這個(gè)函數(shù)。
又開始檢查SYN標(biāo)志,還是滿足。那么這就是第一次握手,
下一步就是動(dòng)態(tài)分配一個(gè)PCB,并做必要的填空題。
npcb = tcp_alloc(pcb->prio);
ip_addr_copy(npcb->local_ip, current_iphdr_dest); npcb->local_port = pcb->local_port; ip_addr_copy(npcb->remote_ip, current_iphdr_src); npcb->remote_port = tcphdr->src; npcb->state = SYN_RCVD; npcb->rcv_nxt = seqno + 1; npcb->rcv_ann_right_edge = npcb->rcv_nxt; npcb->snd_wnd = tcphdr->wnd; npcb->snd_wnd_max = tcphdr->wnd; npcb->ssthresh = npcb->snd_wnd;
注意現(xiàn)在狀態(tài)是SYN_RCVD,同時(shí)會(huì)發(fā)會(huì)SYNACK ,完成第二次握手 ,此時(shí)PCB被加入到活躍鏈表中。在這器件會(huì)等待最后一個(gè)SYN ACK 的ACK如果TCP_SYN_RCVD_TIMEOUT時(shí)間不來(lái)。怎么辦?果斷移除,連接失敗。正常是客戶端回應(yīng)了ACK 這樣就會(huì)沿著tcp_process進(jìn)入,當(dāng)前是SYN_RCVD收到ACK,也就是第三次握手了。
case SYN_RCVD: if (flags & TCP_ACK) { /* expected ACK number? */ if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) { u16_t old_cwnd; pcb->state = ESTABLISHED;
/* Call the accept function. */ TCP_EVENT_ACCEPT(pcb, ERR_OK, err); 顯然狀態(tài)變成了ESTABLISHED;到這里TCP 的三次握手全部建立。TCP進(jìn)入隨時(shí)可以收發(fā)數(shù)據(jù)的狀態(tài)。也就是正常狀態(tài)了。
OK 最后一個(gè)問(wèn)題就是accept,上面的代碼可見(jiàn),變換完畢狀態(tài)之后立馬TCP_EVENT_ACCEPT(pcb, ERR_OK, err); 這就是accept函數(shù)了。這個(gè)函數(shù)最后執(zhí)行自定義的注冊(cè)的 pcb->accept !!!
也就是每有一個(gè)客戶端連接過(guò)來(lái)并且3次握手成功后就會(huì)調(diào)用 pcb->accept 。于是乎他的作用就是告訴應(yīng)用程序,TCP連接建立了,你準(zhǔn)備吧。這就是在accpet函數(shù)中要執(zhí)行 tcp_recv(pcb, my_recv)的原因,這樣就可以收到來(lái)自客戶端的數(shù)據(jù)了
20151106
laowangtou
bt
|