日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

告知你不為人知的 UDP:連接性和負(fù)載均衡

 

引言

說起網(wǎng)絡(luò) socket,大家自然會想到 TCP ,用的最多也是 TCP,UDP 在大家的印象中是作為 TCP 的補充而存在,是無連接、不可靠、無序、無流量控制的傳輸層協(xié)議。UDP的無連接性已經(jīng)深入人心,協(xié)議上的無連接性指的是一個 UDP 的 Endpoint1(IP,PORT),可以向多個 UDP 的 Endpointi ( IP , PORT )發(fā)送數(shù)據(jù)包,也可以接收來自多個 UDP 的 Endpointi(IP,PORT) 的數(shù)據(jù)包。實現(xiàn)上,考慮這樣一個特殊情況:UDP Client 在 Endpoint_C1只往 UDP Server 的 Endpoint_S1 發(fā)送數(shù)據(jù)包,并且只接收來自 Endpoint_S1 的數(shù)據(jù)包,把 UDP 通信雙方都固定下來,這樣不就形成一條單向的虛”連接”了么?

1. UDP的”連接性”

估計很多同學(xué)認(rèn)為UDP的連接性只是將UDP通信雙方都固定下來了,一對一只是多對多的一個特例而已,這樣UDP連接不連接到無所謂了。果真如此嗎?其實不然,UDP的連接性可以帶來以下兩個好處:

1.1 高效率、低消耗

我們知道linux系統(tǒng)有用戶空間(用戶態(tài))和內(nèi)核空間(內(nèi)核態(tài))之分,對于x86處理器以及大多數(shù)其它處理器,用戶空間和內(nèi)核空間之前的切換是比較耗時(涉及到上下文的保存和恢復(fù),一般3種情況下會發(fā)生用戶態(tài)到內(nèi)核態(tài)的切換:發(fā)生系統(tǒng)調(diào)用時、產(chǎn)生異常時、中斷時)。那么對于一個高性能的服務(wù)應(yīng)該減少頻繁不必要的上下文切換,如果切換無法避免,那么盡量減少用戶空間和內(nèi)核空間的數(shù)據(jù)交換,減少數(shù)據(jù)拷貝。熟悉socket編程的同學(xué)對下面幾個系統(tǒng)調(diào)用應(yīng)該比較熟悉了,由于UDP是基于用戶數(shù)據(jù)報的,只要數(shù)據(jù)包準(zhǔn)備好就應(yīng)該調(diào)用一次send或sendto進(jìn)行發(fā)包,當(dāng)然包的大小完全由應(yīng)用層邏輯決定的。

細(xì)看兩個系統(tǒng)調(diào)用的參數(shù)便知道,sendto比send的參數(shù)多2個,這就意味著每次系統(tǒng)調(diào)用都要多拷貝一些數(shù)據(jù)到內(nèi)核空間,同時,參數(shù)到內(nèi)核空間后,內(nèi)核還需要初始化一些臨時的數(shù)據(jù)結(jié)構(gòu)來存儲這些參數(shù)值(主要是對端Endpoint_S的地址信息),在數(shù)據(jù)包發(fā)出去后,內(nèi)核還需要在合適的時候釋放這些臨時的數(shù)據(jù)結(jié)構(gòu)。進(jìn)行UDP通信的時候,如果首先調(diào)用connect綁定對端Endpoint_S的后,那么就可以直接調(diào)用send來給對端Endpoint_S發(fā)送UDP數(shù)據(jù)包了。用戶在connect之后,內(nèi)核會永久維護一個存儲對端Endpoint_S的地址信息的數(shù)據(jù)結(jié)構(gòu),內(nèi)核不再需要分配/刪除這些數(shù)據(jù)結(jié)構(gòu),只需要查找就可以了,從而減少了數(shù)據(jù)的拷貝。這樣對于connect方而言,該UDP通信在內(nèi)核已經(jīng)維護這一個“連接”了,那么在通信的整個過程中,內(nèi)核都能隨時追蹤到這個“連接”。

int connect(int socket, const struct sockaddr *address,
              socklen_t address_len);             
ssize_t send(int socket, const void *buffer, size_t length, 
              int flags);
