南京金九建設(shè)集團(tuán)網(wǎng)站aso優(yōu)化貼吧
原始套接字(Raw Socket)
原始套接字(Raw Socket)是一種提供較低級(jí)別網(wǎng)絡(luò)訪問(wèn)的套接字。通過(guò)使用原始套接字,應(yīng)用程序可以直接發(fā)送或接收網(wǎng)絡(luò)層如IP的數(shù)據(jù)包,或者傳輸層如TCP、UDP的段,而無(wú)需通過(guò)常規(guī)的套接字API提供的協(xié)議處理。
以下是原始套接字的一些關(guān)鍵點(diǎn):
-
協(xié)議獨(dú)立性:使用原始套接字,我們可以操作或構(gòu)建自己的協(xié)議,或者直接與現(xiàn)有協(xié)議(如ICMP)交互。
-
繞過(guò)內(nèi)核處理:通常,當(dāng)發(fā)送或接收數(shù)據(jù)包時(shí),操作系統(tǒng)內(nèi)核會(huì)為我們處理很多細(xì)節(jié),例如TCP的三次握手或IP頭的填充。但是,使用原始套接字,我們可以直接構(gòu)建或解析這些協(xié)議,從而繞過(guò)標(biāo)準(zhǔn)的內(nèi)核處理。
-
特權(quán):由于原始套接字提供了對(duì)網(wǎng)絡(luò)的低級(jí)訪問(wèn),使用它們通常需要特權(quán),例如root權(quán)限。
-
應(yīng)用場(chǎng)景:
- 網(wǎng)絡(luò)診斷和測(cè)試工具:例如,
ping
使用原始套接字發(fā)送和接收ICMP回顯請(qǐng)求和回顯響應(yīng)。 - 定制協(xié)議的實(shí)現(xiàn):例如,如果我們想實(shí)驗(yàn)一個(gè)新的傳輸層協(xié)議。
- 安全研究和網(wǎng)絡(luò)攻擊:例如,執(zhí)行某些類型的DoS攻擊或網(wǎng)絡(luò)掃描。
- 網(wǎng)絡(luò)診斷和測(cè)試工具:例如,
-
創(chuàng)建原始套接字:在Linux中,我們可以使用
socket
函數(shù)并為其提供AF_INET
(對(duì)于IPv4)或AF_INET6
(對(duì)于IPv6)以及SOCK_RAW
來(lái)創(chuàng)建一個(gè)原始套接字。 -
手動(dòng)頭部處理:使用原始套接字,需要手動(dòng)構(gòu)建或解析協(xié)議頭部。例如,如果我們正在發(fā)送一個(gè)TCP段,我們需要手動(dòng)構(gòu)建IP和TCP頭部,并設(shè)置所有必要的字段。同樣,當(dāng)從一個(gè)原始套接字接收數(shù)據(jù)時(shí),我們將獲取整個(gè)數(shù)據(jù)包,需要自己解析它。
-
混雜模式:如果我們想使用原始套接字捕獲一個(gè)接口上的所有流量(而不僅僅是發(fā)給特定地址的流量),我們需要將接口設(shè)置為混雜模式。
需要注意的是,雖然原始套接字提供了強(qiáng)大的功能,但也需要小心使用。手動(dòng)處理協(xié)議細(xì)節(jié)容易導(dǎo)致錯(cuò)誤,并可能引起網(wǎng)絡(luò)問(wèn)題或安全隱患。
Socket()
socket()
函數(shù)是計(jì)算機(jī)網(wǎng)絡(luò)編程中的核心函數(shù)之一,用于創(chuàng)建一個(gè)新的套接字。套接字是端到端的通信鏈路,是進(jìn)程之間進(jìn)行網(wǎng)絡(luò)通信的主要手段。
下面詳細(xì)介紹socket()
函數(shù):
函數(shù)原型
int socket(int domain, int type, int protocol);
參數(shù)
-
domain(或稱為family):指定使用哪種地址族。常見(jiàn)的選擇包括:
AF_INET
:IPv4 地址族。用于IPv4網(wǎng)絡(luò)通信。AF_INET6
:IPv6 地址族。用于IPv6網(wǎng)絡(luò)通信。AF_UNIX
:本地套接字(UNIX 域套接字)地址族。用于同一機(jī)器上的進(jìn)程間通信。
-
type:指定套接字的類型。常見(jiàn)的選擇包括:
SOCK_STREAM
:提供面向連接、可靠、雙向的字節(jié)流服務(wù)。典型的協(xié)議有TCP。SOCK_DGRAM
:提供無(wú)連接的、不可靠的數(shù)據(jù)報(bào)服務(wù)。典型的協(xié)議有UDP。SOCK_RAW
:提供原始套接字訪問(wèn),允許直接發(fā)送或接收協(xié)議如IP的數(shù)據(jù)包。SOCK_SEQPACKET
:提供面向連接的、可靠的、固定最大長(zhǎng)度的記錄序列。
-
protocol:指定要使用的協(xié)議。通常,當(dāng)給定了套接字的類型時(shí),可以將此參數(shù)設(shè)置為0,讓系統(tǒng)自動(dòng)選擇合適的協(xié)議。例如,當(dāng)
type
是SOCK_STREAM
時(shí),系統(tǒng)通常選擇TCP作為協(xié)議。
返回值
- 成功:返回一個(gè)非負(fù)描述符,代表新創(chuàng)建的套接字。
- 失敗:返回-1,并設(shè)置相應(yīng)的錯(cuò)誤碼。
使用示例
創(chuàng)建一個(gè)用于IPv4 TCP通信的套接字:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {perror("socket creation failed");exit(EXIT_FAILURE);
}
創(chuàng)建一個(gè)用于IPv4 UDP通信的套接字:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {perror("socket creation failed");exit(EXIT_FAILURE);
}
注意事項(xiàng)
- 創(chuàng)建套接字只是第一步。為了實(shí)際上進(jìn)行通信,還需要其他函數(shù),如
bind()
,listen()
,accept()
,connect()
,send()
, 和recv()
,來(lái)配置并操作這個(gè)套接字。 - 對(duì)于TCP服務(wù)端,通常在
socket()
之后會(huì)調(diào)用bind()
,listen()
和accept()
來(lái)綁定地址、監(jiān)聽(tīng)連接和接受連接。 - 對(duì)于TCP客戶端,通常在
socket()
之后會(huì)調(diào)用connect()
來(lái)連接到服務(wù)器。 - 對(duì)于UDP,沒(méi)有建立或接受連接的概念,所以只需創(chuàng)建套接字,然后可以直接使用
sendto()
和recvfrom()
進(jìn)行通信。
socket()
函數(shù)是網(wǎng)絡(luò)編程中的基礎(chǔ),幾乎所有的網(wǎng)絡(luò)應(yīng)用程序都會(huì)在某個(gè)地方使用它來(lái)開(kāi)始其網(wǎng)絡(luò)通信。
bind()
bind()
是套接字編程中的一個(gè)關(guān)鍵函數(shù),用于將套接字與特定的IP地址和端口號(hào)綁定。它通常在服務(wù)器設(shè)置期間使用,以指定服務(wù)器將在哪個(gè)地址和端口監(jiān)聽(tīng)即將到來(lái)的客戶端連接。
函數(shù)原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數(shù):
-
sockfd: 這是一個(gè)套接字描述符,它表示要綁定的套接字。
-
addr: 這是一個(gè)指向
struct sockaddr
的指針,該結(jié)構(gòu)定義了套接字的地址(IP和端口)。在實(shí)踐中,通常使用特定于協(xié)議的結(jié)構(gòu)(如struct sockaddr_in
對(duì)于IPv4)來(lái)填充這個(gè)參數(shù),并將其指針類型強(qiáng)制轉(zhuǎn)換為struct sockaddr *
。 -
addrlen: 這是地址結(jié)構(gòu)的大小(例如,對(duì)于IPv4,這將是
sizeof(struct sockaddr_in)
)。
返回值:
- 成功時(shí),
bind()
返回0。 - 失敗時(shí),返回-1,并設(shè)置
errno
以指示錯(cuò)誤的原因。
常見(jiàn)的使用模式:
在服務(wù)器中,通常首先創(chuàng)建一個(gè)套接字,然后使用bind()
將它綁定到一個(gè)地址和端口。以下是一個(gè)簡(jiǎn)化的示例,演示如何使用bind()
為IPv4地址綁定套接字:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Create a TCP socketif (sockfd < 0) {perror("Error creating socket");return 1;}struct sockaddr_in server_addr;server_addr.sin_family = AF_INET; // Address family for IPv4server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on any interfaceserver_addr.sin_port = htons(8080); // Listen on port 8080if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("Error binding socket");return 1;}// ... the server can then proceed to listen and accept connectionsreturn 0;
}
常見(jiàn)錯(cuò)誤和注意事項(xiàng):
-
Address already in use: 如果嘗試綁定到已被另一個(gè)套接字使用的地址和端口,將會(huì)出現(xiàn)此錯(cuò)誤。這通常發(fā)生在服務(wù)器崩潰并嘗試重新啟動(dòng),但由于之前的套接字仍處于“TIME_WAIT”狀態(tài),所以它不能立即綁定。使用
setsockopt()
和SO_REUSEADDR
可以幫助解決此問(wèn)題。 -
Permission denied: 通常,只有root用戶才能綁定到低于1024的端口。
-
確保在綁定套接字之前填充了整個(gè)
sockaddr_in
結(jié)構(gòu),并正確設(shè)置了sin_family
、sin_addr.s_addr
和sin_port
字段。 -
使用
htons()
函數(shù)確保端口號(hào)是網(wǎng)絡(luò)字節(jié)順序。
通過(guò)合理地使用bind()
函數(shù),開(kāi)發(fā)人員可以確保他們的服務(wù)器監(jiān)聽(tīng)特定的IP地址和端口,從而等待客戶端的連接。
listen()
listen()
是套接字API中的一個(gè)函數(shù),用于讓一個(gè)套接字進(jìn)入監(jiān)聽(tīng)模式,從而能夠接收來(lái)自客戶端的連接請(qǐng)求。這是創(chuàng)建服務(wù)器應(yīng)用程序的必要步驟之一。
函數(shù)原型:
int listen(int sockfd, int backlog);
參數(shù):
-
sockfd: 一個(gè)套接字描述符,它應(yīng)該先前已經(jīng)使用
socket()
創(chuàng)建并使用bind()
綁定到一個(gè)特定的地址和端口。 -
backlog: 這個(gè)參數(shù)定義了等待隊(duì)列的大小,也就是說(shuō),系統(tǒng)應(yīng)該允許等待處理(未
accept()
的)的連接數(shù)量。當(dāng)有更多的客戶端嘗試連接,超過(guò)了backlog指定的數(shù)量時(shí),系統(tǒng)會(huì)開(kāi)始拒絕這些新的連接請(qǐng)求。
返回值:
- 如果函數(shù)調(diào)用成功,則返回0。
- 如果出現(xiàn)錯(cuò)誤,則返回-1,并設(shè)置
errno
以指示出現(xiàn)的特定錯(cuò)誤。
使用:
一旦使用bind()
函數(shù)將套接字綁定到一個(gè)地址和端口后,我們可以調(diào)用listen()
以進(jìn)入監(jiān)聽(tīng)模式。在此模式下,套接字準(zhǔn)備接受來(lái)自客戶端的連接請(qǐng)求。
這是一個(gè)簡(jiǎn)單的示例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Create a socket// ... (bind the socket to an address using bind() here)if (listen(sockfd, 5) < 0) { // Allow up to 5 pending connectionsperror("Error while trying to listen");return 1;
}
此示例創(chuàng)建了一個(gè)套接字,并設(shè)置其最大待處理連接數(shù)為5。當(dāng)超過(guò)5個(gè)客戶端連接并等待被accept()
時(shí),任何進(jìn)一步的連接請(qǐng)求都將被拒絕,直到有一個(gè)連接被accept()
為止。
注意:
- 在調(diào)用
listen()
之前,必須先調(diào)用bind()
。 backlog
參數(shù)的具體含義和行為可能因操作系統(tǒng)而異。在某些系統(tǒng)上,它表示待處理的連接數(shù)量,而在其他系統(tǒng)上,它可能包括已被accept()
但尚未由應(yīng)用程序處理的連接。- 當(dāng)
backlog
隊(duì)列已滿,進(jìn)一步的連接請(qǐng)求可能會(huì)被拒絕。因此,為了避免這種情況,服務(wù)器應(yīng)該盡快處理連接。 - 通常,在
listen()
之后,會(huì)進(jìn)入一個(gè)循環(huán),不斷地調(diào)用accept()
來(lái)接受并處理來(lái)自客戶端的連接。
總之,listen()
函數(shù)是服務(wù)器套接字編程中的關(guān)鍵步驟,使得服務(wù)器能夠開(kāi)始接受客戶端的連接請(qǐng)求。
accept()
accept()
是套接字編程中的一個(gè)關(guān)鍵函數(shù),用于從已經(jīng)處于監(jiān)聽(tīng)模式的套接字中提取連接請(qǐng)求,并返回一個(gè)新的套接字描述符,該描述符代表與客戶端之間的新連接。此函數(shù)在服務(wù)器應(yīng)用程序中經(jīng)常使用,以處理來(lái)自客戶端的連接請(qǐng)求。
函數(shù)原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數(shù):
-
sockfd: 這是一個(gè)處于監(jiān)聽(tīng)模式的套接字描述符,通常先前已經(jīng)通過(guò)
socket()
創(chuàng)建并通過(guò)bind()
和listen()
函數(shù)配置。 -
addr: 這是一個(gè)指向
struct sockaddr
的指針,當(dāng)accept()
調(diào)用返回時(shí),這個(gè)結(jié)構(gòu)將被填充與已經(jīng)接受的連接的遠(yuǎn)程端(客戶端)的地址信息。 -
addrlen: 這是一個(gè)值-結(jié)果參數(shù)。在調(diào)用
accept()
之前,它應(yīng)該被設(shè)置為addr
指向的地址結(jié)構(gòu)的大小。當(dāng)函數(shù)返回時(shí),addrlen
將被設(shè)置為實(shí)際的地址大小。
返回值:
-
成功時(shí),
accept()
返回一個(gè)新的套接字描述符,代表與客戶端的新連接。此新描述符應(yīng)用于后續(xù)的所有通信(例如send()
和recv()
調(diào)用)。 -
失敗時(shí),返回 -1,并設(shè)置
errno
以指示出現(xiàn)的錯(cuò)誤。
使用和注意事項(xiàng):
-
在服務(wù)器應(yīng)用程序中,通常在
listen()
函數(shù)調(diào)用后立即調(diào)用accept()
,等待客戶端的連接。 -
accept()
函數(shù)是阻塞的,這意味著它將等待,直到一個(gè)連接請(qǐng)求可用,除非套接字已被配置為非阻塞。 -
返回的新套接字描述符與原始的監(jiān)聽(tīng)套接字是獨(dú)立的。應(yīng)使用新的套接字描述符進(jìn)行與客戶端的所有通信,并繼續(xù)使用原始的監(jiān)聽(tīng)套接字來(lái)接受其他連接請(qǐng)求。
-
通常,服務(wù)器將為每個(gè)接受的連接啟動(dòng)一個(gè)新的線程或進(jìn)程以并行處理多個(gè)連接。
-
addr
和addrlen
參數(shù)是可選的;如果我們不關(guān)心客戶端的地址,我們可以設(shè)置這兩個(gè)參數(shù)為 NULL。
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>int main() {int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);// Assuming server_sock has been created and set up for listening...client_sock = accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len);if (client_sock < 0) {perror("Error on accept");return 1;}// Use client_sock for communication with the client...close(client_sock);return 0;
}
這個(gè)簡(jiǎn)單的例子展示了如何使用 accept()
函數(shù)從 server_sock
監(jiān)聽(tīng)套接字中接受一個(gè)新的連接,并使用 client_sock
與客戶端進(jìn)行通信。
connect()
connect()
是套接字編程中的一個(gè)函數(shù),主要用于客戶端應(yīng)用程序。該函數(shù)使客戶端嘗試與服務(wù)器端的指定地址建立連接。
函數(shù)原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數(shù):
-
sockfd: 這是我們想要與遠(yuǎn)程主機(jī)連接的套接字的描述符。
-
addr: 這是一個(gè)指向
struct sockaddr
的指針,包含我們想要連接的遠(yuǎn)程主機(jī)的地址信息。在實(shí)際應(yīng)用中,通常使用特定于協(xié)議的結(jié)構(gòu)(如struct sockaddr_in
對(duì)于IPv4)并將其類型強(qiáng)制轉(zhuǎn)換為struct sockaddr *
。 -
addrlen: 這是地址結(jié)構(gòu)的大小,例如對(duì)于IPv4地址,這通常是
sizeof(struct sockaddr_in)
。
返回值:
-
成功時(shí),
connect()
返回0。 -
失敗時(shí),返回-1,并設(shè)置
errno
以指示錯(cuò)誤原因。
使用和注意事項(xiàng):
-
在客戶端應(yīng)用程序中,我們通常首先使用
socket()
函數(shù)創(chuàng)建一個(gè)套接字,然后使用connect()
函數(shù)嘗試與服務(wù)器連接。 -
如果
connect()
成功,客戶端可以開(kāi)始使用send()
和recv()
或其他相關(guān)函數(shù)與服務(wù)器通信。 -
如果連接嘗試失敗,
connect()
將返回-1。這可能是由于多種原因,例如服務(wù)器未在指定的地址和端口上運(yùn)行,網(wǎng)絡(luò)故障,或服務(wù)器拒絕連接。 -
connect()
在默認(rèn)情況下是阻塞的,這意味著它會(huì)等待,直到連接成功或發(fā)生錯(cuò)誤。但是,我們可以將套接字設(shè)置為非阻塞模式,使connect()
立即返回,并后續(xù)使用select()
或poll()
來(lái)等待連接完成。
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main() {int client_sock;struct sockaddr_in server_addr;// Assuming client_sock has been created...server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080); // server portinet_pton(AF_INET, "192.168.1.1", &server_addr.sin_addr); // server IPif (connect(client_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {perror("Error on connect");return 1;}// Now client_sock is connected and can be used to send or receive data...return 0;
}
這個(gè)簡(jiǎn)單的例子展示了如何使用 connect()
函數(shù)嘗試與運(yùn)行在192.168.1.1
的服務(wù)器上的服務(wù)連接,該服務(wù)監(jiān)聽(tīng)端口8080
。
send()
send()
是套接字編程中的一個(gè)函數(shù),用于向一個(gè)已連接的套接字發(fā)送數(shù)據(jù)。它通常用于TCP套接字,但也可以與其他類型的套接字一起使用。
函數(shù)原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
參數(shù):
-
sockfd: 這是一個(gè)已連接的套接字的描述符。
-
buf: 這是一個(gè)指針,指向我們想要發(fā)送的數(shù)據(jù)的緩沖區(qū)。
-
len: 這是
buf
中我們想要發(fā)送的數(shù)據(jù)的字節(jié)數(shù)。 -
flags: 這是一個(gè)修改函數(shù)操作的標(biāo)志集合。常見(jiàn)的標(biāo)志包括
MSG_OOB
(用于發(fā)送"out-of-band"數(shù)據(jù)) 和MSG_NOSIGNAL
(阻止在連接斷開(kāi)時(shí)發(fā)送SIGPIPE信號(hào))。大多數(shù)情況下,我們可以簡(jiǎn)單地將此參數(shù)設(shè)置為0。
返回值:
-
成功時(shí),
send()
返回實(shí)際發(fā)送的字節(jié)數(shù)。請(qǐng)注意,這可能少于我們請(qǐng)求發(fā)送的數(shù)量。 -
失敗時(shí),返回-1,并設(shè)置
errno
以指示錯(cuò)誤原因。
使用和注意事項(xiàng):
-
在一個(gè)已連接的TCP套接字上使用
send()
之前,必須先成功地調(diào)用connect()
(對(duì)于客戶端)或accept()
(對(duì)于服務(wù)器)。 -
TCP是一個(gè)流協(xié)議,這意味著沒(méi)有消息邊界。連續(xù)的
send()
調(diào)用可能會(huì)在接收方看起來(lái)像一個(gè)連續(xù)的數(shù)據(jù)流,而不是單獨(dú)的消息。 -
如果套接字是阻塞的(默認(rèn)情況),
send()
可能會(huì)阻塞,直到有足夠的網(wǎng)絡(luò)緩沖區(qū)可用以發(fā)送數(shù)據(jù)。如果套接字是非阻塞的,而網(wǎng)絡(luò)緩沖區(qū)不可用,則send()
將立即返回-1,并將errno
設(shè)置為EAGAIN
或EWOULDBLOCK
。 -
在連接斷開(kāi)的套接字上調(diào)用
send()
將導(dǎo)致發(fā)送一個(gè)SIGPIPE
信號(hào),除非設(shè)置了MSG_NOSIGNAL
標(biāo)志。此信號(hào)的默認(rèn)行為是終止進(jìn)程,但可以捕獲或忽略它。
示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd; // assuming it's already connectedconst char *message = "Hello, server!";ssize_t bytes_sent;bytes_sent = send(sockfd, message, strlen(message), 0);if (bytes_sent < 0) {perror("Error on send");return 1;}printf("Sent %zd bytes to server.\n", bytes_sent);return 0;
}
這個(gè)簡(jiǎn)單的示例展示了如何使用send()
函數(shù)將一條消息發(fā)送到一個(gè)已連接的服務(wù)器。
recv()
recv()
函數(shù)用于從已連接的套接字接收數(shù)據(jù)。它主要用于 TCP 套接字,但也可以與其他類型的套接字一起使用。
函數(shù)原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
參數(shù):
-
sockfd: 這是一個(gè)已連接的套接字的描述符。
-
buf: 這是一個(gè)指針,指向一個(gè)緩沖區(qū),用于存儲(chǔ)接收到的數(shù)據(jù)。
-
len: 這是緩沖區(qū)的大小,即我們期望接收的最大字節(jié)數(shù)。
-
flags: 這是一個(gè)修改函數(shù)操作的標(biāo)志集合。一些常見(jiàn)的標(biāo)志包括:
MSG_PEEK
: 查看即將到來(lái)的數(shù)據(jù),但不從隊(duì)列中刪除它。MSG_WAITALL
: 嘗試接收指定的len
字節(jié)。與默認(rèn)行為不同,該標(biāo)志會(huì)使函數(shù)等待,直到請(qǐng)求的字節(jié)數(shù)量可用或發(fā)生某些錯(cuò)誤。MSG_OOB
: 用于接收 “out-of-band” 數(shù)據(jù)。
在大多數(shù)常規(guī)操作中,我們可以簡(jiǎn)單地將此參數(shù)設(shè)置為0。
返回值:
-
成功時(shí),
recv()
返回實(shí)際接收到的字節(jié)數(shù)。如果連接已關(guān)閉,返回0。 -
失敗時(shí),返回-1,并設(shè)置
errno
以指示錯(cuò)誤原因。
使用和注意事項(xiàng):
-
在一個(gè)已連接的 TCP 套接字上使用
recv()
之前,我們需要先成功調(diào)用connect()
(對(duì)于客戶端)或accept()
(對(duì)于服務(wù)器)。 -
TCP 是一個(gè)流協(xié)議,這意味著沒(méi)有消息邊界。連續(xù)的
recv()
調(diào)用可能會(huì)接收到之前調(diào)用的數(shù)據(jù)的剩余部分。 -
如果套接字是阻塞的(默認(rèn)情況),并且沒(méi)有數(shù)據(jù)可用,
recv()
會(huì)阻塞,直到數(shù)據(jù)可用。如果套接字是非阻塞的,并且沒(méi)有數(shù)據(jù)可用,recv()
會(huì)立即返回-1,并將errno
設(shè)置為EAGAIN
或EWOULDBLOCK
。 -
當(dāng)連接斷開(kāi)或關(guān)閉時(shí),
recv()
將返回0。因此,接收到0字節(jié)通常意味著對(duì)端關(guān)閉了連接。
示例:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>int main() {int sockfd; // assuming it's already connectedchar buffer[1024];ssize_t bytes_received;bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (bytes_received < 0) {perror("Error on recv");return 1;} else if (bytes_received == 0) {printf("The peer has closed the connection.\n");return 0;}buffer[bytes_received] = '\0'; // Null-terminate the stringprintf("Received: %s\n", buffer);return 0;
}
這個(gè)簡(jiǎn)單的示例展示了如何使用 recv()
函數(shù)從已連接的服務(wù)器接收消息,并將其打印出來(lái)。
綜合案例
下面是一個(gè)簡(jiǎn)單的TCP套接字編程的例子,其中包括一個(gè)服務(wù)器和一個(gè)客戶端。服務(wù)器接收來(lái)自客戶端的消息,然后返回相同的消息。
服務(wù)器端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>#define SERVER_PORT 8080
#define BUFFER_SIZE 1024int main() {int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;char buffer[BUFFER_SIZE];int bytes_read;// 創(chuàng)建套接字server_sock = socket(AF_INET, SOCK_STREAM, 0);if (server_sock == -1) {perror("Socket creation failed");exit(1);}// 設(shè)置服務(wù)器地址結(jié)構(gòu)memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = INADDR_ANY;// 綁定套接字if (bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("Bind failed");exit(1);}// 監(jiān)聽(tīng)連接請(qǐng)求listen(server_sock, 5);printf("Server is listening on port %d...\n", SERVER_PORT);socklen_t client_addr_len = sizeof(client_addr);client_sock = accept(server_sock, (struct sockaddr*)&client_addr, &client_addr_len);if (client_sock == -1) {perror("Accept failed");exit(1);}// 讀取和響應(yīng)客戶端的消息bytes_read = recv(client_sock, buffer, BUFFER_SIZE, 0);buffer[bytes_read] = '\0';printf("Received from client: %s\n", buffer);send(client_sock, buffer, bytes_read, 0);close(client_sock);close(server_sock);return 0;
}
客戶端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024int main() {int client_sock;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];// 創(chuàng)建套接字client_sock = socket(AF_INET, SOCK_STREAM, 0);if (client_sock == -1) {perror("Socket creation failed");exit(1);}// 設(shè)置服務(wù)器地址結(jié)構(gòu)memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);// 連接到服務(wù)器if (connect(client_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("Connect failed");exit(1);}strcpy(buffer, "Hello, Server!");send(client_sock, buffer, strlen(buffer), 0);int bytes_received = recv(client_sock, buffer, BUFFER_SIZE, 0);buffer[bytes_received] = '\0';printf("Received from server: %s\n", buffer);close(client_sock);return 0;
}
運(yùn)行結(jié)果如下:
上述示例中,服務(wù)器創(chuàng)建一個(gè)套接字,綁定到本地地址并監(jiān)聽(tīng)連接。當(dāng)客戶端連接時(shí),服務(wù)器接收來(lái)自客戶端的消息,并將相同的消息發(fā)送回客戶端??蛻舳藙t發(fā)送一個(gè)簡(jiǎn)單的消息,并從服務(wù)器接收響應(yīng)。