天天看點

好教程推薦系列:TCP面試常見題-張小方的知乎Live-輕松搞定技術面試中常見的網絡通信問題(二)

10.select函數可以檢測網絡異常嗎?

答:不可以。當網絡異常時,select函數可以檢測到可讀事件,這時候用read函數讀取資料,會傳回0.

詳情見UNP 卷1 6.3.1 select描述符就緒條件 第131頁和132頁

12. epoll的水準模式LT和邊緣模式ET

答:預設是LT。

水準模式指的是低電平到低電平,或者高電平到高電平。

邊緣模式指的是低電平到高電平,或者高電平到低電平。

水準模式:如果epoll_wait檢測到可讀事件,可以一次性把資料讀完,也可以分多次讀完。

邊緣模式:如果epoll_wait檢測到可讀事件,必須一次性把資料讀完,否則如果分兩次讀,第一次讀部分,第二次再讀時,沒有可讀觸發信号了,讀不到了。隻能使用非阻塞的網絡模型。

參考部落格:

http://blog.csdn.net/analogous_love/article/details/60761528

用于windows或linux水準模式下收取資料,這種情況下收取的資料可以小于指定大小,總之一次能收到多少是多少:

bool TcpSession::Recv()  

{  

   //每次隻收取256個位元組  

   char buff[256];  

   //memset(buff, 0, sizeof(buff));  

   int nRecv = ::recv(clientfd_, buff, 256, 0);  

   if (nRecv == 0)  

       return false;  

   inputBuffer_.add(buff, (size_t)nRecv);  

   return true;  

}  

如果是linux epoll邊緣模式(ET),則一定要一次性收完:

bool TcpSession::RecvEtMode()  

   while (true)  

   {  

       //memset(buff, 0, sizeof(buff));  

       int nRecv = ::recv(clientfd_, buff, 256, 0);  

       if (nRecv == -1)  

       {  

           if (errno == EWOULDBLOCK || errno == EINTR)  

               return true;  

           return false;  

       }  

       //對端關閉了socket  

       else if (nRecv == 0)  

      inputBuffer_.add(buff, (size_t)nRecv);  

   }  

13. 如何将socket設定成非阻塞的(建立時設定與建立完成後設定),非阻塞socket與阻塞的socket在收發資料上的差別

14. send/recv(read/write)傳回值大于0、等于0、小于0的差別

答:

recv:

阻塞與非阻塞recv傳回值沒有區分,都是 <0:出錯,=0:連接配接關閉,>0接收到資料大小,

特别:非阻塞模式下傳回 值 <0時并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況 下認為連接配接是正常的,繼續接收。

隻是阻塞模式下recv會阻塞着接收資料,非阻塞模式下如果沒有資料會傳回,不會阻塞着讀,是以需要 循環讀取。

write:

阻塞與非阻塞write傳回值沒有區分,都是 <0:出錯,=0:連接配接關閉,>0發送資料大小,

特别:非阻塞模式下傳回值 <0時并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認為連接配接是正常的, 繼續發送。

隻是阻塞模式下write會阻塞着發送資料,非阻塞模式下如果暫時無法發送資料會傳回,不會阻塞着 write,是以需要循環發送。

read:

阻塞與非阻塞read傳回值沒有區分,都是 <0:出錯,=0:連接配接關閉,>0接收到資料大小,

隻是阻塞模式下read會阻塞着接收資料,非阻塞模式下如果沒有資料會傳回,不會阻塞着讀,是以需要 循環讀取。

send:

阻塞與非阻塞send傳回值沒有區分,都是 <0:出錯,=0:連接配接關閉,>0發送資料大小,

隻是阻塞模式下send會阻塞着發送資料,非阻塞模式下如果暫時無法發送資料會傳回,不會阻塞着 send,是以需要循環發送。

15.如何編寫正确的收資料代碼與發資料代碼

答:都需要考慮緩沖區的設計,發資料如果失敗先緩存起來,等待下一次的機會再發。