ssize_t sendto(int socket, const void *message, 
              size_t length,              
int flags, const struct sockaddr *dest_addr,
              socklen_t dest_len);
ssize_t recv(int socket, void *buffer, size_t length,
              int flags);
ssize_t recvfrom(int socket, void *restrict buffer, 
              size_t length,              
int flags, struct sockaddr *restrict address,
              socklen_t *restrict address_len);

1.2 錯誤提示

相信大家寫 UDP Socket 程序的時候,有時候在第一次調(diào)用 sendto 給一個 unconnected UDP socket 發(fā)送 UDP 數(shù)據(jù)包時,接下來調(diào)用 recvfrom() 或繼續(xù)調(diào)sendto的時候會返回一個 ECONNREFUSED 錯誤。對于一個無連接的 UDP 是不會返回這個錯誤的,之所以會返回這個錯誤,是因為你明確調(diào)用了 connect 去連接遠(yuǎn)端的 Endpoint_S 了。那么這個錯誤是怎么產(chǎn)生的呢?沒有調(diào)用 connect 的 UDP Socket 為什么無法返回這個錯誤呢?

當(dāng)一個 UDP socket 去 connect 一個遠(yuǎn)端 Endpoint_S 時,并沒有發(fā)送任何的數(shù)據(jù)包,其效果僅僅是在本地建立了一個五元組映射,對應(yīng)到一個對端,該映射的作用正是為了和 UDP 帶外的 ICMP 控制通道捆綁在一起,使得 UDP socket 的接口含義更加豐滿。這樣內(nèi)核協(xié)議棧就維護了一個從源到目的地的單向連接,當(dāng)下層有ICMP(對于非IP協(xié)議,可以是其它機制)錯誤信息返回時,內(nèi)核協(xié)議棧就能夠準(zhǔn)確知道該錯誤是由哪個用戶socket產(chǎn)生的,這樣就能準(zhǔn)確將錯誤轉(zhuǎn)發(fā)給上層應(yīng)用了。對于下層是IP協(xié)議的時候,ICMP 錯誤信息返回時,ICMP 的包內(nèi)容就是出錯的那個原始數(shù)據(jù)包,根據(jù)這個原始數(shù)據(jù)包可以找出一個五元組,根據(jù)該五元組就可以對應(yīng)到一個本地的connect過的UDP socket,進(jìn)而把錯誤消息傳輸給該 socket,應(yīng)用程序在調(diào)用socket接口函數(shù)的時候,就可以得到該錯誤消息了。

對于一個無“連接”的UDP,sendto系統(tǒng)調(diào)用后,內(nèi)核在將數(shù)據(jù)包發(fā)送出去后,就釋放了存儲對端Endpoint_S的地址等信息的數(shù)據(jù)結(jié)構(gòu)了,這樣在下層的協(xié)議有錯誤返回的時候,內(nèi)核已經(jīng)無法追蹤到源socket了。

這里有個注意點要說明一下,由于UDP和下層協(xié)議都是不可靠的協(xié)議,所以,不能總是指望能夠收到遠(yuǎn)端回復(fù)的ICMP包,例如:中間的一個節(jié)點或本機禁掉了ICMP,socket api調(diào)用就無法捕獲這些錯誤了。

2 UDP的負(fù)載均衡

在多核(多CPU)的服務(wù)器中,為了充分利用機器CPU資源,TCP服務(wù)器大多采用accept/fork模式,TCP服務(wù)的MPM機制(multi processing module),不管是預(yù)先建立進(jìn)程池,還是每到一個連接創(chuàng)建新線程/進(jìn)程,總體都是源于accept/fork的變體。然而對于UDP卻無法很好的采用PMP機制,由于UDP的無連接性、無序性,它沒有通信對端的信息,不知道一個數(shù)據(jù)包的前置和后續(xù),它沒有很好的辦法知道,還有沒后續(xù)的數(shù)據(jù)包以及如果有的話,過多久才會來,會來多久,因此UDP無法為其預(yù)先分配資源。

2.1 端口重用SO_REUSEADDR、SO_REUSEPORT

