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