本次 Live 主要包括以下内容 • TCP/IP協定棧層次與三次握手、四次揮手需要知道的細節 • TCP與UDP适用場景 • linux網絡模型 • epoll_event結構中epoll_data_t的fd與ptr使用場景 •Windows網絡模型 •異步connect •select可以檢測網絡異常嗎 •epoll的水準模式和邊緣模式 •阻塞與非阻塞socket的設定與差別 •send/recv傳回值問題 •如何編寫正确的收與發資料代碼 •收發緩沖區如何設計 •SO_SNDTIMEO、SO_RCVTIMEO、TCP_NODELAY、SO_REUSEADDR和SO_REUSEPORT、SO_LINGER選項 •shutdown與優雅關閉 •錯誤碼EINTR •tcp粘包問題 •信号SIGPIPE與EPIPE錯誤碼 •gethostbyname阻塞與錯誤碼 •SO_KEEPALIVE選項與心跳包設計技巧 •如何設計斷線重連機制 •如何清除無效的死鍊 •網絡架構中定時器不同實作 •http協定格式、head、get與post方法細節 •http、socks4與socks5代理編碼實作 •你問我答互動環節 •總結
技術面試中常見的網絡通信細節問題解答
1. TCP/IP協定棧層次結構
2. TCP三向交握需要知道的細節點
3. TCP四次揮手需要知道的細節點(CLOSE_WAIT、TIME_WAIT、MSL)
4. TCP與UDP的差別與适用場景
5. linux常見網絡模型詳解(select、poll與epoll)
6. epoll_event結構中的epoll_data_t的fd與ptr的使用場景
7. Windows常見的網絡模型詳解(select、WSAEventSelect、WSAAsyncSelect)
8. Windows上的完成端口模型(IOCP)
9. 異步的connect函數如何編寫
10.select函數可以檢測網絡異常嗎?
11.你問我答環節一
12. epoll的水準模式和邊緣模式
13. 如何将socket設定成非阻塞的(建立時設定與建立完成後設定),非阻塞socket與阻塞的socket在收發資料上的差別
14. send/recv(read/write)傳回值大于0、等于0、小于0的差別
15.如何編寫正确的收資料代碼與發資料代碼
16.發送資料緩沖區與接收資料緩沖區如何設計
17.socket選項SO_SNDTIMEO和SO_RCVTIMEO
18.socket選項TCP_NODELAY
19.socket選項SO_REUSEADDR和SO_REUSEPORT(Windows平台與linux平台的差別)
20.socket選項SO_LINGER
21.shutdown與優雅關閉
22.你問我答環節二
23.socket選項SO_KEEPALIVE
24.關于錯誤碼EINTR
25.如何解決tcp粘包問題
26.信号SIGPIPE與EPIPE錯誤碼
27.gethostbyname阻塞與錯誤碼擷取問題
28.心跳包的設計技巧(保活心跳包與業務心跳包)
29.斷線重連機制如何設計
30.如何檢測對端已經關閉
31.如何清除無效的死鍊(端與端之間的線路故障)
32.定時器的不同實作及優缺點
33.你問我答環節三
34.http協定的具體格式
35.http head、get與post方法的細節
36.http代理、socks4代理與socks5代理如何編碼實作
37.ping
38.telnet
39.你問我答環節四
40.總結
--------------------------------------------------------------------------------------
技術面試中常見的網絡通信細節問題解答
1. TCP/IP協定棧層次結構

