活久見! TCP兩次揮手,你有看過嗎?那四次握手呢?
我們都知道,TCP是個連接導向的、可靠的、基於位元組流的傳輸層通訊協定。

#那這裡面提到的"面對連結",意味著需要建立連接,使用連接,釋放連接。
建立連線是指我們熟知的TCP三次握手。
而使用連接,則是透過一發送、一確認的形式,進行資料傳輸。
還有就是釋放連線,也就是我們常見的TCP四次揮手。
TCP四次揮手大家應該比較了解了,但大家看過三次揮手嗎?還有兩次揮手呢?
都看過?那四次握手呢?
今天這個話題,不想只是獵奇,也不想搞冷知識。
我們從四次揮手開始說起,搞點實用的知識點。
TCP四次揮手
#簡單回顧下TCP四次揮手。

正常情況下。只要資料傳輸完了,不管是客戶端還是服務端,都可以主動發起四次揮手,釋放連線。
就跟上圖畫的一樣,假設,這次四次揮手是由客戶端主動發起的,那它就是主動方。伺服器是被動接收客戶端的揮手請求的,叫被動方。
客戶端和伺服器,一開始,都是處於ESTABLISHED
狀態。
第一次揮手:一般情況下,主動方執行close()
或shutdown()
方法,會發個FIN報文
出來,表示"我不再發送資料了"。
第二次揮手:在收到主動方的FIN
報文後,被動方立馬回應一個ACK
,意思是"我收到你的FIN了,也知道你不再發資料了"。
上面提到的是主動方不再發送資料了。但如果這時候,被動方還有資料要發,那就繼續發。注意,雖然第二次和第三次揮手之間,被動方是能發數據到主動方的,但主動方能不能正常收就不一定了,這個待會說。
第三次揮手:在被動方在感知到第二次揮手之後,會做了一系列的收尾工作,最後也調用一個close()
, 這時候就會發出第三次揮手的FIN-ACK
。
第四次揮手:主動方回一個ACK
,意思是收到了。
其中第一次揮手和第三次揮手,都是我們在應用程式中主動觸發的(例如呼叫close()
方法),也就是我們平常寫程式碼需要關注的地方。
第二和第四次揮手,都是核心協定堆疊自動幫我們完成的,我們寫程式碼的時候碰不到這地方,因此也不需要太在意。
另外不管是主動或被動,每方發出了一個 FIN
和一個ACK
。也收到了一個 FIN
和一個ACK
。 這一點大家關注下,待會還會提到。
FIN一定要程式執行close()或shutdown()才能發出嗎?
不一定是。一般情況下,透過對socket
執行 close()
或 shutdown()
方法會發出FIN
#。但實際上,只要應用程式退出,不管是主動退出,還是被動退出(因為一些莫名其妙的原因被kill
了), 都會發出FIN
。
FIN 是指"我不再發送資料",所以
shutdown()
關閉讀取不會給對方發FIN, 關閉寫才會發FIN。
如果機器上FIN-WAIT-2狀態特別多,是為什麼
根據上面的四次揮手圖,可以看出,FIN-WAIT-2
是主動方那邊的狀態。
處於這個狀態的程序,一直在等待第三次揮手的FIN
。而第三次揮手需要由被動方在程式碼裡執行close()
發出。
因此當機器上FIN-WAIT-2
狀態特別多,那一般來說,另外一台機器上會有大量的 CLOSE_WAIT
。需要檢查有大量的 CLOSE_WAIT
的那台機器,為什麼遲遲不願意呼叫close()
關閉連線。
所以,如果機器上FIN-WAIT-2
狀態特別多,一般是因為對端一直不執行close()
方法發出第三次揮手。

