建站之星如何建網(wǎng)站sem推廣是什么
一、操作函數(shù)簡(jiǎn)介
在 Linux 中,TCP(傳輸控制協(xié)議)操作涉及多種系統(tǒng)調(diào)用和函數(shù),通常用來(lái)創(chuàng)建套接字、連接、發(fā)送/接收數(shù)據(jù)、關(guān)閉連接等。以下是一些常用的 TCP 操作函數(shù)和它們的簡(jiǎn)要說(shuō)明:
1. socket()
- 函數(shù)原型:
int socket(int domain, int type, int protocol);
- 功能: 創(chuàng)建一個(gè)新的套接字(socket),它是與網(wǎng)絡(luò)通信相關(guān)的基本對(duì)象。
- 參數(shù):
domain
: 協(xié)議族(如AF_INET
用于 IPv4,AF_INET6
用于 IPv6)。type
: 套接字類型(如SOCK_STREAM
表示 TCP,SOCK_DGRAM
表示 UDP)。protocol
: 使用的協(xié)議,通常設(shè)為0
,由系統(tǒng)自動(dòng)選擇合適的協(xié)議。
- 返回值: 返回一個(gè)套接字描述符(文件描述符),失敗時(shí)返回
-1
。
2. bind()
- 函數(shù)原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能: 將套接字與本地地址(IP 地址和端口)綁定。
- 參數(shù):
sockfd
: 要綁定的套接字。addr
: 地址結(jié)構(gòu),通常是struct sockaddr_in
,指定 IP 和端口。addrlen
: 地址結(jié)構(gòu)的長(zhǎng)度。
- 返回值: 成功返回
0
,失敗返回-1
。
3. listen()
- 函數(shù)原型:
int listen(int sockfd, int backlog);
- 功能: 將套接字設(shè)置為被動(dòng)模式,等待客戶端連接。
- 參數(shù):
sockfd
: 套接字描述符。backlog
: 最多可連接的等待隊(duì)列的大小。
- 返回值: 成功返回
0
,失敗返回-1
。
4. accept()
- 函數(shù)原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能: 接受來(lái)自客戶端的連接請(qǐng)求,并返回一個(gè)新的套接字描述符用于與客戶端通信。
- 參數(shù):
sockfd
: 已經(jīng)調(diào)用listen()
的套接字。addr
: 客戶端的地址信息。addrlen
: 地址結(jié)構(gòu)的大小。
- 返回值: 返回新的套接字描述符,用于與客戶端的通信,失敗時(shí)返回
-1
。
5. connect()
- 函數(shù)原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能: 客戶端發(fā)起與服務(wù)器的連接請(qǐng)求。
- 參數(shù):
sockfd
: 客戶端套接字描述符。addr
: 目標(biāo)服務(wù)器的地址信息。addrlen
: 地址結(jié)構(gòu)的長(zhǎng)度。
- 返回值: 成功返回
0
,失敗返回-1
。
6. send()
- 函數(shù)原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 功能: 通過(guò)套接字發(fā)送數(shù)據(jù)。
- 參數(shù):
sockfd
: 套接字描述符。buf
: 數(shù)據(jù)緩沖區(qū)。len
: 發(fā)送數(shù)據(jù)的長(zhǎng)度。flags
: 發(fā)送標(biāo)志(一般設(shè)為0
)。
- 返回值: 返回實(shí)際發(fā)送的字節(jié)數(shù),失敗時(shí)返回
-1
。
7. recv()
- 函數(shù)原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 功能: 從套接字接收數(shù)據(jù)。
- 參數(shù):
sockfd
: 套接字描述符。buf
: 存儲(chǔ)接收到數(shù)據(jù)的緩沖區(qū)。len
: 接收數(shù)據(jù)的最大長(zhǎng)度。flags
: 接收標(biāo)志(一般設(shè)為0
)。
- 返回值: 返回實(shí)際接收的字節(jié)數(shù),失敗時(shí)返回
-1
。
8. close()
- 函數(shù)原型:
int close(int fd);
- 功能: 關(guān)閉套接字,釋放相關(guān)資源。
- 參數(shù):
fd
: 套接字描述符。
- 返回值: 成功返回
0
,失敗返回-1
。
9. shutdown()
- 函數(shù)原型:
int shutdown(int sockfd, int how);
- 功能: 用于關(guān)閉套接字的讀、寫(xiě)或者雙向通信。
- 參數(shù):
sockfd
: 套接字描述符。how
: 控制關(guān)閉的方式,常用值為:SHUT_RD
: 關(guān)閉讀取(不能再讀取數(shù)據(jù))。SHUT_WR
: 關(guān)閉寫(xiě)入(不能再發(fā)送數(shù)據(jù))。SHUT_RDWR
: 同時(shí)關(guān)閉讀寫(xiě)。
- 返回值: 成功返回
0
,失敗返回-1
。
10. getsockopt() 和 setsockopt()
- 函數(shù)原型:
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
- 功能: 用于獲取或設(shè)置套接字的選項(xiàng)(如 TCP 的各種參數(shù),如緩沖區(qū)大小、超時(shí)時(shí)間等)。
- 參數(shù):
sockfd
: 套接字描述符。level
: 設(shè)置選項(xiàng)的協(xié)議層級(jí),通常為SOL_SOCKET
(套接字層)或IPPROTO_TCP
(TCP 層)。optname
: 選項(xiàng)名稱(如SO_RCVBUF
,SO_RCVBUF
等)。optval
: 選項(xiàng)的值。optlen
: 選項(xiàng)值的長(zhǎng)度。
- 返回值: 成功返回
0
,失敗返回-1
。
11. select() 和 poll()
- 函數(shù)原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 功能: 允許程序監(jiān)聽(tīng)多個(gè)套接字,并在某些事件(如可讀、可寫(xiě)等)發(fā)生時(shí)進(jìn)行處理。
12. accept4()(Linux 特有)
- 函數(shù)原型:
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
- 功能: 與
accept()
類似,但支持額外的標(biāo)志(如SOCK_NONBLOCK
等),在非阻塞模式下返回。 - 返回值: 返回一個(gè)新的套接字描述符,失敗時(shí)返回
-1
。
小結(jié):
這些是常見(jiàn)的用于 TCP 通信的 Linux 系統(tǒng)調(diào)用和函數(shù)。它們?cè)试S應(yīng)用程序通過(guò)網(wǎng)絡(luò)進(jìn)行基本的連接管理、數(shù)據(jù)發(fā)送/接收等操作。通常情況下,服務(wù)器會(huì)使用 socket()
、bind()
、listen()
和 accept()
來(lái)創(chuàng)建并處理客戶端連接,而客戶端則使用 socket()
和 connect()
發(fā)起連接。數(shù)據(jù)的發(fā)送和接收使用 send()
和 recv()
。
二、socket/listen/accept與TCB的關(guān)系
下面將詳細(xì)解釋在socket()
、listen()
、accept()
等函數(shù)調(diào)用過(guò)程中,TCP控制塊(TCB,struct tcp_sock
)的創(chuàng)建和隊(duì)列的使用,以及它們與文件描述符(socket_fd
和 client_fd
)的關(guān)系。
1. socket()
函數(shù)調(diào)用后的TCB關(guān)聯(lián)
- 當(dāng)你調(diào)用
socket()
函數(shù)時(shí),操作系統(tǒng)會(huì)為這個(gè)套接字創(chuàng)建一個(gè)struct sock
結(jié)構(gòu)體(具體來(lái)說(shuō),如果是TCP套接字,將創(chuàng)建一個(gè)struct tcp_sock
,它是struct sock
的子類)。這個(gè)結(jié)構(gòu)體就是TCP控制塊(TCB),負(fù)責(zé)管理該套接字的所有TCP連接狀態(tài)。 - 創(chuàng)建的
sock
結(jié)構(gòu)體會(huì)與socket_fd
綁定,socket_fd
是應(yīng)用層與內(nèi)核層進(jìn)行通信的文件描述符。通過(guò)socket_fd
,內(nèi)核可以找到與之關(guān)聯(lián)的sock
結(jié)構(gòu)體。
2. listen()
函數(shù)調(diào)用后的隊(duì)列創(chuàng)建
- 當(dāng)調(diào)用
listen()
函數(shù)時(shí),TCP進(jìn)入監(jiān)聽(tīng)狀態(tài),這時(shí)在與該監(jiān)聽(tīng)套接字對(duì)應(yīng)的TCB上會(huì)創(chuàng)建兩個(gè)隊(duì)列:- 半連接隊(duì)列(Syn Queue):存放正在進(jìn)行三次握手的連接。
- 全連接隊(duì)列(Accept Queue):存放已經(jīng)完成三次握手的連接。
這些隊(duì)列用于管理TCP連接的不同狀態(tài),但隊(duì)列中的成員并不是直接的TCB(struct tcp_sock
)類型:
-
半連接隊(duì)列中的成員:是
struct request_sock
類型。request_sock
是一個(gè)輕量級(jí)的數(shù)據(jù)結(jié)構(gòu),用于在三次握手未完成時(shí)存儲(chǔ)連接請(qǐng)求的狀態(tài)信息。在接收到客戶端的SYN之后,服務(wù)端在半連接隊(duì)列中分配一個(gè)request_sock
,并等待三次握手完成。 -
全連接隊(duì)列中的成員:在三次握手完成后,內(nèi)核會(huì)從半連接隊(duì)列移除
request_sock
并創(chuàng)建一個(gè)完整的struct tcp_sock
(也稱作TCB),然后將其移入全連接隊(duì)列中,表示該連接已經(jīng)建立。
3. accept()
函數(shù)調(diào)用后
-
當(dāng)應(yīng)用程序調(diào)用
accept()
函數(shù)時(shí),內(nèi)核會(huì)從全連接隊(duì)列中取出一個(gè)已經(jīng)完成三次握手的TCP連接。 -
在全連接隊(duì)列中的成員是一個(gè)完整的
struct tcp_sock
(即TCB),它記錄了該連接的所有TCP狀態(tài)。 -
內(nèi)核會(huì)為這個(gè)新的TCP連接創(chuàng)建一個(gè)新的文件描述符,稱為
client_fd
,并將該文件描述符與這個(gè)TCP連接的TCB(struct tcp_sock
)進(jìn)行綁定。換句話說(shuō),
client_fd
與新連接的struct tcp_sock
關(guān)聯(lián)起來(lái),使得通過(guò)client_fd
可以操作該TCP連接(如發(fā)送或接收數(shù)據(jù))。
總結(jié)流程
-
socket()
: 創(chuàng)建一個(gè)struct sock
(具體為struct tcp_sock
),并與socket_fd
關(guān)聯(lián)。 -
listen()
: 在tcp_sock
上創(chuàng)建半連接隊(duì)列和全連接隊(duì)列:- 半連接隊(duì)列存放
struct request_sock
,用于管理三次握手中的連接。 - 全連接隊(duì)列存放已建立連接的
struct tcp_sock
。
- 半連接隊(duì)列存放
-
accept()
: 從全連接隊(duì)列中取出一個(gè)struct tcp_sock
,為它分配一個(gè)新的文件描述符client_fd
,并將client_fd
與這個(gè)TCP連接的TCB(struct tcp_sock
)綁定。
因此,調(diào)用accept()
后,全連接隊(duì)列中的TCP連接會(huì)與新的client_fd
關(guān)聯(lián),應(yīng)用程序通過(guò)client_fd
來(lái)處理這個(gè)TCP連接。
三、listen函數(shù)backlog的作用
listen()
函數(shù)的backlog
參數(shù)在TCP服務(wù)器中用于指定全連接隊(duì)列(Accept Queue)的最大長(zhǎng)度,即允許在服務(wù)器上排隊(duì)等待accept()
的已建立連接的最大數(shù)量。
1. listen()
函數(shù)及 backlog
參數(shù)的作用
當(dāng)你調(diào)用listen()
函數(shù)時(shí),服務(wù)器的套接字進(jìn)入監(jiān)聽(tīng)狀態(tài),開(kāi)始等待客戶端的連接請(qǐng)求。backlog
參數(shù)定義了以下內(nèi)容:
- 最大已完成連接數(shù):
backlog
參數(shù)指定全連接隊(duì)列的最大長(zhǎng)度,即已經(jīng)完成三次握手但尚未被應(yīng)用程序accept()
取走的連接數(shù)。 - 當(dāng)客戶端發(fā)起連接請(qǐng)求并完成了三次握手,連接會(huì)被放入全連接隊(duì)列。如果隊(duì)列已滿,新完成的連接將被拒絕,客戶端會(huì)收到TCP RST(復(fù)位)信號(hào),表示連接無(wú)法建立。
2. backlog
參數(shù)的工作機(jī)制
在listen(sockfd, backlog)
中:
- 全連接隊(duì)列(Accept Queue) 存放的是已經(jīng)完成三次握手、處于
ESTABLISHED
狀態(tài)的連接,這些連接等待應(yīng)用程序調(diào)用accept()
來(lái)處理。 - 半連接隊(duì)列(Syn Queue) 管理尚未完全建立的連接(正在三次握手中的連接),它與
backlog
關(guān)系較小,主要受tcp_max_syn_backlog
內(nèi)核參數(shù)的影響。
具體行為:
- 當(dāng)全連接隊(duì)列中的連接數(shù)達(dá)到
backlog
限制時(shí),新完成的連接將無(wú)法進(jìn)入隊(duì)列,導(dǎo)致客戶端收到RST包,連接被拒絕。 - 如果設(shè)置的
backlog
值太小,服務(wù)器可能無(wú)法處理高并發(fā)連接,導(dǎo)致連接請(qǐng)求頻繁被拒絕。 - 如果設(shè)置的
backlog
值過(guò)大,可能會(huì)增加系統(tǒng)負(fù)擔(dān),尤其是在沒(méi)有足夠的資源或處理能力時(shí)。
3. backlog
參數(shù)的實(shí)際值
-
雖然應(yīng)用程序可以指定
backlog
的大小,但內(nèi)核實(shí)際上會(huì)對(duì)該值進(jìn)行限制。 -
Linux內(nèi)核中有一個(gè)參數(shù)
somaxconn
,它定義了允許的最大backlog
值。如果你在listen()
中傳入的backlog
值大于/proc/sys/net/core/somaxconn
中設(shè)定的值,系統(tǒng)會(huì)將backlog
限制為somaxconn
的值。- 查看和調(diào)整
somaxconn
參數(shù):cat /proc/sys/net/core/somaxconn echo 1024 > /proc/sys/net/core/somaxconn
- 查看和調(diào)整
4. 實(shí)際例子
假設(shè)你調(diào)用了如下的listen()
函數(shù):
listen(sockfd, 10);
- 這意味著全連接隊(duì)列的長(zhǎng)度最大為10,即最多允許10個(gè)已經(jīng)完成三次握手的連接排隊(duì)等待
accept()
。 - 如果第11個(gè)連接嘗試建立,服務(wù)器將返回TCP RST包,拒絕該連接。
5. 總結(jié)
backlog
參數(shù)用于指定服務(wù)器上全連接隊(duì)列的最大長(zhǎng)度,即等待應(yīng)用層accept()
調(diào)用的已建立連接數(shù)的最大值。- 過(guò)小的
backlog
值會(huì)導(dǎo)致高并發(fā)時(shí)連接被拒絕,而過(guò)大的值會(huì)增加系統(tǒng)資源占用,需根據(jù)系統(tǒng)處理能力合理設(shè)置。
四、半連接隊(duì)列的限制
在 TCP 服務(wù)器中,半連接隊(duì)列的數(shù)量(即 SYN 隊(duì)列)由內(nèi)核的 tcp_max_syn_backlog
參數(shù)控制。
1. 半連接隊(duì)列(SYN隊(duì)列):
- 當(dāng)客戶端向服務(wù)器發(fā)送 SYN 請(qǐng)求時(shí),服務(wù)器將這個(gè)連接請(qǐng)求放入 半連接隊(duì)列(也稱為 SYN 隊(duì)列)。此隊(duì)列用于存儲(chǔ)尚未完成三次握手的連接。
- 一旦握手完成并且服務(wù)器準(zhǔn)備好接受數(shù)據(jù),連接就會(huì)移入 全連接隊(duì)列(Accept Queue)。
2. tcp_max_syn_backlog
參數(shù):
- 作用: 控制半連接隊(duì)列的最大長(zhǎng)度,即可以緩存的未完成三次握手的連接數(shù)。
- 默認(rèn)值: 在大多數(shù) Linux 系統(tǒng)中,默認(rèn)值通常為 128,意味著最多可以緩存 128 個(gè)尚未完成三次握手的連接。
- 調(diào)整: 可以通過(guò)修改
/proc/sys/net/ipv4/tcp_max_syn_backlog
文件來(lái)調(diào)整此值。例如:
或者在echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
sysctl.conf
中添加:net.ipv4.tcp_max_syn_backlog=2048
3. SYN 隊(duì)列溢出:
- 如果半連接隊(duì)列已滿并且有新的 SYN 請(qǐng)求到達(dá),內(nèi)核會(huì)丟棄這些連接請(qǐng)求,通??蛻舳藭?huì)收到一個(gè) TCP RST(重置) 消息,或者如果客戶端重試,可能會(huì)延遲連接。
- 為了避免此情況,通常需要根據(jù)實(shí)際的網(wǎng)絡(luò)負(fù)載來(lái)調(diào)整該參數(shù),尤其是在高并發(fā)的服務(wù)器上。
4. 全連接隊(duì)列:
- 在調(diào)用
listen()
函數(shù)時(shí),backlog
參數(shù)設(shè)置的是 全連接隊(duì)列 的大小,即已完成三次握手的連接的最大數(shù)量。它并不直接影響半連接隊(duì)列的大小。 - 如果 全連接隊(duì)列 已滿,
accept()
會(huì)阻塞,直到隊(duì)列中有空間為止。
總結(jié):
- 半連接隊(duì)列(SYN 隊(duì)列)的大小是由
tcp_max_syn_backlog
參數(shù)控制。 - 全連接隊(duì)列(Accept Queue)的大小是由
listen()
函數(shù)的backlog
參數(shù)控制。
因此,半連接隊(duì)列和全連接隊(duì)列的長(zhǎng)度由不同的參數(shù)控制,而服務(wù)器需要根據(jù)實(shí)際的負(fù)載情況合理配置這些參數(shù),以確保高并發(fā)時(shí)的連接性能和穩(wěn)定性。
五、send函數(shù)的第四個(gè)參數(shù)是什么作用
send()
函數(shù)的第四個(gè)參數(shù)是**flags
**,用于指定發(fā)送操作的行為。通過(guò)設(shè)置不同的標(biāo)志,應(yīng)用程序可以控制send()
函數(shù)的具體行為。
send()
函數(shù)的原型
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
:目標(biāo)套接字的文件描述符。buf
:要發(fā)送的數(shù)據(jù)的緩沖區(qū)。len
:要發(fā)送的數(shù)據(jù)長(zhǎng)度。flags
:控制發(fā)送行為的標(biāo)志位(即第四個(gè)參數(shù))。
常用的 flags
值
以下是一些常用的標(biāo)志及其作用,它們可以組合使用(使用按位或操作符 |
):
-
MSG_DONTWAIT
:- 使
send()
成為非阻塞操作。如果套接字的發(fā)送緩沖區(qū)已滿,send()
不會(huì)等待緩沖區(qū)空閑,而是立即返回,返回值為-1
,并設(shè)置errno
為EAGAIN
或EWOULDBLOCK
。 - 適用于非阻塞套接字,也可以臨時(shí)使阻塞套接字表現(xiàn)為非阻塞模式。
- 使
-
MSG_OOB
(Out-of-Band Data):- 發(fā)送緊急數(shù)據(jù)(帶外數(shù)據(jù)),僅適用于TCP協(xié)議。緊急數(shù)據(jù)會(huì)優(yōu)先于普通數(shù)據(jù)處理,但在實(shí)際應(yīng)用中,帶外數(shù)據(jù)的使用較少。
- 常用于一些需要快速響應(yīng)的特殊場(chǎng)景。
-
MSG_NOSIGNAL
:- 如果向已斷開(kāi)的連接發(fā)送數(shù)據(jù),通常會(huì)觸發(fā)
SIGPIPE
信號(hào),導(dǎo)致程序終止。使用該標(biāo)志可以抑制SIGPIPE
信號(hào),防止程序崩潰。 - 適用于需要處理網(wǎng)絡(luò)中斷且不希望信號(hào)干擾的場(chǎng)景。
- 如果向已斷開(kāi)的連接發(fā)送數(shù)據(jù),通常會(huì)觸發(fā)
-
MSG_CONFIRM
:- 僅適用于基于某些協(xié)議(如UDP)的發(fā)送,表示希望確認(rèn)對(duì)端的存在,通常用于實(shí)現(xiàn)鏈路層的鄰居確認(rèn)。
- 僅用于某些低層協(xié)議的特定場(chǎng)景,在常規(guī)TCP/UDP應(yīng)用中較少使用。
-
MSG_DONTROUTE
:- 發(fā)送數(shù)據(jù)時(shí),不查找路由表,直接將數(shù)據(jù)發(fā)送到與目標(biāo)網(wǎng)絡(luò)直接相連的接口。通常用于網(wǎng)絡(luò)診斷和本地網(wǎng)絡(luò)通信的場(chǎng)景。
- 在大多數(shù)普通應(yīng)用場(chǎng)景中很少使用。
-
MSG_EOR
(End of Record):- 僅用于某些基于記錄的協(xié)議,表示本次
send()
調(diào)用發(fā)送的數(shù)據(jù)是一個(gè)邏輯記錄的結(jié)束。 - 對(duì)于常見(jiàn)的TCP或UDP通信,這個(gè)標(biāo)志不常用。
- 僅用于某些基于記錄的協(xié)議,表示本次
-
MSG_MORE
:- 表示應(yīng)用程序還有更多的數(shù)據(jù)要發(fā)送。在某些協(xié)議(如TCP)中,使用該標(biāo)志時(shí),內(nèi)核會(huì)暫時(shí)將數(shù)據(jù)保留在緩沖區(qū)中,而不是立即發(fā)送,以減少網(wǎng)絡(luò)上的包數(shù)。
- 適合分多次發(fā)送數(shù)據(jù),但希望減少網(wǎng)絡(luò)開(kāi)銷的場(chǎng)景。
示例:使用 MSG_DONTWAIT
和 MSG_NOSIGNAL
char message[] = "Hello, World!";
int result = send(sockfd, message, sizeof(message), MSG_DONTWAIT | MSG_NOSIGNAL);if (result == -1) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 緩沖區(qū)已滿,發(fā)送失敗printf("Send would block, try again later.\n");} else {// 處理其他錯(cuò)誤perror("send");}
}
總結(jié)
send()
函數(shù)的第四個(gè)參數(shù)flags
用于控制發(fā)送操作的行為。常見(jiàn)的標(biāo)志包括MSG_DONTWAIT
(非阻塞發(fā)送)、MSG_OOB
(發(fā)送緊急數(shù)據(jù))、MSG_NOSIGNAL
(避免SIGPIPE
信號(hào))等。你可以根據(jù)具體應(yīng)用場(chǎng)景使用不同的標(biāo)志來(lái)改變send()
的默認(rèn)行為。
六、為什么握手要三次,揮手要四次,揮手中間的兩次不能像握手那樣合并在一起嗎?
在TCP協(xié)議的三次握手和四次揮手過(guò)程中,雖然在三次握手時(shí)可以將SYN
和ACK
合并到一個(gè)數(shù)據(jù)包發(fā)送,但在四次揮手過(guò)程中,FIN
和ACK
通常不能合并到同一個(gè)數(shù)據(jù)包發(fā)送。這主要與TCP的連接狀態(tài)和雙方通信的半關(guān)閉狀態(tài)有關(guān)。
1. 三次握手(SYN 和 ACK 合并的原因)
在三次握手中,通信雙方需要同步序列號(hào),建立可靠的連接。具體過(guò)程是:
- 第一次握手:客戶端發(fā)送一個(gè)
SYN
包,表示請(qǐng)求建立連接,并傳遞初始序列號(hào)。 - 第二次握手:服務(wù)器收到
SYN
后,回復(fù)一個(gè)包含SYN
和ACK
的包。這里的ACK
是對(duì)客戶端SYN
的確認(rèn),而SYN
則是服務(wù)器請(qǐng)求建立連接的信號(hào)。因?yàn)?code>SYN和ACK
是針對(duì)不同的動(dòng)作(SYN
是服務(wù)器發(fā)起的,而ACK
是對(duì)客戶端請(qǐng)求的確認(rèn)),可以一起合并發(fā)送。 - 第三次握手:客戶端收到后,發(fā)送
ACK
確認(rèn),連接建立。
這里之所以可以合并,是因?yàn)殡p方的狀態(tài)在邏輯上是同步的,服務(wù)器既要發(fā)出自己的SYN
,又要確認(rèn)客戶端的SYN
,可以一起處理。
2. 四次揮手(ACK 和 FIN 通常不能合并的原因)
四次揮手過(guò)程用于關(guān)閉TCP連接,具體如下:
- 第一次揮手:客戶端發(fā)送一個(gè)
FIN
包,表示它要關(guān)閉連接(數(shù)據(jù)傳輸結(jié)束)。 - 第二次揮手:服務(wù)器收到后,回復(fù)一個(gè)
ACK
,表示收到了客戶端的FIN
請(qǐng)求,但服務(wù)器可能還在發(fā)送數(shù)據(jù)。 - 第三次揮手:服務(wù)器發(fā)送完數(shù)據(jù)后,再發(fā)送一個(gè)
FIN
包,表示它也同意關(guān)閉連接。 - 第四次揮手:客戶端收到服務(wù)器的
FIN
后,發(fā)送一個(gè)ACK
包,確認(rèn)關(guān)閉。
原因:
-
連接的半關(guān)閉狀態(tài): 在四次揮手過(guò)程中,TCP協(xié)議允許連接進(jìn)入半關(guān)閉狀態(tài),即:
- 當(dāng)客戶端發(fā)送
FIN
請(qǐng)求時(shí),意味著客戶端已經(jīng)不再發(fā)送數(shù)據(jù),但服務(wù)器還可以繼續(xù)發(fā)送未完成的數(shù)據(jù)。 - 客戶端發(fā)送的
FIN
和服務(wù)器接收的ACK
是兩個(gè)不同的操作,它們代表了不同的狀態(tài)。
在這個(gè)階段,服務(wù)器回復(fù)的
ACK
只是表明收到了客戶端的FIN
,但服務(wù)器還沒(méi)有準(zhǔn)備好關(guān)閉連接,因?yàn)榭赡苋匀挥袛?shù)據(jù)需要發(fā)送。如果此時(shí)合并ACK
和FIN
,就意味著服務(wù)器已經(jīng)準(zhǔn)備好關(guān)閉連接了,但實(shí)際上它可能還沒(méi)有完成數(shù)據(jù)發(fā)送。 - 當(dāng)客戶端發(fā)送
-
不同的時(shí)間點(diǎn):
ACK
和FIN
通常不會(huì)在同一時(shí)刻發(fā)生:- 客戶端發(fā)
FIN
后,服務(wù)器需要立即回復(fù)一個(gè)ACK
,但是服務(wù)器可能還在發(fā)送數(shù)據(jù),并未準(zhǔn)備好關(guān)閉連接。 - 只有當(dāng)服務(wù)器確認(rèn)所有數(shù)據(jù)發(fā)送完畢后,它才會(huì)發(fā)送
FIN
來(lái)關(guān)閉連接。這兩個(gè)操作通常在不同的時(shí)間點(diǎn)發(fā)生,無(wú)法合并。
- 客戶端發(fā)
-
確保數(shù)據(jù)完整性: 在四次揮手中,分開(kāi)
ACK
和FIN
的發(fā)送有助于確保所有數(shù)據(jù)都能成功傳輸完畢。服務(wù)器通過(guò)先發(fā)送ACK
確認(rèn)收到客戶端的關(guān)閉請(qǐng)求,并在數(shù)據(jù)發(fā)送完畢后才發(fā)送FIN
,可以避免數(shù)據(jù)丟失或中途終止傳輸。
3. 總結(jié)
- 在三次握手中,
SYN
和ACK
可以合并到一個(gè)數(shù)據(jù)包中發(fā)送,因?yàn)樗鼈冊(cè)谶壿嬌鲜遣⑿械牟僮?#xff0c;且是在同一時(shí)刻發(fā)送的。 - 在四次揮手中,
ACK
和FIN
不能合并發(fā)送,因?yàn)樗鼈兺ǔ0l(fā)生在不同的時(shí)間點(diǎn),表示不同的狀態(tài)轉(zhuǎn)換。ACK
是對(duì)接收方收到FIN
的確認(rèn),而FIN
是表示發(fā)送方準(zhǔn)備完全關(guān)閉連接,這兩者之間可能存在數(shù)據(jù)傳輸?shù)难舆t,因此分開(kāi)發(fā)送有助于確保傳輸?shù)目煽啃院屯暾浴?/li>
4. close/shutdown與揮手報(bào)文的關(guān)系:
? ? ? ? 當(dāng)recv函數(shù)返回0時(shí)表示收到了對(duì)方的FIN報(bào)文,此時(shí)close()調(diào)用后,會(huì)直接發(fā)出ACK + FIN。
但是用shutdown(sockfd, SHUT_RD)后只會(huì)發(fā)出 ACK,不會(huì)給發(fā)出FIN,還可以接著給對(duì)方發(fā)送數(shù)據(jù)。
七、shutdown函數(shù) 與 FIN 報(bào)文
是的,調(diào)用shutdown()
函數(shù)時(shí),根據(jù)調(diào)用參數(shù),TCP連接可以發(fā)送FIN報(bào)文,但這取決于shutdown()
的具體使用方式。
1. shutdown()
函數(shù)的作用
shutdown()
函數(shù)用于部分或完全關(guān)閉一個(gè)已經(jīng)建立的TCP連接。它不同于close()
函數(shù),close()
不僅會(huì)關(guān)閉連接,還會(huì)釋放文件描述符,而shutdown()
允許程序在不關(guān)閉文件描述符的情況下關(guān)閉連接的某一方向(發(fā)送或接收)。
shutdown()
函數(shù)的原型:
int shutdown(int sockfd, int how);
其中:
sockfd
:要關(guān)閉的套接字描述符。how
:決定關(guān)閉連接的方式。其值可以是以下之一:SHUT_RD (0)
:關(guān)閉接收方向,該套接字不再能接收數(shù)據(jù)。SHUT_WR (1)
:關(guān)閉發(fā)送方向,該套接字不再能發(fā)送數(shù)據(jù),并發(fā)送FIN包。SHUT_RDWR (2)
:同時(shí)關(guān)閉發(fā)送和接收方向,等同于分別調(diào)用SHUT_RD
和SHUT_WR
。
2. FIN報(bào)文的發(fā)送
當(dāng)shutdown()
函數(shù)的how
參數(shù)為SHUT_WR
或SHUT_RDWR
時(shí),TCP協(xié)議會(huì)發(fā)送一個(gè)FIN報(bào)文,告訴對(duì)方主機(jī)發(fā)送方已經(jīng)關(guān)閉,數(shù)據(jù)發(fā)送已完成,表明不會(huì)再有更多的數(shù)據(jù)從該端發(fā)送。
詳細(xì)說(shuō)明:
-
SHUT_WR (1)
:關(guān)閉發(fā)送方向。當(dāng)調(diào)用shutdown(sockfd, SHUT_WR)
時(shí),TCP協(xié)議棧會(huì)發(fā)送一個(gè)FIN報(bào)文,表示發(fā)送端不再發(fā)送數(shù)據(jù)。之后,這一端仍然可以接收對(duì)方的數(shù)據(jù),但不能再發(fā)送任何數(shù)據(jù)。 -
SHUT_RDWR (2)
:同時(shí)關(guān)閉發(fā)送和接收方向。調(diào)用shutdown(sockfd, SHUT_RDWR)
時(shí),發(fā)送FIN,且無(wú)法再接收對(duì)方的數(shù)據(jù)。此時(shí),連接相當(dāng)于完全關(guān)閉,但文件描述符不會(huì)被釋放,應(yīng)用程序仍然可以繼續(xù)使用文件描述符做其他操作。
3. shutdown()
與close()
的區(qū)別
shutdown()
函數(shù)可以只關(guān)閉連接的一部分(如只關(guān)閉發(fā)送而保留接收),而close()
會(huì)完全關(guān)閉連接并釋放套接字文件描述符。- 在調(diào)用
close()
時(shí),如果還有數(shù)據(jù)沒(méi)有發(fā)送完,TCP協(xié)議棧會(huì)繼續(xù)嘗試發(fā)送剩余數(shù)據(jù),并最終發(fā)送FIN報(bào)文,完成四次揮手流程。
4. 典型使用場(chǎng)景
-
shutdown(sockfd, SHUT_WR)
:當(dāng)一個(gè)應(yīng)用程序完成了發(fā)送數(shù)據(jù),但仍然希望接收對(duì)方的數(shù)據(jù)時(shí),通常會(huì)調(diào)用這個(gè)函數(shù)。例如,HTTP協(xié)議中,服務(wù)器發(fā)送響應(yīng)數(shù)據(jù)后可能會(huì)調(diào)用shutdown()
來(lái)關(guān)閉發(fā)送方向,但仍然保留接收方向以讀取客戶端的請(qǐng)求。 -
shutdown(sockfd, SHUT_RDWR)
:用于完全關(guān)閉連接,類似于close()
,但不釋放文件描述符。
5. 總結(jié)
當(dāng)調(diào)用shutdown(sockfd, SHUT_WR)
或shutdown(sockfd, SHUT_RDWR)
時(shí),TCP會(huì)發(fā)送FIN報(bào)文,表示發(fā)送方已經(jīng)完成數(shù)據(jù)傳輸,關(guān)閉了發(fā)送方向。
八、bind函數(shù)端口號(hào)的設(shè)置
端口號(hào)在網(wǎng)絡(luò)協(xié)議中起著非常重要的作用,它們被用來(lái)標(biāo)識(shí)不同的服務(wù)或應(yīng)用程序。端口號(hào)可以分為兩大類:知名端口(Well-Known Ports)和動(dòng)態(tài)或私有端口(Dynamic or Private Ports)。這些端口號(hào)由 Internet Assigned Numbers Authority (IANA) 管理,確保網(wǎng)絡(luò)中的每個(gè)服務(wù)都能有唯一的端口標(biāo)識(shí)。
端口號(hào)的分類
- 知名端口(Well-Known Ports): 范圍為 0 到 1023,通常分配給操作系統(tǒng)和知名的服務(wù)協(xié)議。
- 注冊(cè)端口(Registered Ports): 范圍為 1024 到 49151,供用戶和應(yīng)用程序使用。
- 動(dòng)態(tài)或私有端口(Dynamic or Private Ports): 范圍為 49152 到 65535,通常用于臨時(shí)分配給客戶端應(yīng)用。
知名端口(0 - 1023)
這些端口通常由 IANA 分配給常用服務(wù)和協(xié)議,以下是一些常見(jiàn)的協(xié)議和對(duì)應(yīng)的端口號(hào):
端口號(hào) | 協(xié)議 / 服務(wù) | 說(shuō)明 |
---|---|---|
20 | FTP 數(shù)據(jù)傳輸(File Transfer Protocol) | 用于 FTP 數(shù)據(jù)傳輸 |
21 | FTP 控制(File Transfer Protocol) | 用于 FTP 控制連接 |
22 | SSH(Secure Shell) | 用于安全遠(yuǎn)程登錄 |
23 | Telnet | 用于非加密的遠(yuǎn)程登錄 |
25 | SMTP(Simple Mail Transfer Protocol) | 用于郵件傳輸 |
53 | DNS(Domain Name System) | 用于域名解析 |
67 | DHCP 服務(wù)器端(Dynamic Host Configuration Protocol) | 用于 DHCP 服務(wù)器 |
68 | DHCP 客戶端(Dynamic Host Configuration Protocol) | 用于 DHCP 客戶端 |
69 | TFTP(Trivial File Transfer Protocol) | 用于輕量級(jí)的文件傳輸 |
80 | HTTP(HyperText Transfer Protocol) | 用于 Web 服務(wù)(網(wǎng)頁(yè)瀏覽) |
110 | POP3(Post Office Protocol version 3) | 用于郵件接收 |
119 | NNTP(Network News Transfer Protocol) | 用于新聞組協(xié)議 |
123 | NTP(Network Time Protocol) | 用于網(wǎng)絡(luò)時(shí)間同步 |
143 | IMAP(Internet Message Access Protocol) | 用于郵件接收(替代 POP3) |
161 | SNMP(Simple Network Management Protocol) | 用于網(wǎng)絡(luò)設(shè)備管理 |
194 | IRC(Internet Relay Chat) | 用于即時(shí)聊天 |
443 | HTTPS(HyperText Transfer Protocol Secure) | 用于加密的 Web 服務(wù)(HTTPS) |
514 | Syslog | 用于網(wǎng)絡(luò)設(shè)備和操作系統(tǒng)的日志記錄 |
520 | RIP(Routing Information Protocol) | 用于路由協(xié)議 |
3389 | RDP(Remote Desktop Protocol) | 用于遠(yuǎn)程桌面訪問(wèn) |
注冊(cè)端口(1024 - 49151)
這些端口主要由軟件供應(yīng)商和開(kāi)發(fā)者為其應(yīng)用程序所使用。IANA 對(duì)這些端口進(jìn)行注冊(cè),但它們通常不屬于標(biāo)準(zhǔn)化的、固定的協(xié)議。以下是一些常見(jiàn)的服務(wù)和對(duì)應(yīng)的端口號(hào):
端口號(hào) | 協(xié)議 / 服務(wù) | 說(shuō)明 |
---|---|---|
1080 | SOCKS(SOCKS Proxy Protocol) | 用于代理服務(wù) |
1433 | Microsoft SQL Server | 用于 Microsoft SQL 數(shù)據(jù)庫(kù)服務(wù) |
3306 | MySQL | 用于 MySQL 數(shù)據(jù)庫(kù)服務(wù) |
3389 | Microsoft RDP | 用于遠(yuǎn)程桌面協(xié)議 |
5432 | PostgreSQL | 用于 PostgreSQL 數(shù)據(jù)庫(kù)服務(wù) |
5900 | VNC(Virtual Network Computing) | 用于虛擬網(wǎng)絡(luò)計(jì)算(遠(yuǎn)程桌面控制) |
8080 | HTTP(Alternative Port) | 用于 Web 服務(wù)的備用端口(HTTP) |
8888 | HTTP(Alternative Port) | 用于 Web 服務(wù)的備用端口(HTTP) |
動(dòng)態(tài)或私有端口(49152 - 65535)
這些端口通常由操作系統(tǒng)或應(yīng)用程序動(dòng)態(tài)分配給客戶端程序使用,尤其是在進(jìn)行臨時(shí)連接時(shí)。它們不固定分配給任何特定服務(wù)。通常在 TCP/IP 會(huì)話中,客戶端通過(guò)使用這些端口號(hào)連接到遠(yuǎn)程服務(wù)器的服務(wù)端口。
端口號(hào)范圍 | 說(shuō)明 |
---|---|
49152 - 65535 | 動(dòng)態(tài)端口范圍,用于臨時(shí)分配給客戶端 |
端口號(hào)的使用說(shuō)明
-
給用戶的端口號(hào):這些端口號(hào)由操作系統(tǒng)和服務(wù)程序?yàn)橛脩籼峁?#xff0c;用來(lái)執(zhí)行應(yīng)用程序或服務(wù)的訪問(wèn)。這些端口號(hào)一般需要符合特定協(xié)議,使用時(shí)需要確保沒(méi)有沖突。
- 例如:Web 服務(wù)使用 80 或 443 端口,郵件服務(wù)使用 25、110 或 143 端口。
-
給協(xié)議的端口號(hào):協(xié)議端口號(hào)由 IANA(Internet Assigned Numbers Authority)分配,用于區(qū)分不同的網(wǎng)絡(luò)協(xié)議和服務(wù)。許多常見(jiàn)的協(xié)議和服務(wù)有固定的端口號(hào),比如 HTTP(80)、FTP(21)、SSH(22)等。
-
特定協(xié)議的端口號(hào):許多協(xié)議和應(yīng)用程序會(huì)規(guī)定固定的端口號(hào),用于指定特定的服務(wù)。例如:
- HTTP/HTTPS 協(xié)議默認(rèn)使用端口 80 和 443。
- FTP 使用端口 21 進(jìn)行控制連接,端口 20 用于數(shù)據(jù)連接。
- SMTP 使用端口 25 發(fā)送郵件,POP3 使用端口 110 接收郵件。
-
動(dòng)態(tài)分配端口:客戶端與服務(wù)器建立連接時(shí),通常會(huì)使用動(dòng)態(tài)端口(范圍 49152 到 65535)。例如,在 HTTP 請(qǐng)求中,客戶端使用隨機(jī)分配的端口號(hào)連接服務(wù)器的端口 80 或 443。
端口號(hào)的重要性
- 網(wǎng)絡(luò)服務(wù)和協(xié)議標(biāo)識(shí):端口號(hào)幫助操作系統(tǒng)區(qū)分不同的網(wǎng)絡(luò)協(xié)議和服務(wù),使得同一臺(tái)機(jī)器可以同時(shí)提供多個(gè)不同的服務(wù)。
- 安全性考慮:某些服務(wù)使用的端口號(hào)可能存在安全漏洞,因此安全防護(hù)設(shè)備(如防火墻)通常會(huì)對(duì)端口號(hào)進(jìn)行過(guò)濾,阻止不安全的端口。
- 端口掃描:攻擊者通常通過(guò)端口掃描來(lái)查找開(kāi)放的端口和運(yùn)行的服務(wù),進(jìn)而尋找潛在的攻擊入口。
總結(jié)
- 端口號(hào)是網(wǎng)絡(luò)通信中的重要組成部分,允許不同的服務(wù)和應(yīng)用程序在同一臺(tái)機(jī)器上并行運(yùn)行。
- 端口號(hào)分為知名端口、注冊(cè)端口和動(dòng)態(tài)端口,分別用于系統(tǒng)服務(wù)、應(yīng)用程序服務(wù)和臨時(shí)連接。
- 各種協(xié)議和服務(wù)使用不同的端口號(hào),IANA 負(fù)責(zé)管理這些端口號(hào)的分配。
九、TCP系統(tǒng)調(diào)用函數(shù)的 -- 阻塞性
在 TCP 編程中,某些網(wǎng)絡(luò)操作可能會(huì)阻塞,即函數(shù)在沒(méi)有完成操作之前會(huì)等待特定條件的發(fā)生。這些函數(shù)通常用于執(zhí)行需要等待數(shù)據(jù)到達(dá)、連接建立、或者連接關(guān)閉等操作的任務(wù)。阻塞行為通常與網(wǎng)絡(luò)狀態(tài)、系統(tǒng)資源、以及協(xié)議本身的特性相關(guān)。
以下是一些常見(jiàn)的會(huì)阻塞的 TCP 相關(guān)函數(shù),以及它們?yōu)槭裁磿?huì)阻塞:
1. accept()
- 阻塞原因:
accept()
用于在服務(wù)器端接受一個(gè)已經(jīng)完成三次握手的連接請(qǐng)求。如果沒(méi)有等待的連接,它會(huì)阻塞,直到有客戶端發(fā)起連接請(qǐng)求。 - 何時(shí)阻塞: 當(dāng)沒(méi)有客戶端連接請(qǐng)求到達(dá)時(shí),
accept()
會(huì)阻塞,直到有連接請(qǐng)求到來(lái)。 - 代碼示例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080);bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(sockfd, 5);int client_sock = accept(sockfd, NULL, NULL); // 阻塞直到有連接到來(lái) if (client_sock == -1) {perror("accept"); }
2. recv()
/ recvfrom()
/ read()
- 阻塞原因: 這些函數(shù)用于從套接字中接收數(shù)據(jù)。如果沒(méi)有數(shù)據(jù)可讀,它們會(huì)阻塞,直到有數(shù)據(jù)可用。
recv()
在默認(rèn)情況下會(huì)阻塞,直到接收到至少一個(gè)字節(jié)的數(shù)據(jù)。 - 何時(shí)阻塞: 如果緩沖區(qū)中沒(méi)有數(shù)據(jù)(例如,客戶端沒(méi)有發(fā)送數(shù)據(jù)),則會(huì)阻塞等待數(shù)據(jù)的到來(lái)。
- 代碼示例:
char buffer[1024]; int bytes_received = recv(client_sock, buffer, sizeof(buffer), 0); // 阻塞直到數(shù)據(jù)到達(dá) if (bytes_received == -1) {perror("recv"); }
3. send()
/ sendto()
/ write()
- 阻塞原因: 如果發(fā)送緩沖區(qū)已滿,
send()
或write()
可能會(huì)阻塞,直到發(fā)送緩沖區(qū)有足夠的空間來(lái)存儲(chǔ)數(shù)據(jù)。特別是在網(wǎng)絡(luò)擁堵或者接收方的速度跟不上發(fā)送速度時(shí),發(fā)送函數(shù)可能會(huì)阻塞。 - 何時(shí)阻塞: 當(dāng)套接字處于阻塞模式且發(fā)送緩沖區(qū)已滿時(shí),
send()
或write()
會(huì)阻塞,直到緩沖區(qū)有空間。 - 代碼示例:
const char *msg = "Hello, Client!"; int bytes_sent = send(client_sock, msg, strlen(msg), 0); // 阻塞直到數(shù)據(jù)被發(fā)送 if (bytes_sent == -1) {perror("send"); }
4. connect()
- 阻塞原因:
connect()
用于客戶端與服務(wù)器建立 TCP 連接。如果服務(wù)器沒(méi)有響應(yīng)或不可達(dá),connect()
會(huì)阻塞,直到連接成功建立或者超時(shí)。 - 何時(shí)阻塞: 如果沒(méi)有可用的遠(yuǎn)程服務(wù)器響應(yīng)或服務(wù)器未準(zhǔn)備好接收連接,
connect()
會(huì)阻塞,直到連接成功或失敗。 - 代碼示例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8080); server_addr.sin_addr.s_addr = inet_addr("192.168.1.100");int result = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 阻塞直到連接成功 if (result == -1) {perror("connect"); }
5. listen()
- 阻塞原因:
listen()
是在 TCP 服務(wù)器端調(diào)用的,用于將套接字設(shè)為監(jiān)聽(tīng)模式,等待客戶端的連接請(qǐng)求。它本身不會(huì)阻塞,但會(huì)準(zhǔn)備好接收連接。在后續(xù)調(diào)用accept()
時(shí),才會(huì)阻塞。 - 何時(shí)阻塞:
listen()
本身不會(huì)阻塞,但它為accept()
阻塞操作做好準(zhǔn)備。 - 代碼示例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(8080);bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); listen(sockfd, 5); // 為后續(xù)的accept準(zhǔn)備
6. shutdown()
- 阻塞原因:
shutdown()
可以關(guān)閉套接字的某些操作(如讀、寫(xiě)),并等待數(shù)據(jù)的完全傳輸或清理。如果你調(diào)用shutdown()
來(lái)關(guān)閉寫(xiě)操作,它可能會(huì)阻塞,直到 TCP 將所有待發(fā)送的數(shù)據(jù)發(fā)送完畢。 - 何時(shí)阻塞: 如果套接字有未發(fā)送的數(shù)據(jù)需要傳輸,
shutdown()
會(huì)阻塞,直到數(shù)據(jù)傳輸完畢。 - 代碼示例:
int result = shutdown(sockfd, SHUT_WR); // 關(guān)閉寫(xiě)端,阻塞直到所有數(shù)據(jù)被發(fā)送 if (result == -1) {perror("shutdown"); }
為什么阻塞?
TCP 是一種面向連接、可靠的數(shù)據(jù)傳輸協(xié)議,它保證數(shù)據(jù)的可靠交付,確保所有數(shù)據(jù)包按照順序到達(dá)目的地,并通過(guò)流量控制、擁塞控制等機(jī)制避免網(wǎng)絡(luò)過(guò)載。為確保這些特性,某些操作需要等待特定事件的發(fā)生,導(dǎo)致阻塞:
- 等待數(shù)據(jù)到達(dá):如在調(diào)用
recv()
時(shí),系統(tǒng)必須等待數(shù)據(jù)從遠(yuǎn)程主機(jī)傳輸?shù)奖镜亍?/li> - 等待連接建立:如在
accept()
和connect()
中,系統(tǒng)必須等待對(duì)方準(zhǔn)備好接收或發(fā)起連接。 - 緩沖區(qū)未滿:如在發(fā)送數(shù)據(jù)時(shí),如果發(fā)送緩沖區(qū)已滿,系統(tǒng)會(huì)等待緩沖區(qū)騰出空間來(lái)進(jìn)行數(shù)據(jù)傳輸。
如何避免阻塞?
-
非阻塞模式:可以將套接字設(shè)置為非阻塞模式,在這種模式下,調(diào)用函數(shù)不會(huì)阻塞。如果操作無(wú)法立即完成,它會(huì)返回
EAGAIN
或EWOULDBLOCK
錯(cuò)誤,應(yīng)用程序可以做其他事情或稍后再試。- 代碼示例:
int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); // 設(shè)置套接字為非阻塞模式
- 代碼示例:
-
超時(shí)設(shè)置:對(duì)于
connect()
、recv()
等函數(shù),可以設(shè)置超時(shí)時(shí)間,避免長(zhǎng)時(shí)間阻塞。比如可以使用select()
或poll()
來(lái)實(shí)現(xiàn)超時(shí)檢測(cè)。 -
多線程或異步 I/O:在多線程程序中,阻塞的操作可以放在單獨(dú)的線程中執(zhí)行,主線程繼續(xù)進(jìn)行其他任務(wù)。使用異步 I/O 也是一種避免阻塞的方式,特別是在高性能網(wǎng)絡(luò)應(yīng)用中。
總結(jié)
TCP 編程中,許多函數(shù)會(huì)阻塞,尤其是與連接、數(shù)據(jù)接收和發(fā)送相關(guān)的操作。accept()
、recv()
、send()
和 connect()
等函數(shù)在沒(méi)有數(shù)據(jù)或連接不可用時(shí)會(huì)阻塞,直到特定條件滿足。為了避免阻塞,開(kāi)發(fā)者可以使用非阻塞模式、超時(shí)機(jī)制或多線程來(lái)處理阻塞操作。
十、調(diào)用返回失敗的情況分析
在 TCP 編程中,許多常用的函數(shù)會(huì)返回失敗的情況,尤其是在網(wǎng)絡(luò)條件不理想或系統(tǒng)資源不足時(shí)。了解每個(gè)函數(shù)返回失敗時(shí)的具體錯(cuò)誤原因非常重要,這有助于調(diào)試和錯(cuò)誤處理。以下是對(duì)常見(jiàn)的 TCP 函數(shù)返回失敗時(shí)的錯(cuò)誤情況的詳細(xì)說(shuō)明:
1. accept()
accept()
用于接受客戶端連接。當(dāng)調(diào)用失敗時(shí),返回值為 -1
,并設(shè)置 errno
以指示具體的錯(cuò)誤原因。
EINVAL
: 如果套接字未正確綁定(如bind()
未調(diào)用)或者套接字類型不支持accept()
(例如,UDP 套接字),則返回此錯(cuò)誤。ECONNABORTED
: 如果先前的連接被中止,accept()
返回此錯(cuò)誤。EFAULT
: 傳遞給accept()
的地址指針無(wú)效。EINTR
: 系統(tǒng)調(diào)用被信號(hào)中斷。accept()
被中斷時(shí)返回該錯(cuò)誤。
2. recv()
/ recvfrom()
/ read()
這些函數(shù)用于從 TCP 套接字接收數(shù)據(jù)。當(dāng)返回 -1
時(shí),表示出現(xiàn)錯(cuò)誤,errno
將設(shè)置為相應(yīng)的錯(cuò)誤代碼。0時(shí)表示對(duì)方已經(jīng)斷開(kāi)連接。
EAGAIN
或EWOULDBLOCK
: 套接字被設(shè)置為非阻塞模式,且沒(méi)有數(shù)據(jù)可用時(shí)返回該錯(cuò)誤。EBADF
: 套接字無(wú)效,可能是已經(jīng)關(guān)閉或未正確初始化。EINTR
: 系統(tǒng)調(diào)用被信號(hào)中斷。操作在信號(hào)處理程序執(zhí)行后被中斷,導(dǎo)致recv()
返回失敗。ENOTCONN
: 套接字未連接,調(diào)用recv()
時(shí),TCP 套接字未完成連接。ECONNRESET
: 對(duì)方主機(jī)強(qiáng)制關(guān)閉連接,TCP 連接被重置,導(dǎo)致接收操作失敗。ENOTSOCK
: 目標(biāo)文件描述符不是一個(gè)套接字。EFAULT
: 提供的緩沖區(qū)地址無(wú)效。
3. send()
/ sendto()
/ write()
這些函數(shù)用于向 TCP 套接字發(fā)送數(shù)據(jù),失敗時(shí)返回 -1
,并設(shè)置 errno
。
EAGAIN
或EWOULDBLOCK
: 套接字被設(shè)置為非阻塞模式,且發(fā)送緩沖區(qū)已滿,無(wú)法繼續(xù)發(fā)送數(shù)據(jù)。EBADF
: 套接字無(wú)效,可能是已經(jīng)關(guān)閉或未正確初始化。EINTR
: 系統(tǒng)調(diào)用被信號(hào)中斷,導(dǎo)致send()
被中斷。ENOTCONN
: 套接字未連接時(shí)調(diào)用send()
會(huì)失敗。ECONNRESET
: 對(duì)方主機(jī)強(qiáng)制關(guān)閉連接,導(dǎo)致連接重置,發(fā)送操作失敗。ENOTSOCK
: 目標(biāo)文件描述符不是一個(gè)套接字。EPIPE
: 當(dāng)發(fā)送數(shù)據(jù)到一個(gè)已經(jīng)關(guān)閉的連接時(shí)返回此錯(cuò)誤,表示對(duì)方已經(jīng)關(guān)閉了連接,寫(xiě)入操作失敗。
4. connect()
connect()
用于客戶端建立與服務(wù)器的連接。如果返回值是 -1
,則表示連接失敗,errno
會(huì)被設(shè)置為特定錯(cuò)誤值。
ECONNREFUSED
: 目標(biāo)服務(wù)器拒絕連接。通常是目標(biāo)服務(wù)器未啟動(dòng)或未監(jiān)聽(tīng)指定的端口。ETIMEDOUT
: 連接請(qǐng)求超時(shí)。在指定時(shí)間內(nèi)沒(méi)有完成連接。EINPROGRESS
: 如果套接字是非阻塞模式且連接正在進(jìn)行中,這個(gè)錯(cuò)誤會(huì)發(fā)生。不是錯(cuò)誤,表示連接正在進(jìn)行。EAGAIN
: 套接字設(shè)置為非阻塞模式時(shí),連接嘗試會(huì)立即返回EAGAIN
錯(cuò)誤,表示無(wú)法立即連接。EADDRINUSE
: 本地地址已在使用中,無(wú)法為新連接分配。ENETUNREACH
: 網(wǎng)絡(luò)不可達(dá),可能是由于路由或網(wǎng)絡(luò)配置問(wèn)題。EHOSTUNREACH
: 主機(jī)不可達(dá),通常由于目標(biāo)主機(jī)未開(kāi)機(jī)或網(wǎng)絡(luò)不可達(dá)。ENOTSOCK
: 目標(biāo)文件描述符不是一個(gè)套接字。
5. listen()
listen()
用于在服務(wù)器端啟動(dòng)監(jiān)聽(tīng)。失敗時(shí)返回 -1
,并設(shè)置 errno
。
EADDRINUSE
: 如果指定的端口已被其他應(yīng)用程序占用,listen()
會(huì)失敗并返回此錯(cuò)誤。EINVAL
: 如果套接字不是流式套接字(例如 UDP 套接字),則會(huì)發(fā)生此錯(cuò)誤。ENOTSOCK
: 傳入的文件描述符不是套接字。
6. shutdown()
shutdown()
用于關(guān)閉套接字的讀寫(xiě)操作。如果返回 -1
,則表示操作失敗,errno
被設(shè)置為錯(cuò)誤值。
EBADF
: 套接字無(wú)效,可能是已經(jīng)關(guān)閉或者未正確初始化。EINTR
: 系統(tǒng)調(diào)用被信號(hào)中斷,shutdown()
被中斷。ENOTSOCK
: 目標(biāo)文件描述符不是一個(gè)套接字。
7. fcntl()
fcntl()
用于獲取或設(shè)置套接字的屬性,如設(shè)置非阻塞模式等。如果返回 -1
,表示操作失敗,errno
被設(shè)置為錯(cuò)誤碼。
EBADF
: 套接字無(wú)效,可能是已經(jīng)關(guān)閉或未正確初始化。EINVAL
: 無(wú)效的命令或參數(shù)。ENOTTY
: 非法的文件描述符類型,不支持該操作。
8. bind()
bind()
用于將套接字與本地地址(IP 和端口)綁定。如果返回 -1
,表示綁定失敗,errno
被設(shè)置為特定錯(cuò)誤碼。
EADDRINUSE
: 地址已被使用,無(wú)法綁定。EADDRNOTAVAIL
: 本地地址不可用,可能由于沒(méi)有該網(wǎng)絡(luò)接口或地址配置問(wèn)題。EBADF
: 套接字無(wú)效。EINVAL
: 無(wú)效的套接字類型,通常是由于套接字類型和協(xié)議不匹配。
總結(jié)
了解這些 TCP 函數(shù)返回失敗時(shí)的錯(cuò)誤原因非常重要,有助于調(diào)試和錯(cuò)誤處理。一般情況下,當(dāng)函數(shù)返回 -1
時(shí),errno
會(huì)提供失敗的詳細(xì)信息。開(kāi)發(fā)者應(yīng)該根據(jù)不同的錯(cuò)誤代碼進(jìn)行適當(dāng)?shù)腻e(cuò)誤處理,例如通過(guò)重試、記錄日志、關(guān)閉套接字等方式來(lái)恢復(fù)網(wǎng)絡(luò)操作,確保程序的健壯性。
十一、recv返回0時(shí)的詳細(xì)說(shuō)明
在 TCP 編程中,recv()
函數(shù)的返回值為 0
是一個(gè)非常重要的情況,它表示 對(duì)方關(guān)閉了連接。這個(gè)情況常常被用來(lái)判斷連接是否已經(jīng)正常關(guān)閉。
recv()
返回0
:- 當(dāng)調(diào)用
recv()
時(shí),如果返回值是0
,這并不表示錯(cuò)誤,而是表示連接已經(jīng)被對(duì)方關(guān)閉(也就是對(duì)方發(fā)送了一個(gè) TCP FIN 包 來(lái)終止連接),并且沒(méi)有更多的數(shù)據(jù)可接收。 - 這個(gè)返回值表示對(duì)方已經(jīng)優(yōu)雅地關(guān)閉了連接,并且沒(méi)有數(shù)據(jù)需要讀取。
- 當(dāng)調(diào)用
具體情況:
-
TCP 連接正常關(guān)閉:
- 在正常的 TCP 連接關(guān)閉過(guò)程中,通信雙方會(huì)經(jīng)過(guò)四次揮手(Four-way Handshake),具體來(lái)說(shuō):
- 一方(通常是主動(dòng)關(guān)閉的那方)發(fā)送一個(gè) FIN 包,表示希望關(guān)閉連接。
- 接收方確認(rèn)收到 FIN 包,并發(fā)送一個(gè) ACK 包。
- 接收方也會(huì)發(fā)送自己的 FIN 包,表示自己也準(zhǔn)備關(guān)閉連接。
- 主動(dòng)關(guān)閉的一方確認(rèn)收到接收方的 FIN 包,完成連接關(guān)閉過(guò)程。
在這個(gè)過(guò)程中,當(dāng)
recv()
讀取到接收到的 FIN 包 后,表示對(duì)方已經(jīng)關(guān)閉了連接,函數(shù)返回0
。 - 在正常的 TCP 連接關(guān)閉過(guò)程中,通信雙方會(huì)經(jīng)過(guò)四次揮手(Four-way Handshake),具體來(lái)說(shuō):
-
recv()
返回0
的例子: 下面是一個(gè)簡(jiǎn)單的代碼示例,演示如何使用recv()
判斷對(duì)方關(guān)閉連接:char buffer[1024]; int bytes_received;// 假設(shè)客戶端已經(jīng)連接到服務(wù)器 bytes_received = recv(client_sock, buffer, sizeof(buffer), 0);if (bytes_received == 0) {printf("The remote side has closed the connection gracefully.\n");// 對(duì)方關(guān)閉了連接,進(jìn)行相應(yīng)的清理工作close(client_sock); } else if (bytes_received < 0) {perror("recv failed"); } else {// 處理接收到的數(shù)據(jù)printf("Received %d bytes: %s\n", bytes_received, buffer); }
在上面的代碼中:
- 如果
recv()
返回0
,表示對(duì)方已經(jīng)正常關(guān)閉了連接。此時(shí),通常需要關(guān)閉自己的套接字并清理相關(guān)資源。 - 如果
recv()
返回負(fù)值(< 0
),表示發(fā)生了錯(cuò)誤,可以通過(guò)errno
獲取錯(cuò)誤原因。
- 如果
其他返回情況:
-
返回負(fù)值(
< 0
):- 如果
recv()
返回一個(gè)負(fù)值,通常表示發(fā)生了錯(cuò)誤。常見(jiàn)的錯(cuò)誤包括:EINTR
: 系統(tǒng)調(diào)用被信號(hào)中斷。EAGAIN
或EWOULDBLOCK
: 套接字處于非阻塞模式,且沒(méi)有數(shù)據(jù)可讀取。ECONNRESET
: 連接被對(duì)方重置(例如,遠(yuǎn)程主機(jī)強(qiáng)制關(guān)閉了連接)。
- 如果
-
返回大于零的正數(shù):
- 如果返回一個(gè)大于
0
的值,表示成功接收到數(shù)據(jù),值表示接收到的數(shù)據(jù)字節(jié)數(shù)。開(kāi)發(fā)者可以處理這些數(shù)據(jù)。
- 如果返回一個(gè)大于
為什么 recv()
返回 0
代表對(duì)方關(guān)閉了連接?
這是因?yàn)樵?TCP 協(xié)議中,連接關(guān)閉是通過(guò)發(fā)送 FIN 包來(lái)實(shí)現(xiàn)的。此時(shí),連接的另一端會(huì)通知接收端自己已經(jīng)沒(méi)有數(shù)據(jù)發(fā)送,并且希望關(guān)閉連接。當(dāng)接收端收到這個(gè) FIN 包后,recv()
返回 0
,表示沒(méi)有更多的數(shù)據(jù)可讀。
- TCP FIN 包:當(dāng)連接的某一方發(fā)送
FIN
包時(shí),它表示已經(jīng)發(fā)送完所有數(shù)據(jù)并且希望關(guān)閉連接。接收方接收到FIN
包后,會(huì)回復(fù)一個(gè) ACK 包,表示已經(jīng)收到關(guān)閉請(qǐng)求。此時(shí),接收方的接收緩沖區(qū)為空,不再有數(shù)據(jù)傳輸,recv()
將返回0
,表示對(duì)方已關(guān)閉連接。
總結(jié):
recv()
返回0
表示對(duì)方已經(jīng)關(guān)閉了連接,通常是正常的連接關(guān)閉過(guò)程。- 該返回值是用于 優(yōu)雅地關(guān)閉連接 的指示,表明沒(méi)有更多數(shù)據(jù)可讀,開(kāi)發(fā)者可以清理資源并關(guān)閉自己的套接字。
- 當(dāng)遇到
0
時(shí),通常需要進(jìn)行關(guān)閉套接字、清理資源等操作。
0voice · GitHub