久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 2857|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

TcpSever 和 三次握手

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:99624 發(fā)表于 2015-12-22 23:57 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
總是認(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



分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表
主站蜘蛛池模板: 天天插天天操 | 久久国产成人 | 黄色国产 | 欧美黑人狂野猛交老妇 | 久久6视频 | 日韩a视频 | 成人av一区二区三区 | 台湾佬久久 | 久久久久久久国产 | 国产色爽 | 九九精品在线 | 成人老司机| 成人av电影在线 | 国产黄色在线观看 | 亚洲精品久久久久久久久久久 | 国产精品久久久久久久粉嫩 | 久久大陆 | 日韩精品人成在线播放 | 国产精品久久久久久久久久久免费看 | 日韩精品一区二区三区中文在线 | 亚洲一区二区 | 亚洲一区二区久久 | 国产福利视频网站 | 国产视频在线一区二区 | 一区二区国产精品 | 欧美视频在线一区 | 欧美精品a∨在线观看不卡 国产精品久久国产精品 | 国内精品一区二区三区 | 国产区精品 | 97久久久久久久久 | 亚洲日本三级 | 91资源在线观看 | 青春草在线 | av一区二区在线观看 | 久草成人 | 亚洲午夜久久久 | 亚洲aⅴ | 久久精品视频一区二区 | 99在线免费观看视频 | 色播久久久| 91精品国产一二三 |