要進(jìn)行多處理,就免不了要在相同的地址端口上處理數(shù)據(jù),SO_REUSEADDR允許端口的重用,只要確保四元組的唯一性即可。對于TCP,在bind的時候所有可能產(chǎn)生四元組不唯一的bind都會被禁止(于是,ip相同的情況下,TCP套接字處于TIME_WAIT狀態(tài)下的socket,才可以重復(fù)綁定使用);對于connect,由于通信兩端中的本端已經(jīng)明確了,那么只允許connect從來沒connect過的對端(在明確不會破壞四元組唯一性的connect才允許發(fā)送SYN包);對于監(jiān)聽listen端,四元組的唯一性油connect端保證就OK了。

TCP通過連接來保證四元組的唯一性,一個connect請求過來,accept進(jìn)程accept完這個請求后(當(dāng)然不一定要單獨accept進(jìn)程),就可以分配socket資源來標(biāo)識這個連接,接著就可以分發(fā)給相應(yīng)的worker進(jìn)程去處理該連接后續(xù)的事情了。這樣就可以在多核服務(wù)器中,同時有多個worker進(jìn)程來同時處理多個并發(fā)請求,從而達(dá)到負(fù)載均衡,CPU資源能夠被充分利用。

UDP的無連接狀態(tài)(沒有已有對端的信息),使得UDP沒有一個有效的辦法來判斷四元組是否沖突,于是對于新來的請求,UDP無法進(jìn)行資源的預(yù)分配,于是多處理模式難以進(jìn)行,最終只能“守株待兔“,UDP按照固定的算法查找目標(biāo)UDP socket,這樣每次查到的都是UDP socket列表固定位置的socket。UDP只是簡單基于目的IP和目的端口來進(jìn)行查找,這樣在一個服務(wù)器上多個進(jìn)程內(nèi)創(chuàng)建多個綁定相同IP地址(SO_REUSEADDR),相同端口的UDP socket,那么你會發(fā)現(xiàn),只有最后一個創(chuàng)建的socket會接收到數(shù)據(jù),其它的都是默默地等待,孤獨地等待永遠(yuǎn)也收不到UDP數(shù)據(jù)。UDP這種只能單進(jìn)程、單處理的方式將要破滅UDP高效的神話,你在一個多核的服務(wù)器上運行這樣的UDP程序,會發(fā)現(xiàn)只有一個核在忙,其他CPU核心處于空閑的狀態(tài)。創(chuàng)建多個綁定相同IP地址,相同端口的UDP程序,只會起到容災(zāi)備份的作用,不會起到負(fù)載均衡的作用。

要實現(xiàn)多處理,那么就要改變UDP Socket查找的考慮因素,對于調(diào)用了connect的UDP Client而言,由于其具有了“連接”性,通信雙方都固定下來了,那么內(nèi)核就可以根據(jù)4元組完全匹配的原則來匹配。于是對于不同的通信對端,可以查找到不同的UDP Socket從而實現(xiàn)多處理。而對于server端,在使用SO_REUSEPORT選項(linux 3.9以上內(nèi)核),這樣在進(jìn)行UDP socket查找的時候,源IP地址和源端口也參與進(jìn)來了,內(nèi)核查找算法可以保證:

  • [1] 固定的四元組的UDP數(shù)據(jù)包總是查找到同一個UDP Socket;
  • [2] 不同的四元組的UDP數(shù)據(jù)包可能會查找到不同的UDP Socket。

這樣對于不同client發(fā)來的數(shù)據(jù)包就能查找到不同的UDP socket從而實現(xiàn)多處理。這樣看來,似乎采用SO_REUSEADDR、SO_REUSEPORT這兩個socket選項并利用內(nèi)核的socket查找算法,我們在多核CPU服務(wù)器上多個進(jìn)程內(nèi)創(chuàng)建多個綁定相同端口,相同IP地址的UDP socket就能做到負(fù)載均衡充分利用多核CPU資源了。然而事情遠(yuǎn)沒這么順利、簡單。

2.2 UDP Socket列表變化問題

