網(wǎng)絡(luò)字節(jié)序
? 內(nèi)存中的多字節(jié)數(shù)據(jù)相對(duì)于內(nèi)存地址有大端和小端之分,磁盤文件中的多字節(jié)數(shù)據(jù)相對(duì)于文件中的偏移地址也有大端小端之分,網(wǎng)絡(luò)數(shù)據(jù)流同樣有大端小端之分。
? TCP/IP協(xié)議規(guī)定,網(wǎng)絡(luò)數(shù)據(jù)流應(yīng)采用大端字節(jié)序,即低地址高字節(jié)。不管這臺(tái)主機(jī)是大端機(jī)還是小端機(jī), 都會(huì)按照這個(gè)TCP/IP規(guī)定的網(wǎng)絡(luò)字節(jié)序來發(fā)送/接收數(shù)據(jù)。為使網(wǎng)絡(luò)程序具有可移植性,使同樣的C代碼在大端和小端計(jì)算機(jī)上編譯后都能正常運(yùn)行,可以調(diào)用以下庫函數(shù)做網(wǎng)絡(luò)字節(jié)序和主機(jī)字節(jié)序的轉(zhuǎn)換。
-
h表示host,n表示network,l表示32位長整數(shù),s表示16位短整數(shù)。
-
htonl表示將32位的長整數(shù)從主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。
-
ntohl表示將32位的長整數(shù)從網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序。
socket編程
? socket套接字通常指的是封裝了ip和port的結(jié)構(gòu)體,其是網(wǎng)絡(luò)編程中的一種通信機(jī)制,支持TCP/IP的網(wǎng)絡(luò)通信的基本操作單元,簡(jiǎn)單的說就是通信的兩方的一種約定,用套接字中的相關(guān)函數(shù)來完成通信過程。
socket 常見API
// 創(chuàng)建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務(wù)器)
int socket(int domain, int type, int protocol);
// 綁定端口號(hào) (TCP/UDP, 服務(wù)器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 開始監(jiān)聽socket (TCP, 服務(wù)器)
int listen(int socket, int backlog);
// 接收請(qǐng)求 (TCP, 服務(wù)器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立連接 (TCP, 客戶端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr結(jié)構(gòu)
套接字分類:
- 域間套接字 —— 本地通信
- 原始套接字 —— 允許繞過傳輸層,直接跟底層打交道,主要用來寫一些工具。
- 網(wǎng)絡(luò)套接字 —— 網(wǎng)絡(luò)通信
? 理論上是三種應(yīng)用場(chǎng)景,對(duì)應(yīng)的應(yīng)該是三套接口,但是Linux設(shè)計(jì)套接字的時(shí)候不想設(shè)計(jì)過多的接口,所以Linux將所有的接口進(jìn)行了統(tǒng)一,只使用sockaddr結(jié)構(gòu)體來描述這三種場(chǎng)景。 但是真正在基于IPv4編程時(shí), 使用的數(shù)據(jù)結(jié)構(gòu)是sockaddr_in; 這個(gè)結(jié)構(gòu)里主要有三部分信息: 地址類型, 端口號(hào), IP地址。
UDP編程
創(chuàng)建socket
#include <sys/types.h>
#include <sys/socket.h>
// 創(chuàng)建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務(wù)器)
int socket(int domain, int type, int protocol);
//參數(shù)
domain:指定網(wǎng)絡(luò)層的協(xié)議
AF_INT: 使用ipv4版本的ip協(xié)議
AF_INT6:使用ipv6版本的ip協(xié)議
AF_UNIX:本地通信
type: 指定套接字的類型
SOCK_DGRAM :使用UDP數(shù)據(jù)報(bào)套接字
SOCK_STREAM:使用TCP字節(jié)流套接字
protocol:指定使用的協(xié)議
0:使用套接字類型對(duì)應(yīng)的默認(rèn)協(xié)議
綁定socket
int bind(int socket, const struct sockaddr* address,socklen_t address_len);*
//參數(shù)
sockfd: socket函數(shù)返回的套接字描述符
addr: 地址信息結(jié)構(gòu)體成員,struct sockaddr 是一個(gè)通用地址信息結(jié)構(gòu)
address_len:地址信息結(jié)構(gòu)的長度
sendto發(fā)送數(shù)據(jù)
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen);
//參數(shù)
sockfd: 套接字描述符
buf: 要發(fā)送的數(shù)據(jù)
len: 發(fā)送數(shù)據(jù)的長度
flags: 0-阻塞發(fā)送
dest_addr: 目標(biāo)主機(jī)的地址信息結(jié)構(gòu)體
addrlen: 目標(biāo)主機(jī)地址信息結(jié)構(gòu)體的長度
//返回值
成功返回發(fā)送的字節(jié)數(shù)量,失敗返回-1
recvform接收數(shù)據(jù)
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags,struct sockaddr* src_addr, socklen_t* addrlen);
//參數(shù)
sockfd: 套接字描述符
buf: 將數(shù)據(jù)接收到buf當(dāng)中
len: buf的最大接收能力
flags: 0-阻塞接收
src_addr: 數(shù)據(jù)來源的主機(jī)地址信息結(jié)構(gòu)體
addrlen: 輸入輸出型參數(shù)
//返回值
成功則返回實(shí)際接收到的字符數(shù),失敗返回-1,錯(cuò)誤原因會(huì)存于errno中
關(guān)閉socket
close(int sockfd);
//sockfd: 套接字描述符
TCP編程
創(chuàng)建socket
#include <sys/types.h>
#include <sys/socket.h>
// 創(chuàng)建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務(wù)器)
int socket(int domain, int type, int protocol);
//參數(shù)
domain:指定網(wǎng)絡(luò)層的協(xié)議
AF_INT: 使用ipv4版本的ip協(xié)議
AF_INT6:使用ipv6版本的ip協(xié)議
AF_UNIX:本地通信
type: 指定套接字的類型
SOCK_DGRAM :使用UDP數(shù)據(jù)報(bào)套接字
SOCK_STREAM:使用TCP字節(jié)流套接字
protocol:指定使用的協(xié)議
0:使用套接字類型對(duì)應(yīng)的默認(rèn)協(xié)議
//返回值:
socket()打開一個(gè)網(wǎng)絡(luò)通訊端口,如果成功的話,就像open()一樣返回一個(gè)文件描述符,程序可以像讀寫文件一樣用read/write在網(wǎng)絡(luò)上收發(fā)數(shù)據(jù);調(diào)用出錯(cuò)則返回-1
綁定socket
int bind(int socket, const struct sockaddr* address,socklen_t address_len);*
//參數(shù)
sockfd: socket函數(shù)返回的套接字描述符
addr: 地址信息結(jié)構(gòu)體成員,struct sockaddr 是一個(gè)通用地址信息結(jié)構(gòu)
address_len:地址信息結(jié)構(gòu)的長度
listen監(jiān)聽套接字
//listen()聲明sockfd處于監(jiān)聽狀態(tài)
int listen(int sockfd, int backlog);
//參數(shù)
sockfd: 套接字描述符
backlog:已完成連接隊(duì)列的大小
//返回值:
??成功:0
??失?。?span id="n5n3t3z" class="token operator">-1
當(dāng)客戶端和服務(wù)端進(jìn)行三次握手的時(shí)候會(huì)存在兩種狀態(tài):連接還未建立和連接已建立,此時(shí)操作系統(tǒng)內(nèi)核中就會(huì)存在兩個(gè)隊(duì)列:未完成連接隊(duì)列和已完成連接隊(duì)列。當(dāng)完成三次握手后會(huì)由未完成連接隊(duì)列放到已完成連接隊(duì)列,而backlog就是已完成連接隊(duì)列的大小,backlog影響了服務(wù)端并發(fā)接收連接的能力。
accept服務(wù)端接收連接套接字
//從已經(jīng)完成連接隊(duì)列中獲取已經(jīng)完成三次握手的連接,沒有連接時(shí),調(diào)用accept會(huì)阻塞等待。
int accept(int sockfd, struct sockaddr* addr, socklen_t * addrlen);
//參數(shù)
sockfd:套接字描述符(listen_sockfd)
addr:輸出型參數(shù),保存客戶端地址信息結(jié)構(gòu)(客戶端IP,客戶端的端口)
addrlen:輸入輸出參數(shù),傳入緩沖區(qū)的大小,傳出客戶端地址信息結(jié)構(gòu)的長度
//返回值
??成功:返回新連接的套接字描述符
??失?。悍祷?span id="n5n3t3z" class="token operator">-1
三次握手的時(shí)候是對(duì)listen_sockfd進(jìn)行操作,當(dāng)調(diào)用accept()會(huì)在Tcp服務(wù)端內(nèi)部創(chuàng)建一個(gè)新的套接字new_sockfd,三次握手之后的數(shù)據(jù)收發(fā)都是多new_sockfd進(jìn)行操作。
connect客戶端連接套接字
//客戶端需要調(diào)用connect()連接服務(wù)器
int connect(int sockfd, const struct sockaddr * addr,socklen_t addrlen);
//參數(shù)
sockfd:套接字描述符(listen_sockfd)
addr:服務(wù)端地址信息結(jié)構(gòu)(服務(wù)端IP,服務(wù)端的端口)
addrlen:服務(wù)端地址信息結(jié)構(gòu)的長度
//返回值
??成功:返回0
??小于0,連接失敗
send發(fā)送數(shù)據(jù)
ssize_t send(int sockfd, const void * buf, size_t len, int flags);
//參數(shù)
sockfd:套接字描述符(new_sockfd)
buf:待要發(fā)送的數(shù)據(jù)
len:發(fā)送數(shù)據(jù)的長度
flags:
??0:阻塞發(fā)送
??MSG_OOB:發(fā)送帶外數(shù)據(jù),在緊急情況下所產(chǎn)生的數(shù)據(jù),會(huì)越過前面進(jìn)行排隊(duì)的數(shù)據(jù)優(yōu)先進(jìn)行發(fā)送。
//返回值
??大于0:返回發(fā)送的字節(jié)數(shù)量
??-1:發(fā)送失敗
recv接收數(shù)據(jù)
ssize_t recv(int sockfd, void * buf, size_t len, int flags);
//參數(shù)
sockfd:套接字描述符(new_sockfd)
buf:將接收的數(shù)據(jù)放到buf
len:buf的最大接收能力
flags:0:阻塞發(fā)送;如果客戶端沒有發(fā)送數(shù)據(jù),調(diào)用recv會(huì)阻塞
//返回值
??大于0:正常接收了多少字節(jié)數(shù)據(jù)
??等于0:對(duì)端將連接關(guān)閉了
??小于0:接受失敗
關(guān)閉socket
close(int sockfd);
//sockfd: 套接字描述符
工具
netstat
netstat -anp | grep [端口號(hào)]
- 查看端口的使用情況
telnet
- 進(jìn)入cmd,使用telnet模仿TCP三次握手建立連接,在cmd窗口輸入 “tenlet + 公網(wǎng)IP + 端口號(hào)” 即可模擬測(cè)試
地址轉(zhuǎn)換函數(shù)
sockaddr_in中的成員struct in_addr sin_addr表示32位 的IP 地址,但是我們通常用點(diǎn)分十進(jìn)制的字符串表示IP 地址,以下函數(shù)可以在字符串表示 和in_addr表示之間轉(zhuǎn)換
字符串轉(zhuǎn)in_addr的函數(shù):
in_addr轉(zhuǎn)字符串的函數(shù):
inet_ntoa函數(shù)把這個(gè)返回結(jié)果放到了靜態(tài)存儲(chǔ)區(qū),這個(gè)時(shí)候不需要我們手動(dòng)進(jìn)行釋放。
socket編程注意細(xì)節(jié)
- 客戶端沒有必要調(diào)用bind()固定一個(gè)端口號(hào),否則如果在同一臺(tái)機(jī)器上啟動(dòng)多個(gè)客戶端,就會(huì)出現(xiàn)端口號(hào)被占用導(dǎo)致不能正確建立連接。
- 服務(wù)器也不是必須調(diào)用bind(),但如果服務(wù)器不調(diào)用bind(), 內(nèi)核會(huì)自動(dòng)給服務(wù)器分配監(jiān)聽端口, 每次啟動(dòng)服務(wù)器時(shí)端口號(hào)都不一樣,客戶端要連接服務(wù)器就會(huì)遇到麻煩。
- 多進(jìn)程的客戶端代碼和單進(jìn)程是一樣的,父進(jìn)程負(fù)責(zé)accept,子進(jìn)程負(fù)責(zé)數(shù)據(jù)的接收和發(fā)送,若子進(jìn)程一直不退出,則父進(jìn)程一直在等待,永遠(yuǎn)無法接收新連接,可以使用自定義信號(hào)處理方式將SIGCHLD信號(hào)重新定義,當(dāng)子進(jìn)程退出發(fā)出SIGCHLD信號(hào)時(shí),父進(jìn)程則對(duì)子進(jìn)程的資源進(jìn)行回收。
- 建立好了tcp連接之后,我們就可以把得到的fd當(dāng)作文件描述符來使用,也可以使用read和 write函數(shù)進(jìn)行讀寫。
代碼案例
UDP案例
UDP · 程序員Jared/Linux - 碼云 - 開源中國 (gitee.com)
TCP案例文章來源:http://www.zghlxwxcb.cn/news/detail-601212.html
TCP · 程序員Jared/Linux - 碼云 - 開源中國 (gitee.com)文章來源地址http://www.zghlxwxcb.cn/news/detail-601212.html
到了這里,關(guān)于09.計(jì)算機(jī)網(wǎng)絡(luò)——套接字編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!