主動方在close之後收到的數據,會怎麼處理
之前寫的一篇文章《代碼執行send成功後,數據就發出去了嗎? 》中,從原始碼的角度提到了,一般情況下,程式主動執行close()
的時候;
- ##如果目前連接對應的
socket
的
接收緩衝區有數據,會發RST。
- 如果
發送緩衝區有數據,那會等待發送完,再發第一次揮手的FIN
。
全雙工通訊,意思是發送資料的同時,還可以接收資料。
Close()的意思是,此時要同時
關閉發送和接收訊息的功能。
理論上,第二次和第三次揮手之間,被動方是可以傳數據給主動方的。
但如果 主動方的四次揮手是透過close() 觸發的,那麼主動方是不會去收這個訊息的。而且還會回一個
RST。直接結束掉這次連線。

第二第三次揮手之間,不能傳輸資料嗎?
也不是。前面提到Close()
的意思是,要同時關閉發送和接收訊息的功能。
那如果能做到只關閉發送訊息,不關閉接收訊息的功能,那就能繼續收訊息了。這種 half-close
的功能,透過呼叫shutdown()
方法就能做到。
int shutdown(int sock, int howto);
其中 howto 為斷開方式。有以下取值:
SHUT_RD:關閉讀取。這時應用層不應該再嘗試接收數據,內核協定棧中就算接收緩衝區收到數據也會被丟棄。
SHUT_WR:關閉寫入。如果發送緩衝區中還有資料沒發,會將資料傳遞到目標主機。
SHUT_RDWR:關閉讀取和寫入。相當於
close()
了。

##
怎么知道对端socket执行了close还是shutdown
不管主动关闭方调用的是close()
还是shutdown()
,对于被动方来说,收到的就只有一个FIN
。
被动关闭方就懵了,"我怎么知道对方让不让我继续发数据?"

其实,大可不必纠结,该发就发。
第二次挥手和第三次挥手之间,如果被动关闭方想发数据,那么在代码层面上,就是执行了 send()
方法。
int send( SOCKET s,const char* buf,int len,int flags);
send()
会把数据拷贝到本机的发送缓冲区。如果发送缓冲区没出问题,都能拷贝进去,所以正常情况下,send()
一般都会返回成功。

然后被动方内核协议栈会把数据发给主动关闭方。
如果上一次主动关闭方调用的是
shutdown(socket_fd, SHUT_WR)
。那此时,主动关闭方不再发送消息,但能接收被动方的消息,一切如常,皆大欢喜。如果上一次主動關閉方呼叫的是
close()
。那主動方在收到被動方的資料後會直接丟棄,然後回一個RST
。
針對第二種情況。
被動方核心協定堆疊收到了RST
,會把連線關閉。但核心連線關閉了,應用層也不知道(除非被通知)。
此時被動方應用層接下來的操作,無非就是讀取或寫入。
如果是讀,則會回傳
RST
的報錯,也就是我們常見的Connection reset by peer
。如果是寫,那麼程式會產生
SIGPIPE
訊號,應用層程式碼可以擷取並處理訊號,如果不處理,則預設情況下進程會終止,意外關閉.
總結,當被動關閉方recv()
傳回EOF
時,說明主動方透過close()
或shutdown(fd, SHUT_WR)
發起了第一次揮手。
如果此時被動方執行兩次 send()
。
第一次
send()
, 一般會成功回傳。第二次
send()
時。如果主動方是透過shutdown(fd, SHUT_WR)
發起的第一次揮手,那麼此時send()
還是會成功。如果主動方透過close()
發起的第一次揮手,那此時會產生SIGPIPE
訊號,進程預設會終止,異常退出。不想異常退出的話,記得捕獲處理這個訊號。
如果被動方一直不發第三次揮手,會怎麼樣
第三次揮手,是由被動方主動觸發的,例如呼叫close()
。
如果因為程式碼錯誤或其他一些原因,被動方就是不執行第三次揮手。
這時候,主動方會根據自身第一次揮手的時候用的是close()
還是shutdown(fd, SHUT_WR)
,有不同的行為表現。
如果是
shutdown(fd, SHUT_WR)
,说明主动方其实只关闭了写,但还可以读,此时会一直处于FIN-WAIT-2
, 死等被动方的第三次挥手。如果是
close()
, 说明主动方读写都关闭了,这时候会处于FIN-WAIT-2
一段时间,这个时间由net.ipv4.tcp_fin_timeout
控制,一般是60s
,这个值正好跟2MSL
一样 。超过这段时间之后,状态不会变成 `TIME-WAIT`,而是直接变成`CLOSED`。
# cat /proc/sys/net/ipv4/tcp_fin_timeout 60