通過上面我們知道,在采用SO_REUSEADDR、SO_REUSEPORT這兩個socket選項后,內(nèi)核會根據(jù)UDP數(shù)據(jù)包的4元組來查找本機上的所有相同目的IP地址,相同目的端口的socket中的一個socket的位置,然后以這個位置上的socket作為接收數(shù)據(jù)的socket。那么要確保來至同一個Client Endpoint的UDP數(shù)據(jù)包總是被同一個socket來處理,就需要保證整個socket鏈表的socket所處的位置不能改變,然而,如果socket鏈表中間的某個socket掛了的話,就會造成socket鏈表重新排序,這樣會引發(fā)問題。于是基本的解決方案是在整個服務(wù)過程中不能關(guān)閉UDP socket(當(dāng)然也可以全部UDP socket都close掉,從新創(chuàng)建一批新的)。要保證這一點,我們需要所有的UDP socket的創(chuàng)建和關(guān)閉都由一個master進(jìn)行來管理,worker進(jìn)程只是負(fù)責(zé)處理對于的網(wǎng)絡(luò)IO任務(wù),為此我們需要socket在創(chuàng)建的時候要帶有CLOEXEC標(biāo)志(SOCK_CLOEXEC)。

2.3 UDP和Epoll結(jié)合 - UDP的Accept模型

到此,為了充分利用多核CPU資源,進(jìn)行UDP的多處理,我們會預(yù)先創(chuàng)建多個進(jìn)程,每個進(jìn)程都創(chuàng)建一個或多個綁定相同端口,相同IP地址(SO_REUSEADDR、SO_REUSEPORT)的UDP socket,這樣利用內(nèi)核的UDP socket查找算法來達(dá)到UDP的多進(jìn)程負(fù)載均衡。然而,這完全依賴于Linux內(nèi)核處理UDP socket查找時的一個算法,我們不能保證其它的系統(tǒng)或者未來的Linux內(nèi)核不會改變算法的行為;同時,算法的查找能否做到比較好的均勻分布到不同的UDP socket,(每個處理進(jìn)程只處理自己初始化時候創(chuàng)建的那些UDP socket)負(fù)載是否均衡是個問題。于是,我們多么想給UPD建立一個accept模型,按需分配UDP socket來處理。

在高性能Server編程中,對于TCP Server而已有比較成熟的解決方案,TCP天然的連接性可以充分利用epoll等高性能event機制,采用多路復(fù)用、異步處理的方式,哪個worker進(jìn)程空閑就去accept連接請求來處理,這樣就可以達(dá)到比較高的并發(fā),可以極限利用CPU資源。然而對于UDP server而言,由于整個Svr就一個UDP socket,接收并響應(yīng)所有的client請求,于是也就不存在什么多路復(fù)用的問題了。UDP svr無法充分利用epoll的高性能event機制的主要原因是,UDP svr只有一個UDP socket來接收和響應(yīng)所有client的請求。然而如果能夠為每個client都創(chuàng)建一個socket并虛擬一個“連接”與之對應(yīng),這樣不就可以充分利用內(nèi)核UDP層的socket查找結(jié)果和epoll的通知機制了么。server端具體過程如下:

  1. UDP svr創(chuàng)建UDP socket fd,設(shè)置socket為REUSEADDR和REUSEPORT、同時bind本地地址local_addr listen_fd = socket(PF_INET, SOCK_DGRAM, 0) setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt)) setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) bind(listen_fd, (struct sockaddr * ) &local_addr, sizeof(struct sockaddr))
  2. 創(chuàng)建epoll fd,并將listen_fd放到epoll中 并監(jiān)聽其可讀事件 epoll_fd = epoll_create(1000); ep_event.events = EPOLLIN|EPOLLET; ep_event.data.fd = listen_fd; epoll_ctl(epoll_fd , EPOLL_CTL_ADD, listen_fd, &ep_event) in_fds = epoll_wait(epoll_fd, in_events, 1000, -1);
  3. epoll_wait返回時,如果epoll_wait返回的事件fd是listen_fd,調(diào)用recvfrom接收client第一個UDP包并根據(jù)recvfrom返回的client地址, 創(chuàng)建一個新的socket(new_fd)與之對應(yīng),設(shè)置new_fd為REUSEADDR和REUSEPORT、同時bind本地地址local_addr,然后connect上recvfrom返回的client地址 recvfrom(listen_fd, buf, sizeof(buf), 0, (struct sockaddr )&client_addr, &client_len) new_fd = socket(PF_INET, SOCK_DGRAM, 0) setsockopt(new_fd , SOL_SOCKET, SO_REUSEADDR, &reuse,sizeof(reuse)) setsockopt(new_fd , SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) bind(new_fd , (struct sockaddr ) &local_addr, sizeof(struct sockaddr)); connect(new_fd , (struct sockaddr * ) &client_addr, sizeof(struct sockaddr)
  4. 將新創(chuàng)建的new_fd加入到epoll中并監(jiān)聽其可讀等事件 client_ev.events = EPOLLIN; client_ev.data.fd = new_fd ; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd , &client_ev)
  5. 當(dāng)epoll_wait返回時,如果epoll_wait返回的事件fd是new_fd 那么就可以調(diào)用recvfrom來接收特定client的UDP包了 recvfrom(new_fd , recvbuf, sizeof(recvbuf), 0, (struct sockaddr * )&client_addr, &client_len)