bool TcpSession::Send()  

       int n = ::send(clientfd_, buffer_, buffer_.length(), 0);  

       if (n == -1)  

           //tcp視窗容量不夠, 暫且發不出去,下次再發  

           if (errno == EWOULDBLOCK)  

               break;  

           //被信号中斷,繼續發送  

           else if (errno == EINTR)  

               continue;  

       //對端關閉了連接配接  

       else if (n == 0)  

       buffer_.erase(n);  

       //全部發送完畢  

       if (buffer_.length() == 0)  

           break;  

tcp是流協定,應用層要自己來區分包的邊界,就是加標頭包尾。但是tcp有個滑動視窗,假如是10,我第一個資料包占了6,然後在伺服器還沒讀取的時候,我發送第二個,也是6大小,此時會先隻發4過去,剩下的6-4=2就得等服務端騰出空間後并提示後再發了.

16.發送資料緩沖區與接收資料緩沖區如何設計

答:參考學習muduo的buffer設計

17.socket選項SO_SNDTIMEO和SO_RCVTIMEO

答:阻塞模式時需要設定逾時時間,否則會卡死。

18.socket選項TCP_NODELAY

答:一般來說,應用層send函數不會立刻把資料發出去,而是先給到網卡緩沖區。網卡緩沖區需要等待資料積累到一定量之後才會發送資料,這樣會導緻一定的延遲。

預設情況下,發送資料采用Nagle算法。這樣雖然提高了網絡吞吐量,但是實時性卻降低了,在一些互動性很強的應用程式來說是不允許的,使用TCP_NODELAY選項可以禁止Nagle算法,避免連續發包出現延遲,這對低延遲網絡服務很重要。 此時,應用程式向核心遞交的每個資料包都會立即發送出去。需要注意的是,雖然禁止了Nagle 算法,但網絡的傳輸仍然受到TCP确認延遲機制的影響。

19.socket選項SO_REUSEADDR和SO_REUSEPORT(Windows平台與linux平台的差別)

答:說白了當伺服器程序關閉時,想立刻再複用原來的ip和端口需要等待2MSL的時間。舉個例子,伺服器監聽了127.0.0.1和8001端口,如果此時結束掉程序,再立刻重新開機,是不可以再監聽成功的。因為TCP四次揮手最後一步TIME_WAIT需要等待應答,如果等不到需要重連。

MSL的時間一般是1min~4min不等。MSL是資料包的最大存活時間,最後一步的ack需要考慮去和回,是以周期是2*MSL。

Linux是所有程序在2MSL的時間内不能複用剛才使用的ip和port,bind會失敗;Windows是除了本程序可以,其他程序不可以。作業系統這麼設計的。

結論:一般為了友善重新開機伺服器或調試,會設定這兩個選項,REUSE就是複用的意思,讓程序立刻可以複用位址和端口。

20.socket選項SO_LINGER

答:當調用closesocket關閉套接字時,SO_LINGER将決定系統如何處理殘存在套接字發送隊列中的資料。處理方式無非兩種:丢棄或者将資料繼續發送至對端,優雅關閉連接配接。事實上,SO_LINGER并不被推薦使用,大多數情況下我們推薦使用預設的關閉方式(即下方表格中的第一種情況)。

21.shutdown與優雅關閉

答:socket 多程序中的shutdown, close使用

當所有的資料操作結束以後,你可以調用close()函數來釋放該socket,進而停止在該socket上的任何資料操作:close(sockfd);

你也可以調用shutdown()函數來關閉該socket。該函數允許你隻停止在某個方向上的資料傳輸,而一個方向上的資料傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受資料,直至讀入所有資料。

int shutdown(int sockfd,int how);

Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:

   SHUT_RD:關閉連接配接的讀端。也就是該套接字不再接受資料,任何目前在套接字接受緩沖區的資料将被丢棄。程序将不能對該套接字發出任何讀操作。對TCP套接字該調用之後接受到的任何資料将被确認然後無聲的丢棄掉。

   SHUT_WR:關閉連接配接的寫端,程序不能在對此套接字發出寫操作。

   SHUT_RDWR:相當于調用shutdown兩次:首先是以SHUT_RD,然後以SHUT_WR。

伺服器如果要主動關閉連接配接,可以這麼執行:先關本地“寫”端,等對方關閉後,再關本地“讀”端。