TCP三次挥手
四次挥手聊完了,那有没有可能出现三次挥手?
是可能的。
我们知道,TCP四次挥手里,第二次和第三次挥手之间,是有可能有数据传输的。第三次挥手的目的是为了告诉主动方,"被动方没有数据要发了"。
所以,在第一次挥手之后,如果被动方没有数据要发给主动方。第二和第三次挥手是有可能合并传输的。这样就出现了三次挥手。

如果有資料要發,就不能是三次揮手了嗎
上面提到的是沒有資料要發的情況,如果第二、第三次揮手之間有數據要發,就不可能變成三次揮手了嗎?
並不是。 TCP中還有個特性叫延遲確認。可以簡單理解為:接收方收到資料以後不需要立刻馬上回覆ACK確認包。
在此基礎上,不是每一次發送資料包都能對應收到一個 ACK
確認包,因為接收者可以合併確認。
而這個合併確認,放在四次揮手裡,可以把第二次揮手、第三次揮手,以及他們之間的資料傳輸都合併在一起發送。因此也出現了三次揮手。

TCP兩次揮手
前面在四次揮手中提到,關閉的時候雙方都發出了一個FIN和收到了一個ACK。
正常情況下TCP連線的兩端,是不同IP 連接埠的進程。
但如果TCP連接的兩端,IP 連接埠是一樣的情況下,那麼在關閉連接的時候,也同樣做到了一端發出了一個FIN,也收到了一個ACK,只不過剛好這兩端其實是同一個socket
。

而這種兩端IP 埠都一樣的連接,叫TCP自連接。
是的,你没看错,我也没打错别字。同一个socket确实可以自己连自己,形成一个连接。
一个socket能建立连接?
上面提到了,同一个客户端socket,自己对自己发起连接请求。是可以成功建立连接的。这样的连接,叫TCP自连接。
下面我们尝试下复现。
注意我是在以下系统进行的实验。在mac
上多半无法复现。
# cat /etc/os-release NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)"
通过nc
命令可以很简单的创建一个TCP自连接
# nc -p 6666 127.0.0.1 6666
上面的 -p
可以指定源端口号。也就是指定了一个端口号为6666
的客户端去连接 127.0.0.1:6666
。
# netstat -nt | grep 6666 tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
整个过程中,都没有服务端参与。可以抓个包看下。

可以看到,相同的socket,自己连自己的时候,握手是三次的。挥手是两次的。

上面这张图里,左右都是同一个客户端,把它画成两个是为了方便大家理解状态的迁移。
我们可以拿自连接的握手状态对比下正常情况下的TCP三次握手。