通過上面的步驟,這樣 UDP svr 就能充分利用 epoll 的事件通知機制了。第一次收到一個新的 client 的 UDP 數(shù)據(jù)包,就創(chuàng)建一個新的UDP socket和這個client對應(yīng),這樣接下來的數(shù)據(jù)交互和事件通知都能準(zhǔn)確投遞到這個新的UDP socket fd了。

這里的UPD和Epoll結(jié)合方案,有以下幾個注意點:

  • [1] client要使用固定的ip和端口和server端通信,也就是client需要bind本地local address。 如果client沒有bind本地local address,那么在發(fā)送UDP數(shù)據(jù)包的時候,可能是不同的Port了,這樣如果server 端的new_fd connect的是client的Port_CA端口,那么當(dāng)Client的Port_CB端口的UDP數(shù)據(jù)包來到server時,內(nèi)核不會投遞到new_fd,相反是投遞到listen_fd。由于需要bind和listen fd一樣的IP地址和端口,因此SO_REUSEADDR和SO_REUSEPORT是必須的。
  • [2] 要小心處理上面步驟3中connect返回前,Client已經(jīng)有多個UDP包到達(dá)Server端的情況。 如果server沒處理好這個情況,在connect返回前,有2個UDP包到達(dá)server端了,這樣server會new出兩個new_fd1和new_fd2分別connect到client,那么后續(xù)的client的UDP到達(dá)server的時候,內(nèi)核會投遞UDP包給new_fd1和new_fd2中的一個

上面的UDP和Epoll結(jié)合的accept模型有個不好處理的小尾巴(也就是上面的注意點[2]),這個小尾巴的存在其本質(zhì)是UDP和4元組沒有必然的對應(yīng)關(guān)系,也就是UDP的無連接性。

2.3 UDP Fork 模型 - UDP accept模型之按需建立UDP處理進(jìn)程

為了充分利用多核 CPU (為簡化討論,不妨假設(shè)為8核),理想情況下,同時有8個工作進(jìn)程在同時工作處理請求。于是我們會初始化8個綁定相同端口,相同IP地址(SO_REUSEADDR、SO_REUSEPORT)的 UDP socket ,接下來就靠內(nèi)核的查找算法來達(dá)到client請求的負(fù)載均衡了。由于內(nèi)核查找算法是固定的,于是,無形中所有的client被劃分為8類,類型1的所有client請求全部被路由到工作進(jìn)程1的UDP socket由工作進(jìn)程1來處理,同樣類型2的client的請求也全部被工作進(jìn)程2來處理。這樣的缺陷是明顯的,比較容易造成短時間的負(fù)載極端不均衡。

一般情況下,如果一個 UDP 包能夠標(biāo)識一個請求,那么簡單的解決方案是每個 UDP socket n 的工作進(jìn)程 n,自行 fork 出多個子進(jìn)程來處理類型n的 client 的請求。這樣每個子進(jìn)程都直接 recvfrom 就 OK 了,拿到 UDP 請求包就處理,拿不到就阻塞。