伺服器如果要被動關閉連接配接,可以這麼執行:當read函數傳回值是0時,先關本地“寫”端,等對方關閉後,再關本地“讀”端。

23.socket選項SO_KEEPALIVE

答:一般來說不推薦使用預設的心跳機制,預設是2小時。預設心跳有兩個缺陷:

1、貌似設定之後會影響整個作業系統所有應用層的心跳時間;

2、每2小時發一次心跳,有時候會造成流量浪費。比如應用層如果有正常資料互動,不需要發心跳。

具體實作可以參考redis源碼anet.c裡的anetKeepAlive函數。

24.關于錯誤碼EINTR

答:EINTR是linux中函數的傳回狀态,在不同的函數中意義不同。表示某種阻塞的操作,被接收到的信号中斷,造成的一種錯誤傳回值。

write

表示:由于信号中斷,沒寫成功任何資料。

read

表示:由于信号中斷,沒讀到任何資料。

sem_wait

函數調用被信号處理函數中斷。

recv

由于信号中斷傳回,沒有任何資料可用。

25.如何解決tcp粘包問題

答:通過應用層自定義協定來解決

1、固定長度的包

2、每個包以"\r\n"結尾

3、定義結構體,包含固定標頭,包體長度等

26.信号SIGPIPE與EPIPE錯誤碼

答:在linux下寫socket的程式的時候,如果伺服器嘗試send到一個disconnected socket上,就會讓底層抛出一個SIGPIPE信号。 這個信号的預設處理方法是退出程序,大多數時候這都不是我們期望的。也就是說,當伺服器繁忙,沒有及時處理用戶端斷開連接配接的事件,就有可能出現在連接配接斷開之後繼續發送資料的情況,如果對方斷開而本地繼續寫入的話,就會造成伺服器程序意外退出。

根據信号的預設處理規則SIGPIPE信号的預設執行動作是terminate(終止、退出),是以client會退出。若不想用戶端退出可以把 SIGPIPE設為SIG_IGN 如:signal(SIGPIPE, SIG_IGN); 這時SIGPIPE交給了系統處理。 伺服器采用了fork的話,要收集垃圾程序,防止僵屍程序的産生,可以這樣處理: signal(SIGCHLD,SIG_IGN); 交給系統init去回收。 這裡子程序就不會産生僵屍程序了。

27.gethostbyname阻塞與錯誤碼擷取問題

答:Unix/Linux下的gethostbyname函數常用來向DNS查詢一個域名的IP位址。 由于DNS的遞歸查詢,常常會發生gethostbyname函數在查詢一個域名時嚴重逾時。而該函數又不能像connect和read等函數那樣通過setsockopt或者select函數那樣設定逾時時間,是以常常成為程式的瓶頸。有人提出一種解決辦法是用alarm設定定時信号,如果逾時就用setjmp和longjmp跳過gethostbyname函數(這種方式我沒有試過,不知道具體效果如何)。

gethostbyname确實是阻塞的,但應該可以設定一個time_out免得DNS Server出問題時老是執行,關于設定Time_out,參閱一下code:

int timeout = TIMEOUT_VALUE;

int err;

SOCKET s;

s = socket( ... );

err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout).

在使用 gethostbyname() 的時候,你不能用perror() 列印錯誤資訊 (因為 errno 沒有使用),你應該調用 herror()。herror()函數簽名如下:

void herror(const char *s);

舉例如下:參考部落格

http://blog.csdn.net/analogous_love/article/details/53433994

bool Connect(const char* pszIp, int nPort)  

   struct hostent* pHostent = NULL;  

   char* host = NULL;  

   struct sockaddr_in addrSrv;  

   memset(&addrSrv, 0, sizeof(addrSrv));  

   addrSrv.sin_addr.s_addr = ::inet_addr(pszIp);  

   if (addrSrv.sin_addr.s_addr == INADDR_NONE)  

       pHostent = ::gethostbyname(pszIp);  

       if (pHostent != NULL)  

           host = inet_ntoa(*((struct in_addr *)pHostent->h_addr));  

           std::cout << pszIp << "=" << host << std::endl;  

       else  

           herror("gethostbyname error");  

           std::cout << std::endl;  

   else  

       host = (char*)pszIp;  

   if (Socket.Connect(host, nPort))  

       return true;  

   std::cout << "Unable to connect to server " << pszIp << ":" << nPort << std::endl;  

   return false;  

}