看了自连接的状态图,再看看下面几个问题。
一端发出第一次握手后,如果又收到了第一次握手的SYN包,TCP连接状态会怎么变化?
第一次握手过后,连接状态就变成了SYN_SENT
状态。如果此时又收到了第一次握手的SYN包,那么连接状态就会从SYN_SENT
状态变成SYN_RCVD
。
// net/ipv4/tcp_input.c static int tcp_rcv_synsent_state_process() { // SYN_SENT状态下,收到SYN包 if (th->syn) { // 状态置为 SYN_RCVD tcp_set_state(sk, TCP_SYN_RECV); } }
一端发出第二次握手后,如果又收到第二次握手的SYN+ACK包,TCP连接状态会怎么变化?
第二握手过后,连接状态就变为SYN_RCVD
了,此时如果再收到第二次握手的SYN+ACK
包。连接状态会变为ESTABLISHED
。
// net/ipv4/tcp_input.c int tcp_rcv_state_process() { // 前面省略很多逻辑,能走到这就认为肯定有ACK if (true) { // 判断下这个ack是否合法 int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) > 0; switch (sk->sk_state) { case TCP_SYN_RECV: if (acceptable) { // 状态从 SYN_RCVD 转为 ESTABLISHED tcp_set_state(sk, TCP_ESTABLISHED); } } } }
一端第一次挥手后,又收到第一次挥手的包,TCP连接状态会怎么变化?
第一次挥手过后,一端状态就会变成 FIN-WAIT-1
。正常情况下,是要等待第二次挥手的ACK
。但实际上却等来了 一个第一次挥手的 FIN
包, 这时候连接状态就会变为CLOSING
。
// net/ static void tcp_fin(struct sock *sk) { switch (sk->sk_state) { case TCP_FIN_WAIT1: tcp_send_ack(sk); // FIN-WAIT-1状态下,收到了FIN,转为 CLOSING tcp_set_state(sk, TCP_CLOSING); break; } }
这可以说是隐藏剧情了。
CLOSING
很少见,除了出现在自连接关闭外,一般还会出现在TCP两端同时关闭连接的情况下。
处于CLOSING
状态下时,只要再收到一个ACK
,就能进入 TIME-WAIT
状态,然后等个2MSL
,连接就彻底断开了。这跟正常的四次挥手还是有些差别的。大家可以滑到文章开头的TCP四次挥手再对比下。
代码复现自连接
可能大家会产生怀疑,这是不是nc
这个软件本身的bug
。
那我们可以尝试下用strace
看看它内部都做了啥。
# strace nc -p 6666 127.0.0.1 6666 // ... socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3 fcntl(3, F_GETFL) = 0x2 (flags O_RDWR) fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 bind(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 connect(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress) // ...
无非就是以创建了一个客户端socket
句柄,然后对这个句柄执行 bind
, 绑定它的端口号是6666
,然后再向 127.0.0.1:6666
发起connect
方法。
我们可以尝试用C语言
去复现一遍。
下面的代码,只用于复现问题。直接跳过也完全不影响阅读。
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <ctype.h> #include <string.h> #include <strings.h> int main() { int lfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char buf[BUFSIZ]; int n = 0, i = 0, ret = 0 ; printf("This is a client \n"); /*Step 1: 创建客户端端socket描述符cfd*/ cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd == -1) { perror("socket error"); exit(1); } int flag=1,len=sizeof(int); if( setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { perror("setsockopt"); exit(1); } bzero(&clie_addr, sizeof(clie_addr)); clie_addr.sin_family = AF_INET; clie_addr.sin_port = htons(6666); inet_pton(AF_INET,"127.0.0.1", &clie_addr.sin_addr.s_addr); /*Step 2: 客户端使用bind绑定客户端的IP和端口*/ ret = bind(cfd, (struct sockaddr* )&clie_addr, sizeof(clie_addr)); if(ret != 0) { perror("bind error"); exit(2); } /*Step 3: connect链接服务器端的IP和端口号*/ bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(6666); inet_pton(AF_INET,"127.0.0.1", &serv_addr.sin_addr.s_addr); ret = connect(cfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)); if(ret != 0) { perror("connect error"); exit(3); } /*Step 4: 向服务器端写数据*/ while(1) { fgets(buf, sizeof(buf), stdin); write(cfd, buf, strlen(buf)); n = read(cfd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, n);//写到屏幕上 } /*Step 5: 关闭socket描述符*/ close(cfd); return 0; }
保存为 client.c
文件,然后执行下面命令,会发现连接成功。
# gcc client.c -o client && ./client This is a client
# netstat -nt | grep 6666 tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
说明,这不是nc的bug。事实上,这也是内核允许的一种情况。
自连接的解决方案
自连接一般不太常见,但遇到了也不难解决。
解决方案比较简单,只要能保证客户端和服务端的端口不一致就行。
事实上,我们写代码的时候一般不会去指定客户端的端口,系统会随机给客户端分配某个范围内的端口。而这个范围,可以通过下面的命令进行查询
# cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999
也就是只要我们的服务器端口不在32768-60999
这个范围内,比如设置为8888
。就可以规避掉这个问题。
另外一个解决方案,可以参考golang
标准网络库的实现,在连接建立完成之后判断下IP和端口是否一致,如果遇到自连接,则断开重试。
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) { // 如果是自连接,这里会重试 for i := 0; i < 2 && (laddr == nil || laddr.Port == 0) && (selfConnect(fd, err) || spuriousENOTAVAIL(err)); i++ { if err == nil { fd.Close() } fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) } // ... } func selfConnect(fd *netFD, err error) bool { // 判断是否端口、IP一致 return l.Port == r.Port && l.IP.Equal(r.IP) }
四次握手
前面提到的TCP
自连接是一个客户端自己连自己的场景。那不同客户端之间是否可以互联?
答案是可以的,有一种情况叫TCP同时打开。

大家可以对比下,TCP同时打开在握手时的状态变化,跟TCP自连接是非常的像。
比如SYN_SENT
状态下,又收到了一个SYN
,其实就相当于自连接里,在发出了第一次握手后,又收到了第一次握手的请求。结果都是变成 SYN_RCVD
。
在 SYN_RCVD
状态下收到了 SYN+ACK
,就相当于自连接里,在发出第二次握手后,又收到第二次握手的请求,结果都是变成 ESTABLISHED
。他们的源码其实都是同一块逻辑。
复现TCP同时打开
分别在两个控制台下,分别执行下面两行命令。
while true; do nc -p 2224 127.0.0.1 2223 -v;done while true; do nc -p 2223 127.0.0.1 2224 -v;done
上面两个命令的含义也比较简单,两个客户端互相请求连接对方的端口号,如果失败了则不停重试。
执行后看到的现象是,一开始会疯狂失败,重试。一段时间后,连接建立完成。
# netstat -an | grep 2223 Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 127.0.0.1:2224 127.0.0.1:2223 ESTABLISHED tcp 0 0 127.0.0.1:2223 127.0.0.1:2224 ESTABLISHED
期间抓包获得下面的结果。

可以看到,这里面建立连接用了四次交互。因此可以说这是通过"四次握手"建立的连接。
而且更重要的是,这里面只涉及两个客户端,没有服务端。
看到这里,不知道大家有没有跟我一样,被刷新了一波认知,对socket
有了重新的认识。
在以前的观念里,建立连接,必须要有一个客户端和一个服务端,并且服务端还要执行一个listen()
和一个accept()
。而实际上,这些都不是必须的。
那麼下次,面試官問你"沒有listen()
, TCP能建立連線嗎?", 我想大家應該知道該怎麼回答了。
但問題又來了,只有兩個客戶端,沒有listen()
,為什麼能建立TCP
連線?
如果大家有興趣,我們以後有機會再填上這個坑。
總結
# 四次揮手中,不管是程式主動執行
close()
,還是進程被殺,都有可能發出第一次揮手FIN
包。如果機器上FIN-WAIT-2
狀態特別多,一般是因為對端一直不執行close()
方法發出第三次揮手。Close()
會同時關閉傳送和接收訊息的功能。shutdown()
能單獨關閉發送或接受訊息。第二、第三次揮手,是有可能合在一起的。於是四次揮手就變成三次揮手了。
同一個socket自己連自己,會產生TCP自連接,自連接的揮手是兩次揮手。
沒有
listen
,兩個客戶端之間也能建立連線。這種情況叫做TCP同時開啟,它由四次握手產生。
最後
#今天提到的,不管是兩次揮手 ,還是自連接,或是TCP同時開啟什麼的。
咋一看,可能對日常搬磚沒什麼用,其實也確實沒什麼用。
並且在面試上大機率也不會被問到。
畢竟一般面試官也不在乎 Turner字有幾種寫法。
這篇文章的目的,主要是想從另一個角度讓大家重新認識socket
。原來TCP
是可以自己連自己的,甚至兩個客戶端之間,不用服務端也能連起來。
這實在是,太出乎意料了。
以上是活久見! TCP兩次揮手,你有看過嗎?那四次握手呢?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

win10如何重置tcp/ip協定?其實方法很簡單的,使用者可以直接的進入到命令提示符,然後按下ctrl shift enter的組合鍵來進行操作就可以了或者是直接的執行重置命令來進行設置,下面就讓本站來為使用者來仔細的介紹一下windows10重置tcp/ip協定棧的方法吧。 windows10重置tcp/ip協定棧的方法一、管理員權限1、我們使用快捷鍵win R直接開啟運行窗口,然後輸入cmd並按住ctrl shift enter的組合鍵。 2、或者我們可以直接在開始選單中搜尋命令提示符,右鍵點

TCP客戶端一個使用TCP協定實現可連續對話的客戶端範例程式碼:importsocket#客戶端設定HOST='localhost'PORT=12345#建立TCP套接字並連接伺服器client_socket=socket.socket(socket.AF_INET,socket .SOCK_STREAM)client_socket.connect((HOST,PORT))whileTrue:#取得使用者輸入message=input("請輸入要傳送的訊息:&

TCP是電腦網路通訊協定的一種,是一種連線導向的傳輸協定。在Java應用開發中,TCP通訊被廣泛應用於各種場景,例如客戶端和伺服器之間的資料傳輸、音訊視訊即時傳輸等等。 Netty4是一個高效能、高可擴展性、高效能的網路程式框架,能夠優化伺服器和用戶端之間的資料交換流程,使其更有效率可靠。使用Netty4進行TCP通訊的具體實作步驟如下:引入

那這裡面提到的"面向連接",意味著需要 建立連接,使用連接,釋放連接。建立連線是指我們熟知的TCP三次握手。而使用連接,則是透過一發送、一確認的形式,進行資料傳輸。還有就是釋放連接,也就是我們常見的TCP四次揮手。

在TCP通信雙方中,為了描述方便,以下將通信雙方用A和B代替。根據TCP協定規定,如果A關閉連線後B繼續發送數據,B會收到A的RST回應。若B繼續發送數據,系統會發出SIGPIPE訊號告知連接已斷開,停止發送。系統對SIGPIPE訊號的預設處理行為是讓B進程退出。作業系統對SIGPIPE訊號的這種預設處理行為非常不友好,讓我們來分析一下。 TCP通訊是全雙工頻道,相當於兩條單工頻道,連線兩端各負責一條。當對端「關閉」時,雖然本意是關閉整個兩條頻道,但本端只是收到FIN包。依TCP協議的規定,當一

使用一個TCP連線發送多個檔案為什麼會有這篇部落格?最近在看一些相關方面的東西,簡單的使用一下Socket進行程式設計是沒有的問題的,但是這樣只是建立了一些基本概念。對於真正的問題,還是無能為力。當我需要進行檔案的傳輸時,我發現我好像只是發送過去了資料(二進位資料),但是關於檔案的一些資訊卻遺失了(檔案的副檔名)。而且每次我只能使用一個Socket發送一個文件,沒有辦法做到連續發送文件(因為我是依靠關閉流來完成發送文件的,也就是說我其實是不知道文件的長度,所以只能以一個Socket連接代表一個檔案)。

TCP和IP是網際網路中兩個不同的協定:1、TCP是一種運輸層協議,而IP是一種網路層協定;2、TCP提供了資料包的分段、排序、確認和重傳等功能,而IP協定負責為資料包提供來源和目標位址;3、TCP是面向連線的協議,而IP協定是無連線的;4、TCP也提供流量控制和擁塞控制。

曾經有這麼一道經典面試題:從 URL 在瀏覽器被輸入到頁面展現的過程中發生了什麼事?相信大多數準備過的同學都能回答出來,但如果繼續問:收到的HTML 如果包含幾十個圖片標籤,這些圖片是以什麼方式、什麼順序、建立了多少連接、使用什麼協議被下載下來的呢?