美國人做的古文字網(wǎng)站亞馬遜免費的關(guān)鍵詞工具
1.概念
????????在Linux中,套接字(socket)是一種通信機(jī)制,用于實現(xiàn)不同進(jìn)程之間或同一主機(jī)上的不同線程之間的數(shù)據(jù)交換。它是網(wǎng)絡(luò)編程的基礎(chǔ),允許應(yīng)用程序通過網(wǎng)絡(luò)進(jìn)行通信,也可以在同一臺機(jī)器上的不同進(jìn)程間進(jìn)行通信。
????????套接字的概念起源于BSD(Berkeley Software Distribution)操作系統(tǒng),是由BSD UNIX提出并實現(xiàn)的。后來,套接字成為了Unix-like系統(tǒng)(包括Linux)中網(wǎng)絡(luò)編程的標(biāo)準(zhǔn)接口。在早期的Unix系統(tǒng)中,進(jìn)程間通信主要通過管道和命名管道(FIFO)實現(xiàn),這些機(jī)制只適用于本地進(jìn)程通信。為了能夠在網(wǎng)絡(luò)上進(jìn)行進(jìn)程間通信,套接字作為一種通用的解決方案被引入,并且得到了廣泛的應(yīng)用。
????????套接字可以被視為一種文件描述符,它允許進(jìn)程通過網(wǎng)絡(luò)發(fā)送和接收數(shù)據(jù)。在Linux中,套接字可以基于網(wǎng)絡(luò)協(xié)議(如TCP/IP、UDP)或本地通信協(xié)議(如UNIX域套接字)工作。它提供了一種統(tǒng)一的接口,使得應(yīng)用程序可以通過不同的傳輸層協(xié)議來進(jìn)行通信,而無需關(guān)心底層網(wǎng)絡(luò)細(xì)節(jié)。
套接字類型
在Linux中,套接字可以根據(jù)其類型和地址族的不同而分為多種類型,主要包括:
- 流套接字(Stream Socket):基于TCP協(xié)議,提供面向連接的可靠數(shù)據(jù)傳輸,數(shù)據(jù)傳輸順序不會變化,適合需要可靠傳輸?shù)膽?yīng)用。
- 數(shù)據(jù)報套接字(Datagram Socket):基于UDP協(xié)議,提供不可靠的數(shù)據(jù)傳輸服務(wù),傳輸速度快,但無法保證數(shù)據(jù)傳輸?shù)捻樞蚝涂煽啃?#xff0c;適合對傳輸效率要求較高的應(yīng)用。
- 原始套接字(Raw Socket):允許應(yīng)用程序直接訪問網(wǎng)絡(luò)協(xié)議,如IP層,用于實現(xiàn)自定義網(wǎng)絡(luò)協(xié)議或進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)包分析等特殊用途。
- UNIX域套接字(Unix Domain Socket):用于在同一臺主機(jī)上的進(jìn)程間通信,不涉及網(wǎng)絡(luò)通信,提供了一種高效的本地通信機(jī)制。
2.字節(jié)序
????????字節(jié)序(Byte Order)是指多字節(jié)數(shù)據(jù)在存儲器中的存放順序。由于計算機(jī)內(nèi)存和存儲器是以字節(jié)為最小單位進(jìn)行尋址的,多字節(jié)數(shù)據(jù)(比如16位、32位、64位整數(shù))在存儲器中占據(jù)連續(xù)的字節(jié)空間。字節(jié)序定義了這些字節(jié)在存儲器中的排列順序。對于單字符來說是沒有字節(jié)序問題的,字符串是單字符的集合,因此字符串也沒有字節(jié)序問題。
大端字節(jié)序(Big Endian)
在大端字節(jié)序中,數(shù)據(jù)的高字節(jié)(Most Significant Byte,MSB)存儲在低地址,低字節(jié)(Least Significant Byte,LSB)存儲在高地址。這種方式類似于把一個多字節(jié)整數(shù)的數(shù)字本身按照從高位到低位的順序存放在內(nèi)存中。
小端字節(jié)序(Little Endian)
在小端字節(jié)序中,數(shù)據(jù)的低字節(jié)(LSB)存儲在低地址,高字節(jié)(MSB)存儲在高地址。這種方式將一個多字節(jié)整數(shù)的最低有效字節(jié)放在最低地址處。
// 有一個16進(jìn)制的數(shù), 有32位 (int): 0xab5c01ff
// 字節(jié)序, 最小的單位: char 字節(jié), int 有4個字節(jié), 需要將其拆分為4份
// 一個字節(jié) unsigned char, 最大值是 255(十進(jìn)制) ==> ff(16進(jìn)制) 內(nèi)存低地址位 內(nèi)存的高地址位
--------------------------------------------------------------------------->
小端: 0xff 0x01 0x5c 0xab
大端: 0xab 0x5c 0x01 0xff
-
網(wǎng)絡(luò)通信:
- 大多數(shù)網(wǎng)絡(luò)協(xié)議(如TCP/IP、HTTP)規(guī)定數(shù)據(jù)傳輸時采用網(wǎng)絡(luò)字節(jié)序,即大端字節(jié)序。這是因為網(wǎng)絡(luò)協(xié)議需要確保通信雙方能夠統(tǒng)一數(shù)據(jù)的解析方式,避免因字節(jié)序問題導(dǎo)致數(shù)據(jù)解析錯誤。
- 在網(wǎng)絡(luò)中,通常使用的是網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序),因此,如果要與網(wǎng)絡(luò)進(jìn)行數(shù)據(jù)交換,尤其是對于傳輸整數(shù)等多字節(jié)數(shù)據(jù)時,使用大端字節(jié)序能夠簡化數(shù)據(jù)的處理和解析。
-
個人計算機(jī):
- 大多數(shù)個人計算機(jī)(如x86架構(gòu))采用小端字節(jié)序。因此,在開發(fā)和編寫面向這些平臺的應(yīng)用程序時,通常會使用小端字節(jié)序。
- Windows、Linux(x86、x86-64架構(gòu))、以及大部分現(xiàn)代桌面和移動設(shè)備的處理器都是小端字節(jié)序。
-
內(nèi)存訪問優(yōu)化:
- 小端字節(jié)序在訪問多字節(jié)數(shù)據(jù)時有時可以更加高效。例如,訪問一個32位整數(shù)的低位字節(jié)時可以直接通過該整數(shù)的地址加1來獲取,而不需要進(jìn)行字節(jié)順序的轉(zhuǎn)換。
相關(guān)函數(shù):
#include <arpa/inet.h>
功能:將32位主機(jī)字節(jié)序整數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)。
uint32_t htonl(uint32_t hostlong);
參數(shù):hostlong:待轉(zhuǎn)換的32位主機(jī)字節(jié)序整數(shù)。
返回值:返回轉(zhuǎn)換后的32位網(wǎng)絡(luò)字節(jié)序整數(shù)。
注意事項:
用于將主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,以便進(jìn)行網(wǎng)絡(luò)通信。
如果主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序相同(通常是小端字節(jié)序的情況下),則 htonl 函數(shù)不會進(jìn)行實際的字節(jié)序轉(zhuǎn)換,直接返回輸入?yún)?shù)本身。
在網(wǎng)絡(luò)編程中,發(fā)送數(shù)據(jù)前通常要使用此函數(shù)將數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。功能:將16位主機(jī)字節(jié)序短整數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)。
uint16_t htons(uint16_t hostshort);
參數(shù):hostshort:待轉(zhuǎn)換的16位主機(jī)字節(jié)序短整數(shù)。
返回值:返回轉(zhuǎn)換后的16位網(wǎng)絡(luò)字節(jié)序短整數(shù)。
注意事項:
用于將主機(jī)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,以便進(jìn)行網(wǎng)絡(luò)通信。
在網(wǎng)絡(luò)編程中,發(fā)送數(shù)據(jù)前通常要使用此函數(shù)將數(shù)據(jù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。功能:將32位網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)整數(shù)轉(zhuǎn)換為主機(jī)字節(jié)序。
uint32_t ntohl(uint32_t netlong);
參數(shù):netlong:待轉(zhuǎn)換的32位網(wǎng)絡(luò)字節(jié)序整數(shù)。
返回值:返回轉(zhuǎn)換后的32位主機(jī)字節(jié)序整數(shù)。
注意事項:
用于將接收到的網(wǎng)絡(luò)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為主機(jī)字節(jié)序,以便應(yīng)用程序正確解析和處理數(shù)據(jù)。
在接收網(wǎng)絡(luò)數(shù)據(jù)后,通常要使用此函數(shù)將數(shù)據(jù)轉(zhuǎn)換為主機(jī)字節(jié)序進(jìn)行處理。功能:將16位網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)短整數(shù)轉(zhuǎn)換為主機(jī)字節(jié)序。
uint16_t ntohs(uint16_t netshort);
參數(shù):netshort:待轉(zhuǎn)換的16位網(wǎng)絡(luò)字節(jié)序短整數(shù)。
返回值:返回轉(zhuǎn)換后的16位主機(jī)字節(jié)序短整數(shù)。
注意事項:
用于將接收到的網(wǎng)絡(luò)字節(jié)序數(shù)據(jù)轉(zhuǎn)換為主機(jī)字節(jié)序,以便應(yīng)用程序正確解析和處理數(shù)據(jù)。
在接收網(wǎng)絡(luò)數(shù)據(jù)后,通常要使用此函數(shù)將數(shù)據(jù)轉(zhuǎn)換為主機(jī)字節(jié)序進(jìn)行處理。
示例代碼:
#include <stdio.h>
#include <arpa/inet.h> // 包含網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換函數(shù)的頭文件int main() {// 定義一個主機(jī)字節(jié)序的32位整數(shù)uint32_t host_long = 0x12345678;// 定義一個主機(jī)字節(jié)序的16位短整數(shù)uint16_t host_short = 0x1234;// 將主機(jī)字節(jié)序的整數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)uint32_t network_long = htonl(host_long);// 將主機(jī)字節(jié)序的短整數(shù)轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)uint16_t network_short = htons(host_short);// 輸出轉(zhuǎn)換前后的整數(shù)值和短整數(shù)值printf("Original Host Long: 0x%x\n", host_long);printf("Network Long (Big Endian): 0x%x\n", network_long);printf("Original Host Short: 0x%x\n", host_short);printf("Network Short (Big Endian): 0x%x\n", network_short);// 將網(wǎng)絡(luò)字節(jié)序的整數(shù)轉(zhuǎn)換回主機(jī)字節(jié)序uint32_t host_long_back = ntohl(network_long);// 將網(wǎng)絡(luò)字節(jié)序的短整數(shù)轉(zhuǎn)換回主機(jī)字節(jié)序uint16_t host_short_back = ntohs(network_short);// 輸出轉(zhuǎn)換回主機(jī)字節(jié)序后的整數(shù)值和短整數(shù)值printf("\nNetwork Long (Big Endian): 0x%x\n", network_long);printf("Back to Host Long: 0x%x\n", host_long_back);printf("Network Short (Big Endian): 0x%x\n", network_short);printf("Back to Host Short: 0x%x\n", host_short_back);return 0;
}
3.IP地址轉(zhuǎn)換
雖然IP地址本質(zhì)是一個整形數(shù),但是在使用的過程中都是通過一個字符串來描述,下面的函數(shù)描述了如何將一個字符串類型的IP地址進(jìn)行大小端轉(zhuǎn)換:
3.1?inet_pton
函數(shù)
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
功能:將點分十進(jìn)制字符串形式的IP地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的二進(jìn)制IP地址表示。
參數(shù):
af:地址族(Address Family),可以是 AF_INET 表示IPv4,或 AF_INET6 表示IPv6。
src:待轉(zhuǎn)換的點分十進(jìn)制字符串形式的IP地址。
dst:指向存放轉(zhuǎn)換后二進(jìn)制IP地址的內(nèi)存空間的指針。
返回值:
如果轉(zhuǎn)換成功,返回1(IPv4)或者1(IPv6)。
如果傳入的字符串不是合法的IP地址,返回0。
如果發(fā)生錯誤,返回-1,并設(shè)置 errno 指示具體錯誤。
注意事項:
dst 參數(shù)應(yīng)該足夠大來容納轉(zhuǎn)換后的二進(jìn)制IP地址。
在使用前需要確保正確設(shè)置 af 參數(shù),以指明是處理IPv4還是IPv6地址。
函數(shù)會自動識別并轉(zhuǎn)換點分十進(jìn)制的IPv4地址和IPv6的十六進(jìn)制地址。
3.2?inet_ntop
函數(shù)
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
功能:將網(wǎng)絡(luò)字節(jié)序的二進(jìn)制IP地址表示轉(zhuǎn)換為點分十進(jìn)制字符串形式的IP地址。
參數(shù):
af:地址族(Address Family),可以是 AF_INET 表示IPv4,或 AF_INET6 表示IPv6。
src:指向存放二進(jìn)制IP地址的內(nèi)存空間的指針。
dst:用于存放轉(zhuǎn)換后的點分十進(jìn)制字符串形式IP地址的緩沖區(qū)。
size:緩沖區(qū) dst 的大小,一般建議使用 INET_ADDRSTRLEN(IPv4地址的最大長度)或 INET6_ADDRSTRLEN(IPv6地址的最大長度)。
返回值:
如果轉(zhuǎn)換成功,返回指向 dst 的指針,即轉(zhuǎn)換后的點分十進(jìn)制字符串形式IP地址。
如果發(fā)生錯誤,返回 NULL,并設(shè)置 errno 指示具體錯誤。
注意事項:
dst 緩沖區(qū)應(yīng)足夠大以容納轉(zhuǎn)換后的IP地址字符串。
函數(shù)根據(jù) af 參數(shù)的值自動識別并轉(zhuǎn)換二進(jìn)制IP地址表示。
在使用前要確保 src 指向的內(nèi)存區(qū)域大小足夠。
示例代碼:
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>int main() {char ip4_str[] = "192.168.1.1";char ip6_str[] = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";struct in_addr ip4_addr;struct in6_addr ip6_addr;char ip_str[INET6_ADDRSTRLEN];// 將IPv4字符串轉(zhuǎn)換為二進(jìn)制格式if (inet_pton(AF_INET, ip4_str, &ip4_addr) <= 0) {perror("inet_pton");return 1;}// 將二進(jìn)制IPv4地址轉(zhuǎn)換為字符串格式const char *ip4_str_converted = inet_ntop(AF_INET, &ip4_addr, ip_str, INET_ADDRSTRLEN);if (ip4_str_converted == NULL) {perror("inet_ntop");return 1;}printf("IPv4地址: %s\n", ip4_str_converted);// 將IPv6字符串轉(zhuǎn)換為二進(jìn)制格式if (inet_pton(AF_INET6, ip6_str, &ip6_addr) <= 0) {perror("inet_pton");return 1;}// 將二進(jìn)制IPv6地址轉(zhuǎn)換為字符串格式const char *ip6_str_converted = inet_ntop(AF_INET6, &ip6_addr, ip_str, INET6_ADDRSTRLEN);if (ip6_str_converted == NULL) {perror("inet_ntop");return 1;}printf("IPv6地址: %s\n", ip6_str_converted);return 0;
}
4.socket套接字
4.1相關(guān)操作函數(shù)
4.1.1?socket
函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:創(chuàng)建一個新的套接字。
參數(shù):
domain:指定協(xié)議族,常見的有 AF_INET(IPv4)和 AF_INET6(IPv6),還有其他如 AF_UNIX(Unix域),AF_LOCAL(本地通信)等。
type:指定套接字類型,如 SOCK_STREAM(流式套接字,用于TCP),SOCK_DGRAM(數(shù)據(jù)報套接字,用于UDP),SOCK_RAW(原始套接字)等。
protocol:指定具體的協(xié)議,通常設(shè)為0以選擇默認(rèn)協(xié)議。
返回值:
如果成功,返回一個非負(fù)的套接字描述符,用于后續(xù)的套接字操作。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
創(chuàng)建套接字時,需要確保傳入正確的 domain、type 和 protocol 參數(shù)。
套接字描述符是一個整數(shù),用于唯一標(biāo)識一個套接字,應(yīng)該小心管理,防止資源泄露。
在使用完套接字后,應(yīng)該通過 close 函數(shù)關(guān)閉套接字,釋放相關(guān)資源。
4.1.2?bind
函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:將一個本地地址(IP地址和端口號)分配給一個套接字。
參數(shù):
sockfd:套接字描述符,由 socket 函數(shù)返回。
addr:指向包含要綁定到套接字的地址信息的結(jié)構(gòu)體指針,通常是 struct sockaddr 結(jié)構(gòu)體。
addrlen:addr 結(jié)構(gòu)體的長度。
返回值:
如果成功,返回 0。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
在使用 bind 函數(shù)前,確保套接字已經(jīng)創(chuàng)建成功,并且填充了正確的地址信息到 addr 結(jié)構(gòu)體中。
只有未被占用的地址才能成功綁定,否則會返回錯誤。
需要特別注意端口號的使用,避免與系統(tǒng)中已有的服務(wù)沖突。
4.1.3 struct sockaddr結(jié)構(gòu)體
struct sockaddr
是用于存儲各種套接字地址的通用結(jié)構(gòu)體,在網(wǎng)絡(luò)編程中廣泛使用。它的設(shè)計靈活,可以適應(yīng)不同協(xié)議族(如IPv4、IPv6、Unix域等)的地址表示。在寫數(shù)據(jù)的時候不好用。struct sockaddr
的定義通常在 <sys/socket.h>
頭文件中,是一個通用的套接字地址結(jié)構(gòu)體。
struct sockaddr {sa_family_t sa_family; // 地址族(Address Family)char sa_data[14]; // 地址數(shù)據(jù)(包括IP地址和端口號)端口(2字節(jié)) + IP地址(4字節(jié)) + 填充(8字節(jié))
};
sa_family:用于指定地址的協(xié)議族(Address Family),可以是以下常見的值之一:AF_INET:IPv4地址族AF_INET6:IPv6地址族AF_UNIX 或 AF_LOCAL:Unix域(本地通信)其他協(xié)議族的值,如AF_PACKET等,根據(jù)具體需要定義。
sa_data:存放套接字地址的實際數(shù)據(jù)部分,包括IP地址和端口號等。由于不同協(xié)議的地址數(shù)據(jù)可能不同,這里使用了一個固定長度的數(shù)組來存儲。
struct sockaddr_in
是用于表示IPv4套接字地址的結(jié)構(gòu)體,在網(wǎng)絡(luò)編程中經(jīng)常使用。它是 struct sockaddr
結(jié)構(gòu)體的一個特定實現(xiàn),用于IPv4地址族。struct sockaddr_in
的定義通常在 <netinet/in.h>
頭文件中,用于表示IPv4地址的套接字地址結(jié)構(gòu)體。
struct in_addr
{in_addr_t s_addr;
}; struct sockaddr_in {sa_family_t sin_family; // 地址族 (AF_INET)in_port_t sin_port; // 端口號 (使用網(wǎng)絡(luò)字節(jié)序)struct in_addr sin_addr; // IPv4地址char sin_zero[8]; // 填充字節(jié),用于使結(jié)構(gòu)體與 struct sockaddr 兼容
};
sin_family:地址族,固定為 AF_INET,表示IPv4地址族。
sin_port:16位端口號,使用網(wǎng)絡(luò)字節(jié)序(即大端字節(jié)序)表示。
sin_addr:struct in_addr 類型的結(jié)構(gòu)體,用于存儲IPv4地址。
sin_zero:填充字段,使 struct sockaddr_in 的大小與 struct sockaddr 相同,用于兼容性。
4.1.4?listen
函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:將未連接的套接字轉(zhuǎn)換為被動監(jiān)聽狀態(tài),用于接受客戶端的連接請求。
參數(shù):
sockfd:套接字描述符,由 socket 函數(shù)返回,并且已經(jīng)通過 bind 綁定了本地地址。
backlog:指定同時等待處理的連接請求的最大數(shù)量,最大值為128
返回值:
如果成功,返回 0。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
在調(diào)用 listen 函數(shù)前,套接字必須已經(jīng)成功綁定到一個本地地址。
backlog 參數(shù)指定內(nèi)核中連接隊列的長度,影響服務(wù)器可以接受的最大連接數(shù)。
當(dāng)有新的連接請求到達(dá)時,服務(wù)器將從隊列中取出并處理。
4.1.5?accept
函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:接受客戶端的連接請求,創(chuàng)建一個新的套接字用于與客戶端通信。
參數(shù):
sockfd:套接字描述符,處于監(jiān)聽狀態(tài)的套接字。
addr:(可選)指向用于存放客戶端地址信息的結(jié)構(gòu)體指針,通常是 struct sockaddr 結(jié)構(gòu)體。
addrlen:(可選)addr 結(jié)構(gòu)體的長度指針。
返回值:
如果成功,返回一個新的套接字描述符,用于與客戶端通信。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
accept 函數(shù)通常在服務(wù)器的主循環(huán)中調(diào)用,用于接受新的客戶端連接。
如果不需要獲取客戶端的地址信息,可以將 addr 和 addrlen 參數(shù)設(shè)置為 NULL。
新創(chuàng)建的套接字用于與特定的客戶端進(jìn)行通信,應(yīng)在通信結(jié)束后及時關(guān)閉。
4.1.6 接收和發(fā)送數(shù)據(jù)函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:將數(shù)據(jù)發(fā)送到連接的套接字。
參數(shù):
sockfd:套接字描述符,指定要發(fā)送數(shù)據(jù)的套接字。
buf:指向要發(fā)送數(shù)據(jù)的緩沖區(qū)的指針。
len:要發(fā)送數(shù)據(jù)的字節(jié)數(shù)。
flags:指定發(fā)送操作的標(biāo)志,通常設(shè)為 0。
返回值:
如果成功,返回發(fā)送的字節(jié)數(shù)。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
send 函數(shù)可能會發(fā)送比請求的數(shù)據(jù)少的字節(jié)數(shù)(部分發(fā)送),應(yīng)該在循環(huán)中調(diào)用直到所有數(shù)據(jù)都被發(fā)送。
需要注意處理信號中斷(EINTR)的情況,以確保數(shù)據(jù)完整性和穩(wěn)定性。ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:從連接的套接字接收數(shù)據(jù)。
參數(shù):
sockfd:套接字描述符,指定要接收數(shù)據(jù)的套接字。
buf:指向接收數(shù)據(jù)的緩沖區(qū)的指針。
len:要接收數(shù)據(jù)的最大字節(jié)數(shù)。
flags:指定接收操作的標(biāo)志,通常設(shè)為 0。
返回值:
如果成功,返回接收的字節(jié)數(shù)。
如果連接關(guān)閉(對于 TCP 套接字),返回 0。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
recv 函數(shù)可能會接收比請求的數(shù)據(jù)少的字節(jié)數(shù)(部分接收),應(yīng)該在循環(huán)中調(diào)用直到接收到所需的數(shù)據(jù)或者達(dá)到預(yù)期的條件。
對于非阻塞套接字,需要處理 EAGAIN 或 EWOULDBLOCK 錯誤。
在使用前確保套接字已經(jīng)連接或者綁定,并且合適地設(shè)置了 buf 和 len。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向文件描述符 fd 寫入數(shù)據(jù)。
參數(shù):
fd:文件描述符,可以是套接字描述符。
buf:指向要寫入數(shù)據(jù)的緩沖區(qū)的指針。
count:要寫入的字節(jié)數(shù)。
返回值:
如果成功,返回實際寫入的字節(jié)數(shù)。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
write 函數(shù)通常用于向已連接的套接字寫入數(shù)據(jù),也可以用于向文件、管道等寫入數(shù)據(jù)。
如果 write 返回值小于 count,則可能是由于部分寫入或者錯誤發(fā)生。
應(yīng)該在循環(huán)中調(diào)用 write 直到所有數(shù)據(jù)都被寫入,或者處理寫入失敗的情況。ssize_t read(int fd, void *buf, size_t count);
功能:從文件描述符 fd 讀取數(shù)據(jù)。
參數(shù):
fd:文件描述符,可以是套接字描述符。
buf:指向存放讀取數(shù)據(jù)的緩沖區(qū)的指針。
count:要讀取的最大字節(jié)數(shù)。
返回值:
如果成功,返回實際讀取的字節(jié)數(shù)。
如果已經(jīng)到達(dá)文件末尾(對套接字來說通常表示連接關(guān)閉),返回 0。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
read 函數(shù)通常用于從已連接的套接字讀取數(shù)據(jù),也可以用于從文件、管道等讀取數(shù)據(jù)。
應(yīng)該在循環(huán)中調(diào)用 read 直到接收到所需的數(shù)據(jù),或者處理讀取失敗的情況。
對于非阻塞套接字,需要處理 EAGAIN 或 EWOULDBLOCK 錯誤。
在使用 socket 套接字進(jìn)行網(wǎng)絡(luò)通信時,特別是在 UDP 協(xié)議中,常用的數(shù)據(jù)發(fā)送和接收函數(shù)包括 sendto
和 recvfrom
。這兩個函數(shù)與 send
和 recv
在功能上類似,但是更適用于無連接的 UDP 套接字,也可以用于有連接的套接字。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:向指定地址發(fā)送數(shù)據(jù)。
參數(shù):
sockfd:套接字描述符。
buf:指向要發(fā)送數(shù)據(jù)的緩沖區(qū)的指針。
len:要發(fā)送的數(shù)據(jù)字節(jié)數(shù)。
flags:發(fā)送標(biāo)志,通常設(shè)置為 0。
dest_addr:指向目標(biāo)地址結(jié)構(gòu)體的指針,包含目標(biāo)地址和端口信息。
addrlen:dest_addr 結(jié)構(gòu)體的長度。
返回值:
如果成功,返回實際發(fā)送的字節(jié)數(shù)。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
sendto 適用于無連接的 UDP 套接字,也可以用于有連接的套接字。
如果 dest_addr 是 NULL,則需要在之前使用 connect 函數(shù)連接套接字。
可以用于向多個目標(biāo)發(fā)送數(shù)據(jù),通過不同的 dest_addr 參數(shù)指定。ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);功能:從指定地址接收數(shù)據(jù)。
參數(shù):
sockfd:套接字描述符。
buf:指向存放接收數(shù)據(jù)的緩沖區(qū)的指針。
len:緩沖區(qū)的大小,即最多接收的數(shù)據(jù)字節(jié)數(shù)。
flags:接收標(biāo)志,通常設(shè)置為 0。
src_addr:指向發(fā)送方地址結(jié)構(gòu)體的指針,用于存放發(fā)送方的地址信息。
addrlen:src_addr 結(jié)構(gòu)體的長度指針,調(diào)用前需設(shè)置為結(jié)構(gòu)體的實際長度。
返回值:
如果成功,返回實際接收的字節(jié)數(shù)。
如果沒有可用數(shù)據(jù)且對方關(guān)閉連接,返回 0。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
recvfrom 適用于無連接的 UDP 套接字,也可以用于有連接的套接字。
如果套接字已經(jīng)連接(通過 connect 函數(shù)),則可以將 src_addr 和 addrlen 設(shè)置為 NULL。
可以用于從多個發(fā)送方接收數(shù)據(jù),通過 src_addr 參數(shù)獲取發(fā)送方的地址信息。
4.1.7?connect
函數(shù)
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:與指定地址的服務(wù)器建立連接。
參數(shù):
sockfd:套接字描述符,即 socket 函數(shù)返回的套接字描述符。
addr:指向 struct sockaddr 結(jié)構(gòu)體的指針,包含要連接的服務(wù)器地址信息。
addrlen:addr 結(jié)構(gòu)體的長度。
返回值:
如果成功,返回 0。
如果失敗,返回 -1,并設(shè)置 errno 指示具體錯誤。
注意事項:
在調(diào)用 connect 前,需要先創(chuàng)建好套接字并填充好服務(wù)器的地址信息。
對于阻塞套接字,connect 函數(shù)可能會阻塞直到連接建立或超時。
對于非阻塞套接字,可能返回 EINPROGRESS,需要進(jìn)一步檢查連接狀態(tài)。
4.2 TCP通信流程
TCP是一個面向連接的,安全的,流式傳輸協(xié)議,這個協(xié)議是一個傳輸層協(xié)議。
-
連接導(dǎo)向:
- TCP 是面向連接的協(xié)議,通信雙方在傳輸數(shù)據(jù)前需要先建立連接,確保數(shù)據(jù)可靠傳輸。
- 連接的建立包括三次握手過程,保證了通信雙方的可靠性和數(shù)據(jù)同步性。
-
可靠性:
- TCP 提供可靠的數(shù)據(jù)傳輸,通過序號、確認(rèn)應(yīng)答、重傳機(jī)制等手段來確保數(shù)據(jù)的完整性和可靠性。
- 數(shù)據(jù)傳輸過程中,如果發(fā)生丟包、出錯或者順序錯亂,TCP 會進(jìn)行重傳,直到數(shù)據(jù)正確送達(dá)目標(biāo)。
-
流量控制:
- TCP 使用滑動窗口協(xié)議進(jìn)行流量控制,通過動態(tài)調(diào)整發(fā)送方的發(fā)送窗口大小,控制發(fā)送數(shù)據(jù)的速率,避免數(shù)據(jù)包丟失和網(wǎng)絡(luò)擁塞。
-
有序性:
- TCP 保證數(shù)據(jù)傳輸?shù)挠行蛐?#xff0c;發(fā)送的數(shù)據(jù)包按照順序到達(dá)接收端,并且按照發(fā)送的順序重組。
- 服務(wù)端和客戶端初始化?
socket
,得到文件描述符; - 服務(wù)端調(diào)用?
bind
,將 socket 綁定在指定的 IP 地址和端口; - 服務(wù)端調(diào)用?
listen
,進(jìn)行監(jiān)聽; - 服務(wù)端調(diào)用?
accept
,等待客戶端連接; - 客戶端調(diào)用?
connect
,向服務(wù)端的地址和端口發(fā)起連接請求; - 服務(wù)端?
accept
?返回用于傳輸?shù)?socket
?的文件描述符; - 客戶端調(diào)用?
write
?寫入數(shù)據(jù);服務(wù)端調(diào)用?read
?讀取數(shù)據(jù); - 客戶端斷開連接時,會調(diào)用?
close
,那么服務(wù)端?read
?讀取數(shù)據(jù)的時候,就會讀取到了?EOF
,待處理完數(shù)據(jù)后,服務(wù)端調(diào)用?close
,表示連接關(guān)閉。
這里需要注意的是,服務(wù)端調(diào)用?accept
?時,連接成功了會返回一個已完成連接的 socket,后續(xù)用來傳輸數(shù)據(jù)。
所以,監(jiān)聽的 socket 和真正用來傳送數(shù)據(jù)的 socket,是「兩個」 socket,一個叫作監(jiān)聽 socket,一個叫作已完成連接 socket。
成功連接建立之后,雙方開始通過 read 和 write 函數(shù)來讀寫數(shù)據(jù),就像往一個文件流里面寫東西一樣。
4.2.1 示例代碼
TCP回顯服務(wù)器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h> // 包含toupper函數(shù)#define PORT 8080
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket, valread;struct sockaddr_in address;int opt = 1;int addrlen = sizeof(address);char buffer[BUFFER_SIZE] = {0};char *hello = "Hello from server";// 創(chuàng)建 TCP 套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 設(shè)置套接字選項,允許地址重用if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {perror("setsockopt");exit(EXIT_FAILURE);}// 設(shè)置服務(wù)器地址結(jié)構(gòu)address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY; // 使用本地IP地址address.sin_port = htons(PORT);// 將套接字綁定到指定地址和端口if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");exit(EXIT_FAILURE);}// 監(jiān)聽連接請求,最多支持 MAX_CLIENTS 個客戶端連接if (listen(server_fd, MAX_CLIENTS) < 0) {perror("listen");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 接受連接并與客戶端通信while (1) {// 等待新連接if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}// 打印客戶端地址信息char client_addr[INET_ADDRSTRLEN];inet_ntop(AF_INET, &address.sin_addr, client_addr, INET_ADDRSTRLEN);printf("New connection from %s:%d\n", client_addr, ntohs(address.sin_port));valread = read(new_socket, buffer, BUFFER_SIZE);if (valread <= 0) {break;}// 將接收到的消息轉(zhuǎn)換為大寫for (int i = 0; i < valread; ++i) {buffer[i] = toupper(buffer[i]);}printf("Received message from %s:%d: %s\n", client_addr, ntohs(address.sin_port), buffer);// 發(fā)送轉(zhuǎn)換后的消息給客戶端send(new_socket, buffer, valread, 0);memset(buffer, 0, sizeof(buffer));// 關(guān)閉與客戶端的連接printf("Client disconnected: %s:%d\n", client_addr, ntohs(address.sin_port));}close(new_socket);close(server_fd);return 0;
}
//客戶端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define PORT 8080
#define BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"int main() {int sock = 0, valread;struct sockaddr_in serv_addr;char buffer[BUFFER_SIZE] = {0};char input_buffer[BUFFER_SIZE] = {0};char *hello = "Hello from client";// 創(chuàng)建 TCP 套接字if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {perror("socket creation failed");return -1;}// 設(shè)置服務(wù)器地址結(jié)構(gòu)serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(PORT);// 將 IPv4 地址從文本轉(zhuǎn)換為二進(jìn)制形式if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");return -1;}// 連接服務(wù)器if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {perror("Connection Failed");return -1;}printf("Connected to server\n");// 循環(huán)發(fā)送消息并接收響應(yīng)while (1) {printf("Enter message to send (or 'exit' to quit): ");fgets(input_buffer, BUFFER_SIZE, stdin);// 去掉輸入的換行符input_buffer[strcspn(input_buffer, "\n")] = 0;// 如果輸入是 'exit',則退出循環(huán)if (strcmp(input_buffer, "exit") == 0) {break;}// 發(fā)送消息給服務(wù)器send(sock, input_buffer, strlen(input_buffer), 0);printf("Message sent to server: %s\n", input_buffer);// 接收服務(wù)器的響應(yīng)valread = read(sock, buffer, BUFFER_SIZE);printf("Server response: %s\n", buffer);memset(buffer, 0, sizeof(buffer));}close(sock);return 0;
}
4.3 UDP通信流程
UDP是一個簡單的、無連接的、使用數(shù)據(jù)報的,輕量級的傳輸層協(xié)議。
-
無連接:
- UDP 是無連接的協(xié)議,通信雙方在傳輸數(shù)據(jù)時不需要建立連接,可以直接發(fā)送數(shù)據(jù)包。
- 沒有連接建立過程,因此UDP的開銷比TCP小,適合對實時性要求較高的應(yīng)用。
-
不可靠性:
- UDP 不提供數(shù)據(jù)傳輸?shù)目煽啃员WC,發(fā)送數(shù)據(jù)后不會確認(rèn)是否到達(dá)目標(biāo),也不會進(jìn)行重傳。
- 發(fā)送的數(shù)據(jù)包可能丟失或者無序到達(dá)接收端,需要應(yīng)用層自行處理數(shù)據(jù)的丟失和重傳。
-
速度和效率:
- UDP 相比TCP速度更快,沒有建立連接和維護(hù)狀態(tài)的開銷,適合實時性要求高、傳輸數(shù)據(jù)量小的應(yīng)用。
- UDP 的頭部開銷小,每個數(shù)據(jù)包僅包含基本的必要信息,傳輸效率較高。
-
廣播和多播:
- UDP 支持廣播和多播,可以將數(shù)據(jù)包發(fā)送到一個網(wǎng)絡(luò)中的多個接收端。
UDP通信流程概述
- UDP發(fā)送方初始化套接字,得到文件描述符;
- UDP接收方初始化套接字,得到文件描述符;
- UDP接收方調(diào)用bind,將套接字綁定在指定的IP地址和端口;
- UDP發(fā)送方調(diào)用sendto發(fā)送數(shù)據(jù)到接收方的地址和端口;
- UDP接收方調(diào)用recvfrom接收數(shù)據(jù);
- UDP接收方處理請求并調(diào)用sendto發(fā)送響應(yīng)數(shù)據(jù)到發(fā)送方;
- UDP發(fā)送方調(diào)用recvfrom接收響應(yīng)數(shù)據(jù);
- 通信結(jié)束后,發(fā)送方和接收方分別調(diào)用close關(guān)閉套接字。
4.3.1 示例代碼
服務(wù)器端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h> // 包含toupper函數(shù)#define PORT 8080
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in server_addr, client_addr;char buffer[BUFFER_SIZE];socklen_t addr_len;int n;// 創(chuàng)建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 設(shè)置服務(wù)器地址結(jié)構(gòu)memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(PORT);// 綁定套接字到指定IP地址和端口if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);while (1) {// 接收客戶端的數(shù)據(jù)addr_len = sizeof(client_addr);n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);buffer[n] = '\0';// 打印客戶端地址信息和接收到的數(shù)據(jù)char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);printf("Received message from %s:%d: %s\n", client_ip, ntohs(client_addr.sin_port), buffer);// 將數(shù)據(jù)轉(zhuǎn)換為大寫for (int i = 0; i < n; i++) {buffer[i] = toupper(buffer[i]);}// 發(fā)送轉(zhuǎn)換后的數(shù)據(jù)回客戶端sendto(sockfd, buffer, n, 0, (struct sockaddr *)&client_addr, addr_len);printf("Sent uppercase message to %s:%d: %s\n", client_ip, ntohs(client_addr.sin_port), buffer);}// 關(guān)閉套接字close(sockfd);return 0;
}
客戶端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8080
#define BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"int main() {int sockfd;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];char recv_buffer[BUFFER_SIZE];socklen_t addr_len;int n;// 創(chuàng)建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 設(shè)置服務(wù)器地址結(jié)構(gòu)memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);printf("Connected to server at %s:%d\n", SERVER_IP, PORT);while (1) {printf("Enter message to send (or 'exit' to quit): ");fgets(buffer, BUFFER_SIZE, stdin);buffer[strcspn(buffer, "\n")] = 0; // 去掉輸入的換行符// 如果輸入是 'exit',則退出循環(huán)if (strcmp(buffer, "exit") == 0) {break;}// 發(fā)送數(shù)據(jù)到服務(wù)器sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));printf("Message sent to server: %s\n", buffer);// 接收服務(wù)器的響應(yīng)addr_len = sizeof(server_addr);n = recvfrom(sockfd, recv_buffer, BUFFER_SIZE, 0, (struct sockaddr *)&server_addr, &addr_len);recv_buffer[n] = '\0';printf("Server response: %s\n", recv_buffer);}// 關(guān)閉套接字close(sockfd);return 0;
}