28.心跳包的設計技巧(保活心跳包與業務心跳包)

http://blog.csdn.net/analogous_love/article/details/78388187

29.用戶端斷線重連機制如何設計

答:用戶端先2s連接配接一次伺服器,如果失敗,再4s連接配接一次,如果失敗,再8s連接配接一次,如果失敗再16s連接配接一次。。。。。

補充一個情況,當網絡狀況突變時,立刻連接配接一次。例如,使用者從地鐵站出來,手機信号滿格了,此時手機app立刻連接配接伺服器。

《Linux多線程服務端程式設計:使用muduo C++網絡庫》P333有這麼描述:

用戶端連接配接斷開後初次重試的延遲應該有随機性,比如說服務端奔潰,它所有的客戶連接配接同時斷開,然後0.5s之後再次發起連接配接,這樣既可能造成SYN丢包,也可能給伺服器帶來短期大負載,影響其服務品質。是以每個用戶端應該等待一段随機的時間(0.5~2s),再重試,避免擁塞。

30.如何檢測對端已經關閉socket

答:根據ERRNO和recv結果進行判斷

在UNIX/LINUX下,非阻塞模式SOCKET可以采用recv+MSG_PEEK的方式進行判斷,其中MSG_PEEK保證了僅僅進行狀态判斷,而不影響資料接收

