套接字-Socket
1. 概念
1.1 局域網(wǎng)和廣域網(wǎng)
局域網(wǎng)(LAN)和廣域網(wǎng)(WAN)是兩種不同范圍的計(jì)算機(jī)網(wǎng)絡(luò),它們用于連接多臺(tái)計(jì)算機(jī)以實(shí)現(xiàn)數(shù)據(jù)共享和通信。
-
局域網(wǎng)(LAN):
- 定義: 局域網(wǎng)是一個(gè)較小范圍內(nèi)的網(wǎng)絡(luò),通常限定在某個(gè)地理區(qū)域,比如一個(gè)辦公室、學(xué)?;蛘呒彝?。
- 范圍: LAN 的范圍通常在幾百米到幾千米之間。
- 連接設(shè)備: 局域網(wǎng)連接的設(shè)備可以是計(jì)算機(jī)、打印機(jī)、服務(wù)器等,通過(guò)局域網(wǎng)內(nèi)的設(shè)備可以方便地共享資源和信息。
- 傳輸速度: 由于較小的范圍,局域網(wǎng)通常具有較高的傳輸速度和較低的延遲。
-
廣域網(wǎng)(WAN):
- 定義: 廣域網(wǎng)是一個(gè)覆蓋范圍更廣泛的網(wǎng)絡(luò),可以連接跨越城市、國(guó)家甚至是全球的多個(gè)局域網(wǎng)。
- 范圍: WAN 的范圍較大,可以覆蓋從幾千千米到全球范圍。
- 連接設(shè)備: 廣域網(wǎng)連接的設(shè)備可以是分布在不同地理位置的局域網(wǎng),通過(guò) WAN,這些設(shè)備可以進(jìn)行遠(yuǎn)程通信和數(shù)據(jù)交換。
- 傳輸速度: 由于覆蓋較大范圍,廣域網(wǎng)的傳輸速度通常相對(duì)較慢,而且可能存在較高的延遲。
-
連接方式:
- 局域網(wǎng): 通常通過(guò)以太網(wǎng)技術(shù)、Wi-Fi等局域網(wǎng)技術(shù)進(jìn)行連接。
- 廣域網(wǎng): 使用各種通信技術(shù),包括光纖、衛(wèi)星、電話線等,以連接不同地理位置的網(wǎng)絡(luò)。
-
應(yīng)用場(chǎng)景:
- 局域網(wǎng): 用于組織內(nèi)部、學(xué)校、家庭等相對(duì)小范圍的網(wǎng)絡(luò)需求。
- 廣域網(wǎng): 用于連接分布在不同城市、國(guó)家或全球范圍內(nèi)的組織和機(jī)構(gòu),實(shí)現(xiàn)遠(yuǎn)程通信和資源共享。
總的來(lái)說(shuō),局域網(wǎng)和廣域網(wǎng)在范圍、連接設(shè)備、傳輸速度以及應(yīng)用場(chǎng)景上有明顯的區(qū)別,它們?cè)诰W(wǎng)絡(luò)體系中協(xié)同工作,為不同規(guī)模和需求的組織提供了靈活的網(wǎng)絡(luò)解決方案。
1.2 IP
IP,或稱為Internet Protocol(互聯(lián)網(wǎng)協(xié)議),是一種**在計(jì)算機(jī)網(wǎng)絡(luò)中用于標(biāo)識(shí)和定位設(shè)備(如計(jì)算機(jī)、路由器、服務(wù)器等)的通信協(xié)議。**IP地址是在Internet Protocol中使用的標(biāo)識(shí)符,用于唯一標(biāo)識(shí)網(wǎng)絡(luò)中的每個(gè)設(shè)備。
- IPv4: 使用一個(gè)32位的整形數(shù)描述一個(gè)IP地址,4個(gè)字節(jié),int型
- IPv6: 使用一個(gè)128位的整形數(shù)描述一個(gè)IP地址,16個(gè)字節(jié)
- 查看IP地址:
# linux
$ ifconfig
# windows
$ ipconfig
# 測(cè)試網(wǎng)絡(luò)是否暢通
# 主機(jī)a: 192.168.1.11
# 當(dāng)前主機(jī): 192.168.1.12
$ ping 192.168.1.11 # 測(cè)試是否可用連接局域網(wǎng)
$ ping www.baidu.com # 測(cè)試是否可用連接外網(wǎng)
# 特殊的IP地址: 127.0.0.1 ==> 和本地的IP地址是等價(jià)的
# 假設(shè)當(dāng)前電腦沒(méi)有聯(lián)網(wǎng), 就沒(méi)有IP地址, 又要做網(wǎng)絡(luò)測(cè)試, 可用使用 127.0.0.1 進(jìn)行本地測(cè)試
1.3 端口
端口是計(jì)算機(jī)網(wǎng)絡(luò)中用于**標(biāo)識(shí)進(jìn)程或服務(wù)的抽象概念。在一臺(tái)計(jì)算機(jī)上,可以同時(shí)運(yùn)行多個(gè)網(wǎng)絡(luò)應(yīng)用程序或服務(wù),而端口就是用來(lái)區(qū)分它們的一種方式**。端口通過(guò)數(shù)字來(lái)標(biāo)識(shí),范圍從0到65535。
-
端口號(hào):
- 端口號(hào)是一個(gè)16位的數(shù)字,用于唯一標(biāo)識(shí)計(jì)算機(jī)上運(yùn)行的特定服務(wù)或應(yīng)用程序。端口號(hào)范圍從0到65535,其中0到1023是被稱為"知名端口",用于一些常見(jiàn)的服務(wù),如HTTP(80端口)和HTTPS(443端口)等。
-
TCP和UDP端口:
- 端口分為TCP端口和UDP端口,這取決于應(yīng)用程序所使用的傳輸協(xié)議。TCP(傳輸控制協(xié)議)和UDP(用戶數(shù)據(jù)報(bào)協(xié)議)是兩種常見(jiàn)的傳輸協(xié)議,它們使用不同的端口范圍。例如,HTTP通常使用TCP的80端口,而DNS服務(wù)通常使用UDP的53端口。
1.4 網(wǎng)絡(luò)分層模型
網(wǎng)絡(luò)分層模型是一種將計(jì)算機(jī)網(wǎng)絡(luò)功能劃分為不同層次的概念性框架,以簡(jiǎn)化網(wǎng)絡(luò)設(shè)計(jì)、管理和維護(hù)。其中最經(jīng)典和廣泛使用的分層模型是OSI(開(kāi)放系統(tǒng)互聯(lián))模型和TCP/IP模型。以下是這兩個(gè)模型的概述:
-
OSI模型(開(kāi)放系統(tǒng)互聯(lián)模型): OSI模型是由國(guó)際標(biāo)準(zhǔn)化組織(ISO)定義的一種七層模型,每一層都執(zhí)行特定的功能,且每一層的功能都建立在下一層提供的服務(wù)之上。這七個(gè)層次從下到上依次是:
- 物理層(Physical Layer): 處理物理連接和傳輸介質(zhì),例如電纜、光纖等。
- 數(shù)據(jù)鏈路層(Data Link Layer): 負(fù)責(zé)將物理層的數(shù)據(jù)劃分成幀,并提供數(shù)據(jù)的可靠傳輸。
- 網(wǎng)絡(luò)層(Network Layer): 處理數(shù)據(jù)包的路由和轉(zhuǎn)發(fā),實(shí)現(xiàn)不同網(wǎng)絡(luò)之間的通信。
- 傳輸層(Transport Layer): 提供端到端的通信控制,包括數(shù)據(jù)的分段、重組和錯(cuò)誤檢測(cè)。
- 會(huì)話層(Session Layer): 管理不同應(yīng)用程序之間的對(duì)話和會(huì)話。
- 表示層(Presentation Layer): 處理數(shù)據(jù)的格式轉(zhuǎn)換、加密和壓縮等。
- 應(yīng)用層(Application Layer): 提供網(wǎng)絡(luò)服務(wù)和應(yīng)用程序之間的接口。
-
TCP/IP模型: TCP/IP模型是互聯(lián)網(wǎng)上廣泛使用的模型,它包含四個(gè)層次,從下到上依次是:
-
網(wǎng)絡(luò)接口層(Network Interface Layer): 類似于OSI的物理層和數(shù)據(jù)鏈路層,處理硬件設(shè)備和網(wǎng)絡(luò)的連接。
-
網(wǎng)絡(luò)層(Internet Layer): 類似于OSI的網(wǎng)絡(luò)層,負(fù)責(zé)數(shù)據(jù)包的路由和轉(zhuǎn)發(fā)。(IP)
-
傳輸層(Transport Layer): 類似于OSI的傳輸層,提供端到端的通信控制。(TCP, UDP)
-
應(yīng)用層(Application Layer): 同樣對(duì)應(yīng)OSI的應(yīng)用層,提供網(wǎng)絡(luò)服務(wù)和應(yīng)用程序之間的接口。(HTTP, FTP, DNS)
1.5 數(shù)據(jù)封裝
2. socket編程
與套接字相關(guān)的函數(shù)被包含在頭文件sys/socket.h中。
2.1 字節(jié)序
字節(jié)序(Byte Order)指的是在計(jì)算機(jī)中,多字節(jié)數(shù)據(jù)類型的存儲(chǔ)順序(字符串就沒(méi)有)。具體來(lái)說(shuō),它指定了在內(nèi)存中多字節(jié)值的字節(jié)排列順序。在多字節(jié)數(shù)據(jù)類型中,比如16位、32位或64位的整數(shù),字節(jié)序定義了它們?cè)趦?nèi)存中的存儲(chǔ)方式。
有兩種主要的字節(jié)序:大端序(Big Endian)和小端序(Little Endian)。
-
大端序(Big Endian):
- 在大端序中,多字節(jié)數(shù)據(jù)的最高有效字節(jié)(Most Significant Byte,MSB)存儲(chǔ)在最低的內(nèi)存地址,而最低有效字節(jié)(Least Significant Byte,LSB)存儲(chǔ)在最高的內(nèi)存地址。
- 舉例來(lái)說(shuō),對(duì)于十六進(jìn)制值
0x12345678
:- 大端序:
12 34 56 78
- 大端序:
-
小端序(Little Endian):
- 在小端序中,多字節(jié)數(shù)據(jù)的最低有效字節(jié)存儲(chǔ)在最低的內(nèi)存地址,而最高有效字節(jié)存儲(chǔ)在最高的內(nèi)存地址。
- 對(duì)于同樣的十六進(jìn)制值
0x12345678
:- 小端序:
78 56 34 12
- 小端序:
// 有一個(gè)16進(jìn)制的數(shù), 有32位 (int): 0xab5c01ff
// 字節(jié)序, 最小的單位: char 字節(jié), int 有4個(gè)字節(jié), 需要將其拆分為4份
// 一個(gè)字節(jié) unsigned char, 最大值是 255(十進(jìn)制) ==> ff(16進(jìn)制)
內(nèi)存低地址位 內(nèi)存的高地址位
--------------------------------------------------------------------------->
小端: 0xff 0x01 0x5c 0xab
大端: 0xab 0x5c 0x01 0xff
在進(jìn)行跨平臺(tái)數(shù)據(jù)交換時(shí),需要考慮字節(jié)序的問(wèn)題,通常使用網(wǎng)絡(luò)字節(jié)序(也稱為大端序)來(lái)確保數(shù)據(jù)的正確傳輸。
2.2 主機(jī)字節(jié)序和網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換函數(shù)(端口)
從主機(jī)字節(jié)序到網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換函數(shù):htons、htonl
;從網(wǎng)絡(luò)字節(jié)序到主機(jī)字節(jié)序的轉(zhuǎn)換函數(shù):ntohs、ntohl
。
-
htons()
:主機(jī)字節(jié)序到網(wǎng)絡(luò)字節(jié)序的16位整數(shù)轉(zhuǎn)換。 -
htonl()
:主機(jī)字節(jié)序到網(wǎng)絡(luò)字節(jié)序的32位整數(shù)轉(zhuǎn)換。
#include <arpa/inet.h>
uint16_t hostShort = 0x1234;
uint32_t hostLong = 0x12345678;
uint16_t networkShort = htons(hostShort);
uint32_t networkLong = htonl(hostLong);
-
ntohs()
:網(wǎng)絡(luò)字節(jié)序到主機(jī)字節(jié)序的16位整數(shù)轉(zhuǎn)換。 -
ntohl()
:網(wǎng)絡(luò)字節(jié)序到主機(jī)字節(jié)序的32位整數(shù)轉(zhuǎn)換。
#include <arpa/inet.h>
uint16_t networkShort = 0x3412;
uint32_t networkLong = 0x78563412;
uint16_t hostShort = ntohs(networkShort);
uint32_t hostLong = ntohl(networkLong);
2.3 IP地址轉(zhuǎn)換
主機(jī)字節(jié)序的IP地址是字符串, 網(wǎng)絡(luò)字節(jié)序IP地址是整形
雖然IP地址本質(zhì)是一個(gè)整形數(shù),但是在使用的過(guò)程中都是通過(guò)一個(gè)字符串來(lái)描述,下面的函數(shù)描述了如何將一個(gè)字符串類型的IP地址進(jìn)行大小端轉(zhuǎn)換:
// 主機(jī)字節(jié)序的IP地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
int inet_pton(int af, const char *src, void *dst);
-
af
:地址族,可以是AF_INET
表示IPv4,或者AF_INET6
表示IPv6。 -
src
:以字符串形式表示的IP地址。 -
dst
:用于存儲(chǔ)轉(zhuǎn)換后的二進(jìn)制地址的緩沖區(qū)。
函數(shù)的返回值是一個(gè)整數(shù),如果轉(zhuǎn)換成功,則返回 1,如果提供的地址無(wú)效,則返回 0,如果發(fā)生錯(cuò)誤,則返回 -1。
- 將計(jì)算機(jī)內(nèi)部使用的二進(jìn)制格式的IPv4和IPv6地址表示轉(zhuǎn)換為人類可讀形式的字符串表示法的函數(shù)
#include <arpa/inet.h>
// 將大端的整形數(shù), 轉(zhuǎn)換為小端的點(diǎn)分十進(jìn)制的IP地址
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
-
af
:地址族,可以是AF_INET
表示IPv4,或者AF_INET6
表示IPv6。 -
src
:指向包含二進(jìn)制格式地址的指針。 -
dst
:用于存儲(chǔ)轉(zhuǎn)換后的字符串的緩沖區(qū)。 -
size
:緩沖區(qū)的大小。
函數(shù)的返回值是一個(gè)指向存儲(chǔ)在 dst
中的字符串的指針,失敗返回 NULL
并設(shè)置 errno
。
3. 套接字函數(shù)
3.1 sockaddr數(shù)據(jù)結(jié)構(gòu)
sockaddr
是一個(gè)通用的套接字地址結(jié)構(gòu),用于在網(wǎng)絡(luò)編程中表示網(wǎng)絡(luò)地址信息。
// 在寫(xiě)數(shù)據(jù)的時(shí)候不好用
struct sockaddr {
sa_family_t sa_family; // 地址族協(xié)議, ipv4
char sa_data[14]; // 端口(2字節(jié)) + IP地址(4字節(jié)) + 填充(8字節(jié))
}
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
struct in_addr
{
in_addr_t s_addr;
};
// sizeof(struct sockaddr) == sizeof(struct sockaddr_in)
struct sockaddr_in
{
sa_family_t sin_family; /* 地址族協(xié)議: AF_INET */
in_port_t sin_port; /* 端口, 2字節(jié)-> 大端 */
struct in_addr sin_addr; /* IP地址, 4字節(jié) -> 大端 */
/* 填充 8字節(jié) */
unsigned char sin_zero[sizeof (struct sockaddr) - sizeof(sin_family) -
sizeof (in_port_t) - sizeof (struct in_addr)];
};
這些結(jié)構(gòu)體用于在套接字編程中表示網(wǎng)絡(luò)地址,通過(guò)將 sockaddr_in
結(jié)構(gòu)體的地址轉(zhuǎn)換為 sockaddr
結(jié)構(gòu)體的地址,可以在函數(shù)參數(shù)中使用通用的 sockaddr
結(jié)構(gòu)。例如,在調(diào)用套接字創(chuàng)建函數(shù)時(shí),可以將一個(gè) sockaddr_in
結(jié)構(gòu)體的地址強(qiáng)制轉(zhuǎn)換為 sockaddr
結(jié)構(gòu)體的地址,以便在不同的網(wǎng)絡(luò)函數(shù)中使用。
3.2 套接字函數(shù)
使用套接字通信函數(shù)需要包含頭文件<arpa/inet.h>
,包含了這個(gè)頭文件<sys/socket.h>
就不用在包含了。
- 創(chuàng)建套接字函數(shù)
// 創(chuàng)建一個(gè)套接字
int socket(int domain, int type, int protocol);
參數(shù):
- domain: 使用的地址族協(xié)議
- AF_INET: 使用IPv4格式的ip地址
- AF_INET6: 使用IPv4格式的ip地址
- type:
- SOCK_STREAM: 使用流式的傳輸協(xié)議(TCP)
- SOCK_DGRAM: 使用報(bào)式(報(bào)文)的傳輸協(xié)議(UDP)
- protocol: 一般寫(xiě)0即可, 使用默認(rèn)的協(xié)議
- SOCK_STREAM: 流式傳輸默認(rèn)使用的是tcp
- SOCK_DGRAM: 報(bào)式傳輸默認(rèn)使用的udp
返回值:
- 成功: 可用于套接字通信的文件描述符
- 失敗: -1
函數(shù)的返回值是一個(gè)文件描述符,通過(guò)這個(gè)文件描述符可以操作內(nèi)核中的某一塊內(nèi)存,網(wǎng)絡(luò)通信是基于這個(gè)文件描述符來(lái)完成的。
- 綁定端口
// 將文件描述符和本地的IP與端口進(jìn)行綁定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數(shù):
- sockfd: 監(jiān)聽(tīng)的文件描述符, 通過(guò)socket()調(diào)用得到的返回值
- addr: 傳入?yún)?shù), 要綁定的IP和端口信息需要初始化到這個(gè)結(jié)構(gòu)體中,IP和端口要轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
- addrlen: 參數(shù)addr指向的內(nèi)存大小, sizeof(struct sockaddr)
返回值:成功返回0,失敗返回-1
- 設(shè)置監(jiān)聽(tīng)
// 給監(jiān)聽(tīng)的套接字設(shè)置監(jiān)聽(tīng)
int listen(int sockfd, int backlog);
參數(shù):
- sockfd: 文件描述符, 可以通過(guò)調(diào)用socket()得到,在監(jiān)聽(tīng)之前必須要綁定 bind()
- backlog: 同時(shí)能處理的最大連接要求,最大值為128
返回值:函數(shù)調(diào)用成功返回0,調(diào)用失敗返回 -1
- 等待并接受客戶端的連接請(qǐng)求
// 等待并接受客戶端的連接請(qǐng)求, 建立新的連接, 會(huì)得到一個(gè)新的文件描述符(通信的)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數(shù):
- sockfd: 監(jiān)聽(tīng)的文件描述符
- addr: 傳出參數(shù), 里邊存儲(chǔ)了建立連接的客戶端的地址信息
- addrlen: 傳入傳出參數(shù),用于存儲(chǔ)addr指向的內(nèi)存大小
返回值:函數(shù)調(diào)用成功,得到一個(gè)文件描述符, 用于和建立連接的這個(gè)客戶端通信,調(diào)用失敗返回 -1
這個(gè)函數(shù)是一個(gè)阻塞函數(shù),當(dāng)沒(méi)有新的客戶端連接請(qǐng)求的時(shí)候,該函數(shù)阻塞;當(dāng)檢測(cè)到有新的客戶端連接請(qǐng)求時(shí),阻塞解除,新連接就建立了,得到的返回值也是一個(gè)文件描述符,基于這個(gè)文件描述符就可以和客戶端通信了。
- 接收數(shù)據(jù)
// 接收數(shù)據(jù)
ssize_t read(int sockfd, void *buf, size_t size);
ssize_t recv(int sockfd, void *buf, size_t size, int flags);
參數(shù):
- sockfd: 用于通信的文件描述符, accept() 函數(shù)的返回值
- buf: 指向一塊有效內(nèi)存, 用于存儲(chǔ)接收是數(shù)據(jù)
- size: 參數(shù)buf指向的內(nèi)存的容量
- flags: 特殊的屬性, 一般不使用, 指定為 0
返回值:
- 大于0:實(shí)際接收的字節(jié)數(shù)
- 等于0:對(duì)方斷開(kāi)了連接
- -1:接收數(shù)據(jù)失敗了
如果連接沒(méi)有斷開(kāi),接收端接收不到數(shù)據(jù),接收數(shù)據(jù)的函數(shù)會(huì)阻塞等待數(shù)據(jù)到達(dá),數(shù)據(jù)到達(dá)后函數(shù)解除阻塞,開(kāi)始接收數(shù)據(jù),當(dāng)發(fā)送端斷開(kāi)連接,接收端無(wú)法接收到任何數(shù)據(jù),但是這時(shí)候就不會(huì)阻塞了,函數(shù)直接返回0。
- 發(fā)送數(shù)據(jù)
// 發(fā)送數(shù)據(jù)的函數(shù)
ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
參數(shù):
- fd: 通信的文件描述符, accept() 函數(shù)的返回值
- buf: 傳入?yún)?shù), 要發(fā)送的字符串
- len: 要發(fā)送的字符串的長(zhǎng)度
- flags: 特殊的屬性, 一般不使用, 指定為 0
返回值:
- 大于0:實(shí)際發(fā)送的字節(jié)數(shù),和參數(shù)len是相等的
- -1:發(fā)送數(shù)據(jù)失敗了
成功連接服務(wù)器之后, 客戶端會(huì)自動(dòng)隨機(jī)綁定一個(gè)端口
// 服務(wù)器端調(diào)用accept()的函數(shù), 第二個(gè)參數(shù)存儲(chǔ)的就是客戶端的IP和端口信息
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數(shù):
- sockfd: 通信的文件描述符, 通過(guò)調(diào)用socket()函數(shù)就得到了
- addr: 存儲(chǔ)了要連接的服務(wù)器端的地址信息: iP 和 端口,這個(gè)IP和端口也需要轉(zhuǎn)換為大端然后再賦值
- addrlen: addr指針指向的內(nèi)存的大小 sizeof(struct sockaddr)
返回值:連接成功返回0,連接失敗返回-1
4. TCP通信流程
TCP是一個(gè)**面向連接的,可靠的,流式傳輸協(xié)議,這個(gè)協(xié)議是一個(gè)傳輸層協(xié)議**。它確保數(shù)據(jù)在發(fā)送和接收之間按順序、可靠地傳輸。
- 面向連接:是一個(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ù)的量都可以不一致
4.1 服務(wù)器端通信流程
- 創(chuàng)建用于監(jiān)聽(tīng)的套接字, 這個(gè)套接字是一個(gè)文件描述符**
socket()
** - 將得到的監(jiān)聽(tīng)的文件描述符和本地的IP 端口進(jìn)行綁定
bind()
- 設(shè)置監(jiān)聽(tīng)(成功之后開(kāi)始監(jiān)聽(tīng), 監(jiān)聽(tīng)的是客戶端的連接)
listen()
- 等待并接受客戶端的連接請(qǐng)求, 建立新的連接, 會(huì)得到一個(gè)新的文件描述符(通信的),沒(méi)有新連接請(qǐng)求就阻塞
accept()
- 通信,讀寫(xiě)操作默認(rèn)都是阻塞的
read()/recv() write()/send()
- 斷開(kāi)連接, 關(guān)閉套接字
close()
在tcp的服務(wù)器端, 有兩類文件描述符
監(jiān)聽(tīng)的文件描述符
- 只需要有一個(gè)
- 不負(fù)責(zé)和客戶端通信, 負(fù)責(zé)檢測(cè)客戶端的連接請(qǐng)求, 檢測(cè)到之后調(diào)用accept就可以建立新的連接
通信的文件描述符
- 負(fù)責(zé)和建立連接的客戶端通信
- 如果有N個(gè)客戶端和服務(wù)器建立了新的連接, 通信的文件描述符就有N個(gè),每個(gè)客戶端和服務(wù)器都對(duì)應(yīng)一個(gè)通信的文件描述符
文件描述符對(duì)應(yīng)的內(nèi)存結(jié)構(gòu):
- 一個(gè)文件文件描述符對(duì)應(yīng)兩塊內(nèi)存, 一塊內(nèi)存是讀緩沖區(qū), 一塊內(nèi)存是寫(xiě)緩沖區(qū)
- 讀數(shù)據(jù): 通過(guò)文件描述符將內(nèi)存中的數(shù)據(jù)讀出, 這塊內(nèi)存稱之為讀緩沖區(qū)
- 寫(xiě)數(shù)據(jù): 通過(guò)文件描述符將數(shù)據(jù)寫(xiě)入到某塊內(nèi)存中, 這塊內(nèi)存稱之為寫(xiě)緩沖區(qū)
服務(wù)器端和客戶端用文件描述夫建立連接和發(fā)送接收數(shù)據(jù)的流程:
監(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ū)中讀出即可
4.2 客戶端的通信流程
在單線程的情況下客戶端通信的文件描述符有一個(gè), 沒(méi)有監(jiān)聽(tīng)的文件描述符
- 創(chuàng)建一個(gè)通信的套接字
socket()
- 連接服務(wù)器, 需要知道服務(wù)器綁定的IP和端口
connect()
- 通信
read()/recv() write()/send()
- 斷開(kāi)連接, 關(guān)閉文件描述符(套接字)
close()
4.3 通信代碼
從通信流程中可以看出, 客戶端在connect()的時(shí)候, 需要知道服務(wù)器的IP和端口號(hào), 所以必須要先啟動(dòng)服務(wù)器端(bind()函數(shù)綁定), 才能進(jìn)行通信
基于tcp的服務(wù)器端通信代碼
// server.cpp
// Created by 47468 on 2024/1/19.
//
#include <iostream>
#include "arpa/inet.h"
using namespace std;
#include "string.h"
#include "unistd.h"
int main() {
// 1. 創(chuàng)建監(jiān)聽(tīng)的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if(lfd == -1){
perror("socket");
exit(0);
}
// 2. 將socket()返回值和本地的IP端口綁定到一起
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000);
// INADDR_ANY代表本機(jī)的所有IP, 假設(shè)有三個(gè)網(wǎng)卡就有三個(gè)IP地址
// 這個(gè)宏可以代表任意一個(gè)IP地址
// 這個(gè)宏一般用于本地的綁定操作
addr.sin_addr.s_addr = INADDR_ANY;
// inet_pton(AF_INET, "192.168.237.131", &addr.sin_addr.s_addr);
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1){
perror("bind");
exit(0);
}
// 3. 設(shè)置監(jiān)聽(tīng)
ret = listen(lfd, 128);
if(ret == -1){
perror("listen");
exit(0);
}
// 4. 阻塞等待并接受客戶端連接
struct sockaddr_in cliaddr;
int clilen = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, (socklen_t*)(&clilen));
if(cfd == -1){
perror("accept");
exit(0);
}
// 打印客戶端的地址信息
char ip[24] = {0};
cout << "客戶端的ip地址: " << inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip))
<< ", 端口: " << ntohs(cliaddr.sin_port) << endl;
// 5. 和客戶端通信
while(1){
// 接收數(shù)據(jù)
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = read(cfd, buf, sizeof(buf));
if(len > 0){
cout << "客戶端say: " << buf << endl;
write(cfd, buf, len);
} else if (len == 0){
cout << "客戶端斷開(kāi)了連接" << endl;
break;
} else {
perror("read");
break;
}
}
close(lfd);
close(cfd);
return 0;
}
基于tcp通信的客戶端通信代碼
// client.cpp
// Created by 47468 on 2024/1/19.
//
#include <iostream>
#include "arpa/inet.h"
using namespace std;
#include "string.h"
#include "unistd.h"
int main() {
// 1. 創(chuàng)建監(jiān)聽(tīng)的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1){
perror("socket");
exit(0);
}
// 2. 鏈接服務(wù)器
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(10000); // 大端端口
inet_pton(AF_INET, "192.168.110.129", &addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1){
perror("connect");
exit(0);
}
// 3. 和服務(wù)器通信
int num = 0;
while(1){
// 發(fā)送數(shù)據(jù)
char buf[1024];
sprintf(buf, "你好服務(wù)器...%d\n", num++);
write(fd, buf, strlen(buf) + 1);
// 接收數(shù)據(jù)
memset(buf, 0, sizeof(buf));
int len = read(fd, buf, sizeof(buf));
if(len > 0){
cout << "服務(wù)器say: " << buf << endl;
} else if (len == 0){
cout << "服務(wù)器端斷開(kāi)了連接" << endl;
break;
} else {
perror("read");
break;
}
sleep(1);
}
close(fd);
return 0;
}
代碼測(cè)試圖示:
- 服務(wù)器先開(kāi)始, 客戶端先結(jié)束
- 服務(wù)器端先開(kāi)始, 服務(wù)器端先結(jié)束
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-810785.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-810785.html
到了這里,關(guān)于套接字通信(附帶單線程TCP套接字通信代碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!