然而,如果一個請求需要多個 UDP 包來標(biāo)識的情況下,事情就沒那么簡單了,我們需要將同一個 client 的所有 UDP 包都路由到同一個工作子進(jìn)程。為了簡化討論,我們將注意力集中在都是類型n的多個client請求UDP數(shù)據(jù)包到來的時候,我們怎么處理的問題,不同類型client的數(shù)據(jù)包路由問題交給內(nèi)核了。這樣,我們需要一個master進(jìn)程來監(jiān)聽UDP socket的可讀事件,master進(jìn)程監(jiān)聽到可讀事件,就采用MSG_PEEK選項來recvfrom數(shù)據(jù)包,如果發(fā)現(xiàn)是新的Endpoit(ip、port)Client的UDP包,那么就fork一個新的進(jìn)行來處理該Endpoit的請求。具體如下:

  • [1] master進(jìn)程監(jiān)聽udp_socket_fd的可讀事件:pfd.fd = udp_socket_fd;pfd.events = POLLIN; poll(pfd, 1, -1); 當(dāng)可讀事件到來,pfd.revents & POLLIN 為true。探測一下到來的UDP包是否是新的client的UDP包:recvfrom(pfd.fd, buf, MAXSIZE, MSG_PEEK, (struct sockaddr *)pclientaddr, &addrlen);查找一下worker_list是否為該client創(chuàng)建過worker進(jìn)程了。
  • [2] 如果沒有查找到,就fork()處理進(jìn)程來處理該請求,并將該client信息記錄到worker_list中。查找到,那么continue,回到步驟[1]
  • [3] 每個worker子進(jìn)程,保存自己需要處理的client信息pclientaddr。worker進(jìn)程同樣也監(jiān)聽udp_socket_fd的可讀事件。poll(pfd, 1, -1);當(dāng)可讀事件到來,pfd.revents & POLLIN 為true。探測一下到來的UDP包是否是本進(jìn)程需要處理的client的UDP包:recvfrom(pfd.fd, buf, MAXSIZE, MSG_PEEK, (struct sockaddr * )pclientaddr_2, &addrlen); 比較一下pclientaddr和pclientaddr_2是否一致。

該fork模型很別扭,過多的探測行為,一個數(shù)據(jù)包來了,會”驚群”喚醒所有worker子進(jìn)程,大家都去PEEK一把,最后只有一個worker進(jìn)程能夠取出UDP包來處理。同時到來的數(shù)據(jù)包只能排隊被取出。更為嚴(yán)重的是,由于recvfrom的排他喚醒,可能會造成死鎖。考慮下面一個場景:

假設(shè)有 worker1、worker2、worker3、和 master 共四個進(jìn)程都阻塞在 poll 調(diào)用上,client1 的一個新的 UDP 包過來,這個時候,四個進(jìn)程會被同時喚醒,worker1比較神速,趕在其他進(jìn)程前將 UPD 包取走了( worker1可以處理 client1的 UDP 包),于是其他三個進(jìn)程的 recvfrom 撲空,它們 worker2、worker3、和 master 按序全部阻塞在 recvfrom 上睡眠( worker2、worker3 排在 master 前面先睡眠的)。這個時候,一個新 client4 的 UDP 包packet4到來,(由于recvfrom的排他喚醒)這個時候只有worker2會從recvfrom的睡眠中醒來,然而worker而卻不能處理該請求UDP包。如果沒有新UDP包到來,那么packet4一直留在內(nèi)核中,死鎖了。之所以recv是排他的,是為了避免“承諾給一個進(jìn)程”的數(shù)據(jù)被其他進(jìn)程取走了。

通過上面的討論,不管采用什么手段,UDP的accept模型總是那么別扭,總有一些無法自然處理的小尾巴。UDP的多路負(fù)載均衡方案不通用,不自然,其本因在于UPD的無連接性、無序性(無法標(biāo)識數(shù)據(jù)的前續(xù)后繼)。我們不知道 client 還在不在,于是難于決策虛擬的”連接”何時終止,以及何時結(jié)束掉fork出來的worker子進(jìn)程(我們不能無限 fork 吧)。于是,在沒有好的決策因素的時候,超時似乎是一個比較好選擇,畢竟當(dāng)所有的裁決手段都失效的時候,一切都要靠時間來沖淡。

分享到:
標(biāo)簽:UDP
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定