網(wǎng)站圖片有什么要求嗎長(zhǎng)春seo排名公司
Linux套接字通信
在網(wǎng)絡(luò)通信的時(shí)候, 程序猿需要負(fù)責(zé)的應(yīng)用層數(shù)據(jù)的處理(最上層),而底層的數(shù)據(jù)封裝與解封裝(如TCP/IP協(xié)議棧的功能)通常由操作系統(tǒng)、網(wǎng)絡(luò)協(xié)議棧或相關(guān)網(wǎng)絡(luò)庫(kù)(如Socket庫(kù))實(shí)現(xiàn)。(程序員只需要調(diào)用對(duì)應(yīng)的API接口)
什么是Socket編程?
Socket套接字目的是將TCP/IP協(xié)議相關(guān)軟件移植到UNIX類系統(tǒng)中。設(shè)計(jì)者開(kāi)發(fā)了一個(gè)接口,以便應(yīng)用程序能簡(jiǎn)單地調(diào)用該接口通信,這個(gè)接口不斷完善,最終形成了Socket套接字。
簡(jiǎn)單來(lái)說(shuō):套接字對(duì)應(yīng)程序猿來(lái)說(shuō)就是一套網(wǎng)絡(luò)通信的接口,使用這套接口就可以完成網(wǎng)絡(luò)通信。
一、字節(jié)序:大端與小端
小端:主機(jī)字節(jié)序
- 低位字節(jié)存儲(chǔ)到內(nèi)存的低位地址
- PC機(jī)的數(shù)據(jù)存儲(chǔ)默認(rèn)小端
大端:網(wǎng)絡(luò)字節(jié)序
- 低位字節(jié)存儲(chǔ)到內(nèi)存的高位地址
- 套接字通信過(guò)程中操作的數(shù)據(jù)都是大端存儲(chǔ)的,包括:接收/發(fā)送的數(shù)據(jù)、IP地址、端口
主機(jī)字節(jié)序與網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換函數(shù)
#include <arpa/inet.h> // h: host, 主機(jī)字節(jié)序 // n: net, 網(wǎng)絡(luò)字節(jié)序 // s:short(port) // l:long(IP)// 這套api主要用于 網(wǎng)絡(luò)通信過(guò)程中 IP 和 端口 的 轉(zhuǎn)換 // 將一個(gè)短整形從主機(jī)字節(jié)序 -> 網(wǎng)絡(luò)字節(jié)序 uint16_t htons(uint16_t hostshort); serv_addr.sin_port = htons(SERV_PORT);//舉例:將端口號(hào)主機(jī)字節(jié)序轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)序// 將一個(gè)整形從主機(jī)字節(jié)序 -> 網(wǎng)絡(luò)字節(jié)序 uint32_t htonl(uint32_t hostlong); // 將一個(gè)短整形從網(wǎng)絡(luò)字節(jié)序 -> 主機(jī)字節(jié)序 uint16_t ntohs(uint16_t netshort) // 將一個(gè)整形從網(wǎng)絡(luò)字節(jié)序 -> 主機(jī)字節(jié)序 uint32_t ntohl(uint32_t netlong);
二、IP地址轉(zhuǎn)換
IP地址本質(zhì)是一個(gè)整形數(shù),但是在使用的過(guò)程中都是通過(guò)一個(gè)字符串來(lái)描述,下面的函數(shù)描述了如何將一個(gè)字符串類型的IP地址進(jìn)行大小端轉(zhuǎn)換:
//IP地址轉(zhuǎn)換函數(shù)(點(diǎn)分十進(jìn)制(字符串)→ 網(wǎng)絡(luò)二進(jìn)制)
int inet_pton(int af, const char *src, void *dst);
/*af:AF_INET、AF_INET6src:傳入?yún)?shù),IP地址(點(diǎn)分十進(jìn)制)dst:傳出參數(shù),轉(zhuǎn)換后的 網(wǎng)絡(luò)字節(jié)序的 IP地址。 返回值:轉(zhuǎn)換是否成功
*///網(wǎng)絡(luò)字節(jié)序→String
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
/*af:AF_INET、AF_INET6src: 傳入?yún)?shù),網(wǎng)絡(luò)字節(jié)序IP地址dst:傳出參數(shù),主機(jī)字節(jié)序(string IP)size: dst 的大小。返回值:指向dst的指針,含格式化后的字符串形式 IP 地址。
*/
//僅限處理IPv4
// 點(diǎn)分十進(jìn)制IP -> 大端整形
in_addr_t inet_addr (const char *cp);
// 大端整形 -> 點(diǎn)分十進(jìn)制IP
char* inet_ntoa(struct in_addr in);
三、sockaddr 數(shù)據(jù)結(jié)構(gòu)
//sockaddr的地址結(jié)構(gòu) man手冊(cè):man 7 ip
struct sockaddr_in {sa_family_t sin_family; /* address family: AF_INET */in_port_t sin_port; /* port in network byte order */struct in_addr sin_addr; /* internet address */
};
四、套接字相關(guān)函數(shù)
socket函數(shù):
創(chuàng)建套接字
#include <sys/socket.h>int socket(int domain, int type, int protocol);
/*
domain: 使用的地址族協(xié)議AF_INET、AF_INET6
type:SOCK_STREAM流式傳輸協(xié)議、SOCK_DGRAM報(bào)文傳輸協(xié)議
protocol: 一般寫(xiě)0即可, 使用默認(rèn)的協(xié)議
返回值:新套接字所對(duì)應(yīng)文件描述符
*/
bind函數(shù):
給socket綁定地址結(jié)構(gòu)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
/*
sockfd: socket文件描述符,socket()的返回值
addr: 傳入的地址結(jié)構(gòu)(包括IP和端口號(hào))(struct sockaddr *)&addr struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = htonl(INADDR_ANY);
addrlen: sizeof(addr) 地址結(jié)構(gòu)的大小。
*/
listen函數(shù):
設(shè)置同時(shí)與服務(wù)器建立連接的上限數(shù)
int listen(int sockfd, int backlog);
/*
sockfd: socket文件描述符,socket()的返回值
backlog: 上限連接數(shù),max = 128
*/
★accept函數(shù):
阻塞等待客戶端建立連接,返回一個(gè)與客戶端成功連接的socket文件描述符(一個(gè)新的用于通信的文件描述符)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*
sockfd: 監(jiān)聽(tīng)的文件描述符,socket()的返回值
addr:傳出參數(shù), 存儲(chǔ)了建立連接的客戶端的地址信息
addrlen: 傳入傳出參數(shù),存儲(chǔ)addr指向的內(nèi)存大小
返回值:返回能與客戶端進(jìn)行數(shù)據(jù)通信的 socket 對(duì)應(yīng)的文件描述。
*/
accept
函數(shù)被調(diào)用時(shí),它會(huì)阻塞程序的執(zhí)行,直到有客戶端連接請(qǐng)求到達(dá)。- 當(dāng)有客戶端連接請(qǐng)求到達(dá)時(shí),
accept
函數(shù)會(huì)接受連接請(qǐng)求,并創(chuàng)建一個(gè)新的套接字用于與客戶端通信。這個(gè)新的套接字是一個(gè)專門(mén)用于與該客戶端進(jìn)行通信的套接字,而原始的服務(wù)器套接字仍然保持在監(jiān)聽(tīng)狀態(tài)以接受其他連接請(qǐng)求。 - 如果提供了非空的
addr
參數(shù),accept
函數(shù)將會(huì)填充客戶端的地址信息,包括IP地址和端口號(hào)。 accept
函數(shù)返回新創(chuàng)建的套接字的文件描述符。通過(guò)該文件描述符,服務(wù)器可以與客戶端進(jìn)行通信,發(fā)送和接收數(shù)據(jù)。
connect函數(shù):
客戶端socket與服務(wù)器建立連接。成功連接服務(wù)器之后, 客戶端會(huì)自動(dòng)隨機(jī)綁定一個(gè)端口
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
sockfd: socket文件描述符,socket()函數(shù)返回值
addr:要連接的服務(wù)器的地址結(jié)構(gòu)(客戶端通常無(wú)法自己指定端口號(hào),由操作系統(tǒng)動(dòng)態(tài)分配) struct sockaddr_in srv_addr; // 服務(wù)器地址結(jié)構(gòu)srv_addr.sin_family = AF_INET;srv_addr.sin_port = 9527 跟服務(wù)器bind時(shí)設(shè)定的 port 完全一致。inet_pton(AF_INET, "服務(wù)器的IP地址",&srv_adrr.sin_addr.s_addr);
addrlen:服務(wù)器的地址結(jié)構(gòu)的大小
*/
write/send函數(shù):
向已建立鏈接的Socket的寫(xiě)緩沖區(qū)中寫(xiě)入數(shù)據(jù)
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
ssize_t send(int fd, const void *buf, size_t len, int flags);
/*fd: 用于通信的文件描述符, accept() 函數(shù)的返回值buf: 存放要寫(xiě)入的數(shù)據(jù)的緩沖區(qū)首地址count: 想要寫(xiě)入的字節(jié)數(shù)len: 要發(fā)送的字符串的長(zhǎng)度f(wàn)lags: 特殊的屬性, 一般不使用, 指定為 0返回值:實(shí)際發(fā)送的字節(jié)數(shù)
*/
read/recv函數(shù):
從一個(gè)已建立連接的 Socket 讀緩沖區(qū)中讀取數(shù)據(jù)
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int fd, void *buf, size_t size, int flags);
/*fd: 用于通信的文件描述符,accept() 函數(shù)的返回值buf: 一個(gè)指向內(nèi)存區(qū)域的指針,用于存儲(chǔ)讀取到的數(shù)據(jù)count: 指定最多讀取的字節(jié)數(shù),即緩沖區(qū)大小。size: 參數(shù)buf指向的內(nèi)存的容量flags: 特殊的屬性, 一般不使用, 指定為 0返回值:實(shí)際發(fā)送的字節(jié)數(shù) = 0,代表連接斷開(kāi);= -1 接收數(shù)據(jù)失敗
*/
?五、TCP通信流程
TCP面向連接、安全、流式傳輸。傳輸層協(xié)議
- 面向連接:是一個(gè)雙向連接,通過(guò)三次握手完成,斷開(kāi)連接需要通過(guò)四次揮手完成。
- 安全:tcp通信過(guò)程中,會(huì)對(duì)發(fā)送的每一數(shù)據(jù)包都會(huì)進(jìn)行校驗(yàn), 如果發(fā)現(xiàn)數(shù)據(jù)丟失, 會(huì)自動(dòng)重傳
- 流式傳輸:發(fā)送端和接收端處理數(shù)據(jù)的速度,數(shù)據(jù)的量都可以不一致
TCP通信流程圖:
5.1 服務(wù)端文件描述符種類
在tcp的服務(wù)器端, 有兩類文件描述符(客戶端只有通信的文件描述符)
監(jiān)聽(tīng)的文件描述符
lfd
- 只需要有一個(gè)
- 不負(fù)責(zé)和客戶端通信, 負(fù)責(zé)檢測(cè)客戶端的連接請(qǐng)求, 檢測(cè)到之后調(diào)用accept就可以建立新的連接
通信的文件描述符
cfd
- 負(fù)責(zé)和建立連接的客戶端通信
- 如果有N個(gè)客戶端和服務(wù)器建立了新的連接, 通信的文件描述符就有N個(gè),每個(gè)客戶端和服務(wù)器都對(duì)應(yīng)一個(gè)通信的文件描述符
???5.2 文件描述符的內(nèi)存結(jié)構(gòu)
強(qiáng)烈建議結(jié)合視頻觀看
文件描述符對(duì)應(yīng)的內(nèi)存結(jié)構(gòu):
一個(gè)文件文件描述符對(duì)應(yīng)兩塊內(nèi)存, 一塊內(nèi)存是讀緩沖區(qū), 一塊內(nèi)存是寫(xiě)緩沖區(qū)
- 注意:read/wirte函數(shù)并不是將數(shù)據(jù)直接發(fā)送/讀取到網(wǎng)絡(luò)流中,而是通過(guò)兩個(gè)緩沖區(qū)進(jìn)行操作,然后再由操作系統(tǒng)的內(nèi)核來(lái)將緩沖區(qū)的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)流中。
讀數(shù)據(jù): 通過(guò)文件描述符將內(nèi)存中的數(shù)據(jù)讀出, 這塊內(nèi)存稱之為讀緩沖區(qū)
寫(xiě)數(shù)據(jù): 通過(guò)文件描述符將數(shù)據(jù)寫(xiě)入到內(nèi)存中, 這塊內(nèi)存稱之為寫(xiě)緩沖區(qū)
監(jiān)聽(tīng)的文件描述符:
- 客戶端的連接請(qǐng)求會(huì)發(fā)送到服務(wù)器端監(jiān)聽(tīng)的文件描述符的讀緩沖區(qū)中
- 讀緩沖區(qū)中有數(shù)據(jù), 說(shuō)明有新的客戶端連接
- 調(diào)用accept()函數(shù), 這個(gè)函數(shù)會(huì)檢測(cè)監(jiān)聽(tīng)文件描述符的讀緩沖區(qū)
- 檢測(cè)不到數(shù)據(jù), 該函數(shù)阻塞
- 如果檢測(cè)到數(shù)據(jù), 解除阻塞, 新的連接建立
通信的文件描述符:
- 客戶端和服務(wù)器端都有通信的文件描述符
- 發(fā)送數(shù)據(jù):調(diào)用函數(shù) write() / send(),數(shù)據(jù)進(jìn)入到內(nèi)核中
- 數(shù)據(jù)并沒(méi)有被發(fā)送出去, 而是將數(shù)據(jù)寫(xiě)入到了通信的文件描述符對(duì)應(yīng)的寫(xiě)緩沖區(qū)中
- 內(nèi)核檢測(cè)到通信的文件描述符寫(xiě)緩沖區(qū)中有數(shù)據(jù), 內(nèi)核會(huì)將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)中
- 接收數(shù)據(jù): 調(diào)用的函數(shù) read() / recv(), 從內(nèi)核讀數(shù)據(jù)
- 數(shù)據(jù)如何進(jìn)入到內(nèi)核程序猿不需要處理, 數(shù)據(jù)進(jìn)入到通信的文件描述符的讀緩沖區(qū)中
- 數(shù)據(jù)進(jìn)入到內(nèi)核, 必須使用通信的文件描述符, 將數(shù)據(jù)從讀緩沖區(qū)中讀出即可
int main(int argc, char* argv[]) {struct sockaddr_in serv_addr, clit_addr;socklen_t clit_addr_len;
//初始化服務(wù)器IP地址和端口號(hào)serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//socketlfd = socket(AF_INET, SOCK_STREAM, 0);
//bindbind(lfd, (const struct sockaddr *)&serv_addr, sizeof(serv_addr));
//listenlisten(lfd, 128);clit_addr_len = sizeof(clit_addr);
//accept:阻塞等待連接,當(dāng)連接成功時(shí),客戶端的地址結(jié)構(gòu)會(huì)填入clit_addrcfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len);
//TCP通信已建立,處理邏輯省略close(lfd);close(cfd);
}
5.3 客戶端通信流程
只有一個(gè)用于通信的套接字
cfd
int main(int argc, char *argv[])
{int cfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serv_addr;serv_addr.sin_port = htons(SERV_PORT);serv_addr.sin_family = AF_INET;inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);int ret = connect(cfd, (const struct sockaddr *)&serv_addr, sizeof(serv_addr));
//通信已建立,自行處理數(shù)據(jù)邏輯close(cfd);return 0;
}
參考文獻(xiàn)
愛(ài)編程的大丙:B站同名視頻