對于主動關閉的SOCKET, recv傳回-1,而且errno被置為9(#define EBADF   9 /* Bad file number */)或104 (#define ECONNRESET 104 /* Connection reset by peer */)

對于被動關閉的SOCKET,recv傳回0,而且errno被置為11(#define EWOULDBLOCK EAGAIN /* Operation would block */)

對正常的SOCKET, 如果有接收資料,則傳回>0, 否則傳回-1,而且errno被置為11(#define EWOULDBLOCK EAGAIN /* Operation would block */)

是以對于簡單的狀态判斷(不過多考慮異常情況):

   recv傳回>0,   正常

31.如何清除無效的死鍊(端與端之間的線路故障)

答:TCP四次揮手時産生的TIME_WAIT或CLOSE_WAIT,造成死鍊。或者伺服器A<->路由器B<->路由器C<->用戶端D,鍊路中的路由器發生了故障,造成死鍊。需要采取定時器/心跳檢測來清理死鍊。

32.定時器的不同實作及優缺點

答:Windows可以使用OnTimer函數,Linux網絡庫定時器需要自己實作。

例如libevent的小根堆,libuv的紅黑樹,muduo的二叉搜尋樹,nginx的紅黑樹,redis的升序連結清單等

學習redis網絡庫,muduo,優先隊列std:priority_queue

34.http協定的具體格式

35.http head、get與post方法的細節

GET /index.php HTTP/1.1\r\n

Host: www.hootina.org\r\n

Connection: keep-alive\r\n

Cache-Control: max-age=0\r\n

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n

content-length: 8

User-Agent: Mozilla/5.0\r\n

\r\n

abcdefgh

POST /index.php HTTP/1.1\r\n

36.http代理、socks4代理與socks5代理如何編碼實作

答:代碼如下

BOOL UMySocket::Connect(PNETCONN_INFO pInfo,LPSTR lpMessage) //連接配接伺服器
{
wsysplus_memory vMemory;
long noDelay(1),tmSend(1800*1000L),tmRecv(1800*1000L);
LPSTR lpBuffer=vMemory.GetBuf(8001);
//關閉連接配接先
UMySocket::Close();
//初始化
if(!lpBuffer)
{
if(lpMessage) strcpy(lpMessage,"MALLOC DATA");
return(FALSE);
}
_hSocket=socket(PF_INET,SOCK_STREAM,0);
if(_hSocket==INVALID_SOCKET)
{
if(lpMessage) strcpy(lpMessage,"INVALID_SOCKET");
return(FALSE);
}
//設定連接配接屬性
setsockopt(_hSocket,IPPROTO_TCP,TCP_NODELAY,(LPSTR)&noDelay,sizeof(long));
setsockopt(_hSocket,SOL_SOCKET,SO_SNDTIMEO,(LPSTR)&tmSend,sizeof(long));
setsockopt(_hSocket,SOL_SOCKET,SO_RCVTIMEO,(LPSTR)&tmRecv,sizeof(long));
//連接配接伺服器
WORD wPortConn=(WORD)pInfo->proxyport;
char *lpServer=pInfo->proxyurl;
sockaddr_in remote={0};
remote.sin_family = AF_INET;
if(!(pInfo->conntype&0x000F)) //未使用代理
{
lpServer = pInfo->srvurl;
wPortConn = (WORD)pInfo->srvport;
}
 
remote.sin_port = htons(wPortConn); 
LPHOSTENT lphost=NULL; 
if((remote.sin_addr.s_addr=inet_addr(lpServer))==INADDR_NONE) 
{ 
if(lphost=gethostbyname(lpServer)) 
remote.sin_addr.s_addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; 
} 
do 
{ 
if(connect(_hSocket,(sockaddr*)&remote,sizeof(remote)) 
==SOCKET_ERROR&&WSAGetLastError()!=WSAEWOULDBLOCK) 
{ 
if(lpMessage) sprintf(lpMessage,"伺服器:%s:%d連接配接失敗",lpServer,wPortConn); 
break; 
} 
if(pInfo->conntype&PROXY_HTTP)//http proxy 
{ 
sprintf(lpBuffer,"CONNECT %s:%d HTTP/1.0\r\nUser-Agent:rmtcmd/0.1\r\n\r\n", 
pInfo->srvurl,pInfo->srvport); 
send(_hSocket,lpBuffer,strlen(lpBuffer),0); 
if(recv(_hSocket,lpBuffer,8000,0)<1) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
if(strstr(lpBuffer,"Connection established")==NULL) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
}
else if(pInfo->conntype&PROXY_SOCK5) //sock5 proxy 
{ 
PSOCK5REQ req = (PSOCK5REQ)lpBuffer; 
PSOCK5ANS ans = (PSOCK5ANS)(lpBuffer+1024); 
req->ver = 5; 
req->lmethods = 2; 
req->methods[0] = 0; 
req->methods[1] = 2; 
send(_hSocket,(LPSTR)req,4,0); 
if(!Recv(ans,sizeof(SOCK5ANS))|| 
ans->ver!=5||(ans->method&&ans->method!=2)) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
if(ans->method==2)  //need user & passwd 
{ 
PAUTHREQ reqa = (PAUTHREQ)lpBuffer; 
PAUTHANS ansa = (PAUTHANS)(lpBuffer+1024); 
memset(reqa,0,sizeof(AUTHREQ)); 
reqa->ver = 1; 
reqa->ulen = strlen(strcpy(reqa->user,pInfo->proxyuser)); 
reqa->plen = strlen(strcpy(reqa->passwd,pInfo->proxypasswd)); 
send(_hSocket,lpBuffer,sizeof(AUTHREQ),0); 
if(!Recv(ansa,sizeof(AUTHANS))||ansa->ver!=1||ansa->status) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
}
PSOCK5REQEX reqex = (PSOCK5REQEX)lpBuffer; 
PSOCK5ANSEX ansex = (PSOCK5ANSEX)(lpBuffer+1024); 
memset(reqex,0,sizeof(SOCK5REQEX)); 
reqex->ver = 5; 
reqex->cmd = 1; 
reqex->rsv = 0; 
reqex->atyp = 1; 
if((reqex->addr=inet_addr(pInfo->srvurl))==INADDR_NONE) 
{ 
if(lphost=gethostbyname(pInfo->srvurl)) 
reqex->addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; 
} 
reqex->port = ntohs((WORD)pInfo->srvport); 
send(_hSocket,(LPSTR)reqex,sizeof(SOCK5REQEX),0); 
if(!Recv(ansex,sizeof(SOCK5ANSEX))||ansex->ver!=5||ansex->rep) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
} 
else if(pInfo->conntype&PROXY_SOCK4) //sock4 proxy 
{ 
PSOCK4REQ req=(PSOCK4REQ)lpBuffer; 
PSOCK4ANS ans=(PSOCK4ANS)(lpBuffer+1024); 
req->vn = 4; 
req->cd = 1; 
req->port = ntohs((WORD)pInfo->srvport); 
if((req->addr=inet_addr(pInfo->srvurl))==INADDR_NONE) 
{ 
if(lphost=gethostbyname(pInfo->srvurl)) 
req->addr = ((LPIN_ADDR)lphost->h_addr)->s_addr; 
}
send(_hSocket,lpBuffer,sizeof(SOCK4REQ),0); 
if(!Recv(ans,sizeof(SOCK4ANS))) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
if(ans->vn||ans->cd!=90) 
{ 
if(lpMessage) sprintf(lpMessage,"代理伺服器:%s:%d通訊失敗",pInfo->srvurl,pInfo->srvport); 
break; 
} 
} 
return(TRUE); 
}while(0); 
UMySocket::Close(); 
return(FALSE); 
}
 
//代理伺服器連接配接 
#pragma pack(push,1) 
//sock4 req & ans 
typedef struct  tagSock4Req 
{ 
char    vn; 
char    cd; 
WORD    port; 
DWORD   addr; 
char    other[1]; 
}SOCK4REQ,*PSOCK4REQ; 
 
typedef struct  tagSock4Ans 
{ 
char    vn; 
char    cd; 
}SOCK4ANS,*PSOCK4ANS; 
//sock5 req & ans 
typedef struct  tagSock5Req 
{ 
char    ver; 
char    lmethods; 
char    methods[255]; 
}SOCK5REQ,*PSOCK5REQ; 
 
typedef struct  tagSock5Ans 
{ 
char    ver; 
char    method; 
}SOCK5ANS,*PSOCK5ANS; 
//sock5 check user 
typedef struct  tagAuthReq 
{ 
char    ver; 
char    ulen; 
char    user[255]; 
char    plen; 
char    passwd[255]; 
}AUTHREQ,*PAUTHREQ; 
 
typedef struct  tagAuthAns 
{ 
char    ver; 
char    status; 
}AUTHANS,*PAUTHANS; 
 
typedef struct  tagSock5ReqEx 
{ 
char    ver; 
char    cmd; 
char    rsv; 
char    atyp; 
long    addr; 
WORD    port; 
}SOCK5REQEX,*PSOCK5REQEX; 
 
typedef struct  tagSock5AnsEx 
{ 
char    ver; 
char    rep; 
char    rsv; 
char    atyp; 
char    other[1]; 
}SOCK5ANSEX,*PSOCK5ANSEX; 
#pragma pack(pop)

      

37.ping

38.telnet

39.close函數,fork

答:參考《UNP》卷1,第94頁。close是引用計數-1,在沒有到0的時候是不會關閉套接字的。fork調用會使父程序打開的socket引用計數+1,。是以一般多程序裡面,父程序在fork子程序之後,父程序可以關閉accept套接字,子程序可以關閉listen套接字。這個時候兩個套接字計數都從2減到1,是以不會關閉。是以父程序可以隻做監聽,子程序隻做通信。

40.Linux終端調試指令

netstat -nalp|grep 8011 #檢視8011端口的連接配接情況,觀察TCP狀态圖

netstat -nalp|grep 8011|wc -l #檢視8011端口的用戶端連接配接數

ulimit -n 102400 #修改目前程序的最大檔案數

tcpdump -i any 'tcp port 80'

lsof -i -Pn #lsof是list opened fd的單詞縮寫

netstat -anip

41.Windows cmd指令

netstat -ano|findstr "8011"#檢視8011端口的連接配接情況,觀察TCP狀态圖

-------

https://leetcode.com/

tcp/ip詳解 第1卷,UNP,APUE,TCP/IP協定族

《程式設計珠玑第2版·修訂版》

《程式設計珠玑(續)(修訂版)》

《程式設計之美——微軟技術面試心得》

《劍指OFFER:名企面試官精講典型程式設計題(第2版)》

《程式員代碼面試指南:IT名企算法與資料結構題目最優解》

《程式員面試寶典(第5版)》