2. TCP三向交握需要知道的細節點
答:1、三次握手,如果前兩次有某一次失敗,會重新從第一次開始,重來三次。
2、三次握手,如果最後一次失敗,伺服器并不會重傳ack封包,而是直接發送RTS封包段,進入CLOSED狀态。這樣做的目的是為了防止SYN洪泛攻擊。詳情參見http://blog.csdn.net/libaineu2004/article/details/79020031
3、發起連接配接時如果發生TCP SYN丢包,那麼系統預設的重試間隔是3s,這期間不會傳回錯誤碼。
4、如何模拟tcp揮手失敗?答案是iptables指令可以過濾資料包,丢棄所有的連接配接請求,緻使用戶端無法得到任何ack封包。
TCP 的那些事兒(上)
TCP 的那些事兒(下)
3. TCP四次揮手需要知道的細節點(CLOSE_WAIT、TIME_WAIT、MSL)
答:http://blog.csdn.net/libaineu2004/article/details/78886213 再談應用環境下的TIME_WAIT和CLOSE_WAIT
http://blog.csdn.net/libaineu2004/article/details/78886182 CLOSE_WAIT狀态的原因與解決方法
http://blog.csdn.net/libaineu2004/article/details/78803068 TCP面試常見題:time_wait狀态産生的原因,危害,如何避免
4. TCP與UDP的差別與适用場景
答:TCP協定棧本身是可靠,不會丢包,不會亂序,失敗會重發。UDP需要應用層做協定來保證可靠性。視訊可以用UDP。
必須使用udp的場景:廣播。
5. linux常見網絡模型詳解(select、poll與epoll)
答:select和poll本質上沒有差別,都是輪詢,但是poll沒有最大裝置描述符數量的限制。
6. epoll_event結構中的epoll_data_t的fd與ptr的使用場景
答:在epoll模型中使用了一個struct epoll_event的結構體:
typedef union epoll_data
{ void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{ uint32_t events;
epoll_data_t data;
};
7. Windows常見的網絡模型詳解(select、WSAEventSelect、WSAAsyncSelect)
8. Windows上的完成端口模型(IOCP)
9. 異步的connect函數如何編寫
參考部落格:http://blog.csdn.net/analogous_love/article/details/60761528
-
//關于tcp連接配接的異步connect實作流程如下:
-
//(1)設定socket連接配接為非阻塞.
-
//(2)調用connect函數.傳回0表明連接配接成功.如果傳回-1,同時errno為EINPROGRESS表明正在建立連接配接.
-
//(3)使用select , epoll等 , 當描述符可寫的時候檢查連接配接狀态.
-
#include <stdio.h>
-
#include <fcntl.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <errno.h>
-
#include <sys/epoll.h>
-
#include <netinet/in.h>
-
#include <string.h>
-
void setnonblock(int fd)
-
{
-
int flags = fcntl(fd, F_GETFL);
-
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
-
}
-
int main(){
-
const char* ip = "127.0.0.1";
-
short port = 9999;
-
//設定連接配接位址
-
struct sockaddr_in addr;
-
socklen_t socklen = sizeof(struct sockaddr_in);
-
memset(&addr , 0 , sizeof(struct sockaddr_in));
-
addr.sin_family = AF_INET;
-
addr.sin_addr.s_addr = inet_addr(ip);
-
addr.sin_port = htons(port);
-
//建立描述符
-
int fd = socket( AF_INET , SOCK_STREAM , 0);
-
if (fd < 0){
-
printf("socket() error\n");
-
return -1;
-
}
-
//設定描述符為非阻塞
-
setnonblock(fd);
-
//連接配接
-
int res;
-
res = connect(fd , (struct sockaddr*)&addr , socklen);
-
if (res == 0){
-
printf("connect ok(1)\n");
-
} else if (res == -1 && errno != EINPROGRESS){
-
printf("connect err(1)\n");
-
close(fd);
-
return -1;
-
} else {
-
int epfd;
-
//建立epoll描述符
-
epfd = epoll_create(1024);
-
if ( (epfd = epoll_create(1024) ) == -1){
-
printf("epoll_create() err\n");
-
close(fd);
-
return -1;
-
}
-
//添加關注事件
-
struct epoll_event ev;
-
ev.events = EPOLLOUT;
-
ev.data.fd = fd;
-
epoll_ctl( epfd , EPOLL_CTL_ADD , fd , &ev);
-
//編寫網絡程式的時候,epoll是程式的主循環.我們這裡為了測試,連接配接上或connect逾時(75秒)就break掉.
-
//正常的流程是寫一個處理connect結果的回調函數.
-
int event_max = 1;
-
struct epoll_event events[event_max];
-
int i;
-
while (1){
-
res = epoll_wait( epfd , events , event_max , -1);
-
if (res > 0){
-
for ( i = 0 ; i < res ; i++){
-
if ( events[i].data.fd == fd && ( events[i].events & EPOLLOUT) ){ //29(EPOLLOUT|EPOLLERR|EPOLLHUP) //4(EPOLLOUT)
-
//檢查是否連接配接成功
-
int optval;
-
socklen_t optlen = sizeof(optval);
-
int res1 = getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
-
if ( res1 < 0 || optval){
-
close(fd);
-
close(epfd);
-
printf("connect err(2)\n");
-
return -1;
-
} else {
-
printf("connect ok(2)\n");
-
}
-
}
-
}
-
break;
-
}
-
}
-
close(fd);
-
close(epfd);
-
}
-
}
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()
-
{
-
//每次隻收取256個位元組
-
char buff[256];
-
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)
-
return false;
-
inputBuffer_.add(buff, (size_t)nRecv);
-
}
-
return true;
-
}
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接收到資料大小,
特别:非阻塞模式下傳回 值 <0時并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況 下認為連接配接是正常的,繼續接收。
隻是阻塞模式下read會阻塞着接收資料,非阻塞模式下如果沒有資料會傳回,不會阻塞着讀,是以需要 循環讀取。
send:
阻塞與非阻塞send傳回值沒有區分,都是 <0:出錯,=0:連接配接關閉,>0發送資料大小,
特别:非阻塞模式下傳回值 <0時并且 (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情況下認為連接配接是正常的, 繼續發送。
隻是阻塞模式下send會阻塞着發送資料,非阻塞模式下如果暫時無法發送資料會傳回,不會阻塞着 send,是以需要循環發送。
15.如何編寫正确的收資料代碼與發資料代碼
答:都需要考慮緩沖區的設計,發資料如果失敗先緩存起來,等待下一次的機會再發。
參考部落格:http://blog.csdn.net/analogous_love/article/details/60761528
-
bool TcpSession::Send()
-
{
-
while (true)
-
{
-
int n = ::send(clientfd_, buffer_, buffer_.length(), 0);
-
if (n == -1)
-
{
-
//tcp視窗容量不夠, 暫且發不出去,下次再發
-
if (errno == EWOULDBLOCK)
-
break;
-
//被信号中斷,繼續發送
-
else if (errno == EINTR)
-
continue;
-
return false;
-
}
-
//對端關閉了連接配接
-
else if (n == 0)
-
return false;
-
buffer_.erase(n);
-
//全部發送完畢
-
if (buffer_.length() == 0)
-
break;
-
}
-
return true;
-
}
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;
-
return false;
-
}
-
}
-
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 )或104 (#define ECONNRESET 104 )
對于被動關閉的SOCKET,recv傳回0,而且errno被置為11(#define EWOULDBLOCK EAGAIN )
對正常的SOCKET, 如果有接收資料,則傳回>0, 否則傳回-1,而且errno被置為11(#define EWOULDBLOCK EAGAIN )
是以對于簡單的狀态判斷(不過多考慮異常情況):
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
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狀态圖