OpenHarmony TCP 通信編程實(shí)戰(zhàn)
想了解更多關(guān)于開源的內(nèi)容,請(qǐng)?jiān)L問(wèn):
前言
本人是一名大一學(xué)生,有幸被選拔進(jìn)了深圳技術(shù)大學(xué)第一屆開源鴻蒙菁英班,并在暑期培訓(xùn)進(jìn)行線上分享,故將講解的內(nèi)容也制作成帖子發(fā)上來(lái)作為學(xué)習(xí)筆記。在準(zhǔn)備分享的過(guò)程中,我基于學(xué)長(zhǎng)們的先前成果,結(jié)合開源鴻蒙源碼的最新版本進(jìn)行了相應(yīng)的調(diào)整和優(yōu)化,幫助大家更好地理解和應(yīng)用開源鴻蒙技術(shù)。本文旨在探討TCP(Transmission Control Protocol,傳輸控制協(xié)議)通訊的相關(guān)知識(shí)。通過(guò)本文,您將了解TCP協(xié)議的工作原理,以及如何運(yùn)用這一協(xié)議進(jìn)行通訊程序設(shè)計(jì)與實(shí)現(xiàn)。
環(huán)境
- OpenHarmony - 4.0 源碼
- 九聯(lián) unionpi_whale 開發(fā)板
一、TCP 通信介紹
1.概念
傳輸控制協(xié)議(TCP,Transmission Control Protocol)是為了在不可靠的互聯(lián)網(wǎng)絡(luò)上提供可靠的端到端字節(jié)流而專門設(shè)計(jì)的一個(gè)傳輸協(xié)議。
2.特性
- OpenHarmony是一個(gè)分布式操作系統(tǒng),它允許設(shè)備之間相互通信和協(xié)作。TCP是一種可靠的通信協(xié)議,適用于跨網(wǎng)絡(luò)的設(shè)備間通信。通過(guò)實(shí)現(xiàn)TCP通信,設(shè)備可以安全、可靠地進(jìn)行數(shù)據(jù)傳輸,實(shí)現(xiàn)各種協(xié)作功能。
- 廣泛的支持:TCP是互聯(lián)網(wǎng)上使用最廣泛的通信協(xié)議之一,幾乎所有的網(wǎng)絡(luò)設(shè)備和操作系統(tǒng)都支持TCP協(xié)議。這意味著使用TCP作為通信協(xié)議可以提高OpenHarmony與其他設(shè)備和系統(tǒng)的兼容性,降低了集成和交互的復(fù)雜性。
- 成熟的實(shí)現(xiàn)和開發(fā)工具:TCP協(xié)議的實(shí)現(xiàn)和開發(fā)工具已經(jīng)非常成熟,有許多可用的庫(kù)和工具可以用于快速開發(fā)和部署TCP通信功能。這可以節(jié)省開發(fā)時(shí)間和資源,并且降低了開發(fā)過(guò)程中的風(fēng)險(xiǎn)。
- 支持面向連接的通信模式:TCP是一種面向連接的通信協(xié)議,它建立了可靠的雙向通信通道,適合于需要長(zhǎng)時(shí)間持續(xù)通信的場(chǎng)景,如客戶端和服務(wù)器之間的通信。這種連接導(dǎo)向的通信模式可以滿足許多應(yīng)用場(chǎng)景的需求,包括實(shí)時(shí)數(shù)據(jù)傳輸、遠(yuǎn)程控制等。
- 通用性:TCP協(xié)議是傳輸層協(xié)議,與網(wǎng)絡(luò)類型無(wú)關(guān),因此它可以在各種類型的網(wǎng)絡(luò)中使用,包括有線網(wǎng)絡(luò)(如以太網(wǎng))和無(wú)線網(wǎng)絡(luò)(如Wi-Fi、蜂窩網(wǎng)絡(luò))。
3.兩個(gè)重要概念:客戶端與服務(wù)端
- 服務(wù)器被動(dòng)連接,客戶端主動(dòng)連接:在TCP連接中,服務(wù)器通常處于被動(dòng)狀態(tài),等待客戶端的連接請(qǐng)求。而客戶端則處于主動(dòng)狀態(tài),負(fù)責(zé)發(fā)起連接請(qǐng)求。一旦連接建立成功,雙方就可以進(jìn)行數(shù)據(jù)傳輸。
4.指令認(rèn)識(shí)
- 以下是幾個(gè)網(wǎng)絡(luò)調(diào)試常用的指令:
ifconfig # 用于Linux和OpenHarmony,常用于查看IP
ipconfig # 用于Windows,常用于查看IP
ping <IP> # 測(cè)試網(wǎng)絡(luò)連通性
5.本節(jié)課使用工具 – NetAssist
- 一個(gè)網(wǎng)絡(luò)調(diào)試工具:下載鏈接
二、Socket 編程(套接字編程)
1.socket()
socket() 函數(shù)是用于創(chuàng)建一個(gè)新的套接字(socket)的系統(tǒng)調(diào)用函數(shù)。套接字是一種通信機(jī)制,允許進(jìn)程通過(guò)網(wǎng)絡(luò)進(jìn)行通信。在網(wǎng)絡(luò)編程中,socket() 函數(shù)是一種創(chuàng)建套接字的標(biāo)準(zhǔn)方法,它通常在客戶端和服務(wù)器端代碼中都會(huì)用到。
- 函數(shù)原型:int socket(int domain, int type, int protocol);
- domain:指定套接字的地址族(Address Family),常見的包括:
- AF_INET:IPv4地址族
- AF_INET6:IPv6地址族
- AF_UNIX:UNIX本地域套接字
- type:指定套接字的類型,常見的包括:
- SOCK_STREAM:流套接字,用于面向連接的可靠數(shù)據(jù)傳輸,常用于TCP通信。
- SOCK_DGRAM:數(shù)據(jù)報(bào)套接字,用于無(wú)連接的不可靠數(shù)據(jù)傳輸,常用于UDP通信。
- protocol:指定協(xié)議,通常為0,表示使用默認(rèn)協(xié)議。
2.close()
- close()函數(shù)用于關(guān)閉一個(gè)已經(jīng)建立的TCP連接。關(guān)閉連接的目的是釋放資源并告知對(duì)方連接的結(jié)束。
- 函數(shù)原型:close(int fd)
- fd:表示待綁定的套接字的文件描述符。
3.sockaddr_in
- sockaddr_in 結(jié)構(gòu)體通常在網(wǎng)絡(luò)編程中用于指定套接字的地址信息。
- 包含在頭文件include <netinet/in.h>中
- 結(jié)構(gòu)體成員:.
struct sockaddr_in{
sa_family_t sin_family; /* 指定地址族,即網(wǎng)絡(luò)通信所使用的協(xié)議類型。*/
in_port_t sin_port; /* 表示端口號(hào)*/
struct in_addr sin_addr; /* 通配地址*/
unint8_t sin_zero[8]; /* 指定套接字的通信地址,從而確立通信的目標(biāo)。通常未使用
};
- sin_addr 用于表示 IPv4 地址,可以是特定的 IP 地址,也可以是通配地址INADDR_ANY;
- sin_port 則表示端口號(hào),用于標(biāo)識(shí)一個(gè)網(wǎng)絡(luò)服務(wù)。
- sin_family用于指定地址族,即網(wǎng)絡(luò)通信所使用的協(xié)議類型。在IPv4的上下文中,它的值通常是AF_INET。
- sin_zero是一個(gè)長(zhǎng)度為8的字節(jié)數(shù)組,通常未使用通過(guò)填充字段,可以指定套接字的通信地址,從而確立通信的目標(biāo)。
- 與系統(tǒng)調(diào)用交互:在進(jìn)行套接字編程時(shí),常常需要將sockaddr_in結(jié)構(gòu)體作為參數(shù)傳遞給一些系統(tǒng)調(diào)用函數(shù),例如bind()、connect()、accept()等。這些函數(shù)通過(guò)讀取sockaddr_in結(jié)構(gòu)體中的地址信息,可以確定套接字的本地或遠(yuǎn)程地址,從而進(jìn)行相應(yīng)的操作,如綁定、連接或接受連接。
4.bind()
- bind() 函數(shù)是用于將一個(gè)套接字(socket)與一個(gè)特定的地址(通常是 IP 地址和端口號(hào))綁定在一起的系統(tǒng)調(diào)用。在網(wǎng)絡(luò)編程中,bind() 函數(shù)通常用于服務(wù)器端程序,在其創(chuàng)建套接字后,將該套接字綁定到一個(gè)特定的端口上,以便監(jiān)聽該端口并接受客戶端的連接請(qǐng)求。
- 函數(shù)原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:指定要綁定的套接字文件描述符。
- addr:指向 sockaddr 結(jié)構(gòu)體的指針,該結(jié)構(gòu)體包含了要綁定的地址信息。
- addrlen:指定地址結(jié)構(gòu)體的長(zhǎng)度。
- 如果綁定成功,bind() 函數(shù)返回 0,否則返回 -1。
- 示例:
if (bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
{
perror("Error binding socket");
close(sockfd);
}
5.listen()
- listen() 函數(shù)用于將指定的套接字設(shè)置為監(jiān)聽狀態(tài),開始接受客戶端的連接請(qǐng)求。在服務(wù)器端編程中,通常在調(diào)用 bind() 函數(shù)綁定地址之后,使用 listen() 函數(shù)來(lái)準(zhǔn)備套接字接受連接請(qǐng)求。
- 函數(shù)原型:int listen(int sockfd, in MAX_CLIENTS);
- sockfd:表示待綁定的套接字的文件描述符,即通過(guò) socket() 函數(shù)創(chuàng)建的套接字。
- MAX_CLIENTS:參數(shù)指定了內(nèi)核允許在套接字隊(duì)列中等待的連接的最大數(shù)量。如果隊(duì)列已滿,后續(xù)的連接請(qǐng)求將被拒絕,直到有連接被接受或隊(duì)列中的連接被處理。這個(gè)參數(shù)通常設(shè)置為一個(gè)適當(dāng)?shù)闹?,以確保服務(wù)器能夠處理所有傳入的連接請(qǐng)求。
- 如果綁定成功,listen() 函數(shù)返回 0,否則返回 -1。
- 示例:
if (listen(server_sock, MAX_CLIENTS) == -1)
{
perror("Failed to listen");
close(server_sock);
}
6.accept()
- accept() 函數(shù)是在服務(wù)器端套接字上調(diào)用的系統(tǒng)調(diào)用,用于接受客戶端的連接請(qǐng)求,并創(chuàng)建一個(gè)新的套接字用于與客戶端進(jìn)行通信。這個(gè)函數(shù)通常在調(diào)用了 listen() 函數(shù)之后,用于實(shí)際接受傳入的連接。
- 函數(shù)原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)。
- sockfd:指定要接受連接請(qǐng)求的監(jiān)聽套接字文件描述符。
- addr:用于存儲(chǔ)客戶端地址信息的結(jié)構(gòu)體指針。如果不需要獲取客戶端地址信息,可以傳入 NULL。
- addrlen:指向一個(gè)整數(shù)的指針,表示客戶端地址信息結(jié)構(gòu)體的長(zhǎng)度。在調(diào)用 accept() 函數(shù)之前,應(yīng)該將其初始化為 sizeof(struct sockaddr)。
- 當(dāng)調(diào)用 accept() 函數(shù)時(shí),它會(huì)阻塞程序的執(zhí)行,直到有客戶端連接請(qǐng)求到達(dá)服務(wù)器套接字 sockfd。一旦有連接請(qǐng)求到達(dá),accept() 函數(shù)會(huì)從服務(wù)器的待處理連接隊(duì)列中取出一個(gè)連接請(qǐng)求,并創(chuàng)建一個(gè)新的套接字來(lái)處理該連接。這個(gè)新的套接字將用于與客戶端進(jìn)行通信,而服務(wù)器原始的套接字繼續(xù)監(jiān)聽其他連接請(qǐng)求。
- 如果綁定成功,bind() 函數(shù)返回 0,否則返回 -1。
- 示例:
if (accept(sockfd,(struct sockaddr *)&client_addr,&client_len) == -1)
{
perror("Error accepting connection");
close(sockfd);
}
7.connect()
- connect() 函數(shù)用于客戶端套接字向服務(wù)器發(fā)起連接請(qǐng)求。當(dāng)客戶端需要與遠(yuǎn)程服務(wù)器建立連接時(shí),就可以使用 connect() 函數(shù)。
- 函數(shù)原型: connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:表示客戶端套接字的文件描述符。
- addr:是一個(gè)指向 sockaddr 結(jié)構(gòu)體(或其子結(jié)構(gòu)體,如 sockaddr_in)的指針,其中包含要連接的服務(wù)器地址信息。
- addrlen:表示地址結(jié)構(gòu)體的長(zhǎng)度。
- 如果連接請(qǐng)求成功建立,則 connect() 函數(shù)返回 0,并客戶端套接字就可以開始與服務(wù)器進(jìn)行通信了,如果連接請(qǐng)求失敗,則 connect() 函數(shù)返回 -1。
- 在 connect() 函數(shù)調(diào)用期間,通常會(huì)發(fā)生阻塞。這意味著當(dāng) connect() 函數(shù)在建立連接時(shí),程序會(huì)暫停執(zhí)行,直到連接成功建立或者發(fā)生錯(cuò)誤。
- 需要注意的是,connect() 函數(shù)只能在套接字類型為流套接字(如 SOCK_STREAM)的情況下使用,因?yàn)樗怯糜诮⒖煽康?、面向連接的連接。對(duì)于數(shù)據(jù)報(bào)套接字(如 SOCK_DGRAM),應(yīng)該使用 sendto() 函數(shù)進(jìn)行發(fā)送,而不是 connect()。
- 示例:
if (connect(client_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0)
{
perror("Failed to connect to server");
}
8.recv() 與 read()
在TCP通信中,recv和read函數(shù)都是用來(lái)從socket接收數(shù)據(jù)的,但它們?cè)诓煌木幊陶Z(yǔ)言和平臺(tái)上有一些細(xì)微的區(qū)別。
(1)recv()
- 函數(shù)原型:recv(int sockfd, void *buf, size_t len, int flags);
- recv是通用的socket接收函數(shù),在許多編程語(yǔ)言和操作系統(tǒng)中都有實(shí)現(xiàn)。它的作用是從已連接的socket中接收數(shù)據(jù),并將接收到的數(shù)據(jù)存儲(chǔ)到指定的緩沖區(qū)中。
- recv函數(shù)通??梢栽O(shè)置一些參數(shù),比如要接收的最大字節(jié)數(shù)、接收數(shù)據(jù)的起始位置等。
- 其中sockfd是socket文件描述符,buf是接收數(shù)據(jù)的緩沖區(qū),len是要接收的最大字節(jié)數(shù),flags是一些控制接收行為的選項(xiàng)。
(2)read()
- 函數(shù)原型:read(int fd, void *buf, size_t count);
- read函數(shù)在OpenHarmony系統(tǒng)中用于從文件描述符(包括socket)中讀取數(shù)據(jù)。它的作用也是從socket接收數(shù)據(jù),類似于recv,但是read函數(shù)更多地用于文件I/O。
- read函數(shù)的用法與recv類似,也需要指定接收數(shù)據(jù)的緩沖區(qū)和最大字節(jié)數(shù)。
- buf是接收數(shù)據(jù)的緩沖區(qū),count是要讀取的最大字節(jié)數(shù)。
9.send() 與 write()
在TCP通信中,send()和write()函數(shù)在TCP通信中都起著發(fā)送數(shù)據(jù)的作用。
(1)send()
- 函數(shù)原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- send函數(shù)是通用的socket發(fā)送函數(shù),在許多編程語(yǔ)言和操作系統(tǒng)中都有實(shí)現(xiàn),它的作用是將數(shù)據(jù)從指定的緩沖區(qū)發(fā)送到已連接的socket 。
- send()函數(shù)通??梢栽O(shè)置一些參數(shù),比如要發(fā)送的數(shù)據(jù)長(zhǎng)度、發(fā)送數(shù)據(jù)的起始位置等。
- sockfd是套接字文件描述符,buf是要發(fā)送數(shù)據(jù)的緩沖區(qū),len是要發(fā)送的數(shù)據(jù)長(zhǎng)度,flags是一些控制發(fā)送行為的選項(xiàng)。如果不需要特定的控制選項(xiàng),可以將flags參數(shù)設(shè)置為0,以便使用默認(rèn)的行為。
(2)write()
- 函數(shù)原型:ssize_t write(int fd, const void *buf, size_t count);
- write函數(shù)在OpenHarmony系統(tǒng)中用于向文件描述符(包括socket)寫入數(shù)據(jù)。它的作用也是將數(shù)據(jù)從指定的緩沖區(qū)發(fā)送到文件描述符所代表的對(duì)象(可能是socket)。
- write函數(shù)的用法與send類似,也需要指定要發(fā)送的數(shù)據(jù)的緩沖區(qū)和數(shù)據(jù)長(zhǎng)度。
- fd是文件描述符,可以是socket文件描述符,buf是要發(fā)送數(shù)據(jù)的緩沖區(qū),count是要發(fā)送的數(shù)據(jù)長(zhǎng)度。
10.網(wǎng)絡(luò)編程中可能用到的幾個(gè)函數(shù)
- htons()函數(shù):用于將一個(gè) 16 位的無(wú)符號(hào)短整型數(shù)據(jù)從主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。在網(wǎng)絡(luò)通信中,不同的計(jì)算機(jī)可能具有不同的字節(jié)序,為了確保數(shù)據(jù)在網(wǎng)絡(luò)中的正確傳輸,需要進(jìn)行字節(jié)序的轉(zhuǎn)換。例如,如果主機(jī)是小端字節(jié)序,而網(wǎng)絡(luò)使用大端字節(jié)序,那么通過(guò) htons()可以將主機(jī)上存儲(chǔ)的短整型數(shù)據(jù)轉(zhuǎn)換為適合在網(wǎng)絡(luò)上傳輸?shù)拇蠖俗止?jié)序形式。
- inet_ntoa()函數(shù):用于將一個(gè) 32 位的網(wǎng)絡(luò)字節(jié)序的 IPv4 地址轉(zhuǎn)換為點(diǎn)分十進(jìn)制的字符串形式。例如,如果有一個(gè) IPv4 地址以網(wǎng)絡(luò)字節(jié)序存儲(chǔ)在一個(gè) in_addr 結(jié)構(gòu)體中,可以使用 inet_ntoa() 將其轉(zhuǎn)換為人們常見的點(diǎn)分十進(jìn)制表示,如 “192.168.0.1” 。
- ntohs()函數(shù):功能與 htons() 相反,它將一個(gè) 16 位的網(wǎng)絡(luò)字節(jié)序無(wú)符號(hào)短整型數(shù)據(jù)轉(zhuǎn)換為主機(jī)字節(jié)序。
- inet_pton()函數(shù):用于將一個(gè)點(diǎn)分十進(jìn)制表示的 IPv4 或 IPv6 地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序的二進(jìn)制形式,并存儲(chǔ)在指定的地址結(jié)構(gòu)中。舉例來(lái)說(shuō),如果你在使用C語(yǔ)言進(jìn)行網(wǎng)絡(luò)編程,你可能會(huì)在調(diào)用connect()函數(shù)連接到遠(yuǎn)程服務(wù)器之前,需要將字符串形式的IP地址轉(zhuǎn)換為套接字庫(kù)可以理解的形式,這時(shí)就需要使用inet_pton()函數(shù)。
三、TCP通信實(shí)例
本文文件結(jié)構(gòu)如下:
在OpenHarmony源碼根目錄下創(chuàng)建文件夾Mysample,下創(chuàng)文件夾tcp_demo:
1.服務(wù)端實(shí)例
(1)實(shí)現(xiàn)流程
- 初始化:
創(chuàng)建一個(gè)socket對(duì)象,通常使用socket(AF_INET, SOCK_STREAM, 0)創(chuàng)建一個(gè)面向連接的socket。設(shè)置socket的端點(diǎn)信息,包括IP地址和端口號(hào),通常使用bind()函數(shù)。啟動(dòng)監(jiān)聽,使用listen()函數(shù)設(shè)置監(jiān)聽隊(duì)列大小。 - 等待連接:使用accept()函數(shù)等待客戶端連接。accept()會(huì)返回一個(gè)新的socket對(duì)象,用于與客戶端進(jìn)行通信。
- 接收數(shù)據(jù):使用read()函數(shù)接收客戶端發(fā)送的數(shù)據(jù)。
- 發(fā)送數(shù)據(jù):使用send()函數(shù)將數(shù)據(jù)發(fā)送給客戶端。
- 關(guān)閉連接:完成數(shù)據(jù)交換后,使用close()函數(shù)關(guān)閉socket,釋放資源。
(2)代碼實(shí)現(xiàn)
"Mysample\tcp_demo\src\tcp_demo.cpp"
#include <iostream> // 引入標(biāo)準(zhǔn)輸入輸出流庫(kù)
#include <stdio.h> // 引入標(biāo)準(zhǔn)輸入輸出庫(kù)
#include <string.h> // 引入字符串處理庫(kù)
#include <netinet/in.h> // 引入IP網(wǎng)絡(luò)庫(kù)
#include <arpa/inet.h> // 引入地址轉(zhuǎn)換庫(kù)
#include <sys/socket.h> // 引入套接字庫(kù)
#include <unistd.h> // 引入U(xiǎn)nix系統(tǒng)調(diào)用庫(kù)
// 服務(wù)器端口號(hào)
#define SERVER_PORT 4567
// 最大客戶端數(shù)量
#define MAX_CLIENTS 5
// 緩沖區(qū)大小
#define TCP_BUFFER_SIZE 1024
int main()
{
// 創(chuàng)建服務(wù)器套接字和客戶端套接字
int server_sock, client_sock;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 創(chuàng)建一個(gè)服務(wù)端TCP套接字
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock == -1)
{
perror("Failed to create socket"); // 創(chuàng)建套接字失敗,打印錯(cuò)誤信息
exit(EXIT_FAILURE); // 退出程序
}
// 設(shè)置服務(wù)端地址
server_addr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
server_addr.sin_port = htons(SERVER_PORT); // 設(shè)置端口號(hào),htons確保端口號(hào)為網(wǎng)絡(luò)字節(jié)序
server_addr.sin_addr.s_addr = INADDR_ANY; // 設(shè)置IP地址為INADDR_ANY,表示接受任何接口的連接
// 綁定套接字
if (bind(server_sock, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
{
perror("Failed to bind socket"); // 綁定套接字失敗,打印錯(cuò)誤信息
close(server_sock); // 關(guān)閉套接字
exit(EXIT_FAILURE); // 退出程序
}
// 開始監(jiān)聽客戶端連接請(qǐng)求
if (listen(server_sock, MAX_CLIENTS) == -1)
{
perror("Failed to listen"); // 監(jiān)聽失敗,打印錯(cuò)誤信息
close(server_sock); // 關(guān)閉套接字
exit(EXIT_FAILURE); // 退出程序
}
std::cout << "Server is listening on port " << SERVER_PORT << std::endl; // 打印服務(wù)器監(jiān)聽端口信息
// 主循環(huán),等待客戶端連接
while(true)
{
// 接受客戶端連接請(qǐng)求
client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_sock == -1)
{
perror("Failed to accept connection"); // 接受連接失敗,打印錯(cuò)誤信息
continue; // 繼續(xù)下一次循環(huán)
}
// 打印出連接成功的客戶端的IP地址和端口號(hào)
std::cout << "Accepted connection from " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;
// 發(fā)送數(shù)據(jù)給客戶端
char request[] = "Hello, here is server!"; // 定義要發(fā)送的字符串
size_t bytes_write = write(client_sock, request, strlen(request)); // 發(fā)送數(shù)據(jù),并返回發(fā)送的字節(jié)數(shù)
if (bytes_write == -1)
{
perror("Failed to write data"); // 發(fā)送數(shù)據(jù)失敗,打印錯(cuò)誤信息
close(client_sock); // 關(guān)閉客戶端套接字
exit(EXIT_FAILURE); // 退出程序
}
// 接受客戶端發(fā)送的數(shù)據(jù)
char buffer[TCP_BUFFER_SIZE]; // 定義緩沖區(qū)用于接收數(shù)據(jù)
size_t bytes_read = read(client_sock, buffer, TCP_BUFFER_SIZE); // 從客戶端讀取數(shù)據(jù),并返回讀取的字節(jié)數(shù)
if (bytes_read == -1)
{
perror("Failed to read data"); // 讀取數(shù)據(jù)失敗,打印錯(cuò)誤信息
close(client_sock); // 關(guān)閉客戶端套接字
exit(EXIT_FAILURE); // 退出程序
}
else
{
std::cout << "Received data from client: " << buffer << std::endl; // 打印接收到的數(shù)據(jù)
break; // 退出循環(huán)
}
}
// 關(guān)閉服務(wù)器套接字
close(server_sock);
return 0;
}
2.客戶端實(shí)例
(1)實(shí)現(xiàn)流程
- 創(chuàng)建套接字:使用socket函數(shù)創(chuàng)建一個(gè)TCP套接字。參數(shù)通常包括地址族(AF_INET表示IPv4)、套接字類型(SOCK_STREAM表示TCP流式套接字)和協(xié)議(通常為0,表示使用默認(rèn)的TCP協(xié)議)。
- 設(shè)置服務(wù)器地址:創(chuàng)建一個(gè)sockaddr_in結(jié)構(gòu)體,用于存儲(chǔ)服務(wù)器的IP地址和端口號(hào)。
- 連接到服務(wù)器:使用connect函數(shù)將客戶端套接字連接到服務(wù)器。
- 發(fā)送數(shù)據(jù):使用write系統(tǒng)函數(shù)調(diào)用來(lái)發(fā)送數(shù)據(jù)到服務(wù)器。
- 接收數(shù)據(jù):使用read系統(tǒng)函數(shù)調(diào)用來(lái)從服務(wù)器接收數(shù)據(jù)。
- 關(guān)閉連接:當(dāng)數(shù)據(jù)傳輸完成后,使用close函數(shù)關(guān)閉套接字。
(2)代碼實(shí)現(xiàn)
"Mysample\tcp_demo\src\tcp_demo.cpp"
#include <iostream> // 引入輸入輸出流庫(kù)
#include <stdio.h> // 引入標(biāo)準(zhǔn)輸入輸出庫(kù)
#include <string.h> // 引入字符串處理庫(kù)
#include <cstring> // 引入C風(fēng)格字符串處理庫(kù)
#include <sys/socket.h> // 引入socket編程庫(kù)
#include <netinet/in.h> // 引入網(wǎng)絡(luò)地址結(jié)構(gòu)定義庫(kù)
#include <arpa/inet.h> // 引入網(wǎng)絡(luò)地址轉(zhuǎn)換庫(kù)
#include <unistd.h> // 引入U(xiǎn)nix標(biāo)準(zhǔn)庫(kù),提供close函數(shù)
#define TCP_BUFFER_SIZE 1024 // 定義TCP緩沖區(qū)大小為1024字節(jié)
int main()
{
int client_sock = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個(gè)IPv4的TCP socket
if (client_sock == -1) // 如果socket創(chuàng)建失敗
{
perror("Failed to create socket"); // 輸出錯(cuò)誤信息
exit(EXIT_FAILURE); // 退出程序
}
struct sockaddr_in server_addr; // 創(chuàng)建一個(gè)服務(wù)器地址結(jié)構(gòu)體
int SERVER_PORT; // 服務(wù)器端口變量
std::string SERVER_ADDR; // 服務(wù)器地址變量
std::cout << "Input server address: "; // 輸出提示信息
std::cin >> SERVER_ADDR; // 從標(biāo)準(zhǔn)輸入讀取服務(wù)器地址
std::cout << "Input server port: "; // 輸出提示信息
std::cin >> SERVER_PORT; // 從標(biāo)準(zhǔn)輸入讀取服務(wù)器端口
server_addr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
server_addr.sin_port = htons(SERVER_PORT); // 設(shè)置端口,網(wǎng)絡(luò)字節(jié)序
if (inet_pton(AF_INET, SERVER_ADDR.c_str(), &server_addr.sin_addr) <= 0) // 如果地址轉(zhuǎn)換失敗
{
std::cerr << "Invalid address/ Address not supported" << std::endl; // 輸出錯(cuò)誤信息
return -1; // 返回錯(cuò)誤
}
while (true) // 無(wú)限循環(huán)嘗試連接
{
if (connect(client_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) // 如果連接失敗
{
perror("Failed to connect to server"); // 輸出錯(cuò)誤信息
close(client_sock); // 關(guān)閉socket
exit(EXIT_FAILURE); // 退出程序
}
std::cout << "Connected to server " << inet_ntoa(server_addr.sin_addr) << ":" << ntohs(server_addr.sin_port) << std::endl; // 輸出連接成功信息
// 向服務(wù)器發(fā)送數(shù)據(jù)
char request[] = "Hello, here is client!"; // 創(chuàng)建請(qǐng)求字符串
size_t bytes_write = write(client_sock, request, strlen(request)); // 發(fā)送數(shù)據(jù)
if (bytes_write == -1) // 如果發(fā)送失敗
{
perror("Failed to write data"); // 輸出錯(cuò)誤信息
close(client_sock); // 關(guān)閉socket
exit(EXIT_FAILURE); // 退出程序
}
// 從服務(wù)器接收數(shù)據(jù)
char buffer[TCP_BUFFER_SIZE]; // 創(chuàng)建緩沖區(qū)
size_t bytes_read = read(client_sock, buffer, TCP_BUFFER_SIZE); // 讀取數(shù)據(jù)
if (bytes_read == -1) // 如果接收失敗
{
perror("Failed to read data"); // 輸出錯(cuò)誤信息
close(client_sock); // 關(guān)閉socket
exit(EXIT_FAILURE); // 退出程序
}
else // 如果接收成功
{
std::cout << "Received data from server: " << buffer << std::endl; // 輸出接收到的數(shù)據(jù)
break; // 退出循環(huán)
}
}
// 關(guān)閉
close(client_sock); // 關(guān)閉socket
return 0; // 程序成功結(jié)束
}
3.編譯構(gòu)建文件
"C:\Users\LIGANG\Desktop\Mysample\tcp_demo\BUILD.gn"
import("http://build/ohos.gni") # 導(dǎo)入編譯模板
ohos_executable("tcp") { # 可執(zhí)行模塊
sources = [ # 模塊源碼
"src/tcp_demo.cpp"
]
cflags = []
cflags_c = []
cflags_cc = []
ldflags = []
configs = []
deps =[] # 部件內(nèi)部依賴
part_name = "tcp_demo" # 所屬部件名稱,必選
install_enable = true # 是否默認(rèn)安裝(缺省默認(rèn)不安裝),可選
}
"C:\Users\LIGANG\Desktop\Mysample\tcp_demo\bundle.json"
{
"name": "@ohos/tcp_demo",
"description": "",
"version": "3.1",
"license": "Apache License 2.0",
"publishAs": "code-segment",
"segment": {
"destPath": "Mysample/tcp_demo"
},
"dirs": {},
"scripts": {},
"component": {
"name": "tcp_demo",
"subsystem": "Mysample",
"syscap": [],
"features": [],
"adapted_system_type": [
"standard"
],
"rom": "10KB",
"ram": "10KB",
"deps": {
"components": [],
"third_party": []
},
"build": {
"sub_component": [
"http://Mysample/tcp_demo:tcp"
],
"inner_kits": [],
"test": []
}
}
}
4.編譯
- 命令行方式
./build.sh --product-name {product_name} #全量編譯
./build.sh --product-name {product_name} --build-target {target_name} #單獨(dú)編譯部件
./build.sh --product-name {product_name} --build-target {target_name} --fast-rebuild #快速重建
- hb方式
hb set #設(shè)置編譯參數(shù)
hb build #全量編譯
hb build -T {target_name} #單獨(dú)編譯部件
hb build -T {target_name} --fast-rebuild #快速重建
- 我們這里使用hb方式來(lái)進(jìn)行編譯。在終端輸入命令hb set,選擇standard和unionpi_whale,在終端輸入命令hb build -T tcp_demo。
- 對(duì)編譯有疑問(wèn)的讀者可查看筆者另外一篇文章【FFH】OpenHarmony構(gòu)建編譯實(shí)戰(zhàn),此處不做贅述。
- 編譯產(chǎn)物在out/board/product目錄下。
5.燒錄
- 全量燒錄: 適合更新版本或者代碼大變動(dòng)打包鏡像->RKDevTool燒錄。
- HDC工具:適合代碼更新時(shí)單獨(dú)發(fā)送所需文件。
- 找到可執(zhí)行文件tcp,并將其放置到電腦hdc.exe同級(jí)目錄下。
- 連接設(shè)備:將開發(fā)板上電,并連接電腦。
- whale開發(fā)板燒錄口為藍(lán)色USB口上層口,使用USBtoUSB線燒錄。
- 從hdc文件夾下進(jìn)入終端,輸入hdc list targets檢查是否連接好,檢測(cè)到設(shè)備后輸-入hdc smode授予進(jìn)程root權(quán)限,再輸入hdc shell mount -o rw,remount /掛載分區(qū),并且賦予可寫權(quán)限。
- 輸入hdc shell進(jìn)入開發(fā)板終端,mkdir sample創(chuàng)建文件夾,exit退出終端。
- hdc file send ./tcp /sample/傳輸文件。(將當(dāng)前目錄下的hello文件傳輸?shù)介_發(fā)板的sample目錄下)
- hdc shell再次進(jìn)入開發(fā)板終端,cd sample進(jìn)入文件夾,chmod 777 *給程序賦予可執(zhí)行權(quán)限。
6.測(cè)試并執(zhí)行
- 服務(wù)端程序測(cè)試:
將開發(fā)板連接上網(wǎng)絡(luò),通過(guò)hdc.exe工具執(zhí)行命令ifconfig查看開發(fā)板IP地址:
- 通過(guò)netassist模擬客戶端,選擇TCP Client,填入開發(fā)板地址與端口號(hào):
- 在sample目錄下執(zhí)行./tcp_demo命令,啟動(dòng)程序,終端打印提示信息:
- 點(diǎn)擊netassist模擬客戶端連接按鈕,可以看到與開發(fā)板服務(wù)端連接成功,并接受到開發(fā)板發(fā)來(lái)的Hello, here is server!消息,終端也打印連接成功的提示信息。
- 點(diǎn)擊發(fā)送按鈕發(fā)送Welcome to NetAssist給開發(fā)板服務(wù)端,終端也打印相對(duì)應(yīng)提示信息:
- 程序結(jié)束,測(cè)試成功。
客戶端程序測(cè)試:
- 將開發(fā)板連接上網(wǎng)絡(luò)。
- 使用ipconfig查看PC主機(jī)IP地址:
- 通過(guò)netassist模擬服務(wù)端,選擇TCP Server,填入開發(fā)板地址與端口號(hào),單擊打開按鈕開啟監(jiān)聽:
- 在sample目錄下執(zhí)行./tcp_demo命令,啟動(dòng)程序,輸入服務(wù)端IP與端口號(hào):
- 可見終端打印連接成功的提示信息,主機(jī)收到了開發(fā)板客戶端發(fā)來(lái)的Hello, here is client!消息:
- 點(diǎn)擊發(fā)送按鈕發(fā)送Welcome to NetAssist給開發(fā)板客戶端,終端也打印相對(duì)應(yīng)提示信息:
- 程序結(jié)束,測(cè)試成功。
7.番外
此程序意在說(shuō)明一臺(tái)設(shè)備既可以作為客戶端也可以作為服務(wù)端,程序?qū)崿F(xiàn)了本機(jī)先作為服務(wù)端與PC主機(jī)進(jìn)行TCP通信,后兩者交換身份,本機(jī)作為客戶端。筆者測(cè)試過(guò),程序無(wú)誤可以正常運(yùn)行,在此不做贅述。有興趣的讀者可以作嘗試,
"C:\Users\LIGANG\Desktop\Mysample\tcp_demo\src\tcp_demo.cpp"
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVER_PORT1 4567 // 服務(wù)器端口號(hào)
#define SERVER_PORT2 7654 // 服務(wù)器端口號(hào)
#define MAX_CLIENTS 5 // 最大客戶端數(shù)量
#define TCP_BUFFER_SIZE 1024 // 緩沖區(qū)大小
int main()
{
// ------------------------------------本機(jī)作為服務(wù)端--------------------------------------------
int server_sock, client_sock; // 服務(wù)器套接字和客戶端套接字
struct sockaddr_in server_addr, client_addr; // 服務(wù)器地址和客戶端地址
socklen_t client_addr_len = sizeof(client_addr); // 客戶端地址長(zhǎng)度
// 創(chuàng)建一個(gè)服務(wù)端TCP套接字
server_sock = socket(AF_INET, SOCK_STREAM, 0);
if(server_sock == -1)
{
perror("Failed to create socket");
exit(EXIT_FAILURE);
}
// 設(shè)置服務(wù)端地址
server_addr.sin_family = AF_INET; // 使用IPv4協(xié)議
server_addr.sin_port = htons(SERVER_PORT1); // 端口號(hào)
server_addr.sin_addr.s_addr = INADDR_ANY; // 監(jiān)聽所有可用的IP地址
// 綁定套接字
if (bind(server_sock, (struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
{
perror("Failed to bind socket");
close(server_sock);
exit(EXIT_FAILURE);
}
// 開始監(jiān)聽客戶端連接請(qǐng)求
if (listen(server_sock, MAX_CLIENTS) == -1)
{
perror("Failed to listen");
close(server_sock);
exit(EXIT_FAILURE);
}
std::cout << "Server is listening on port " << SERVER_PORT1 << std::endl;
while(true)
{
// 接受客戶端連接請(qǐng)求
client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_sock == -1)
{
perror("Failed to accept connection");
continue;
}
// 打印出連接成功的客戶端的IP地址和端口號(hào)
std::cout<<"Accepted connection from "<<inet_ntoa(client_addr.sin_addr)<<":"<<ntohs(client_addr.sin_port)<<std::endl;
// 發(fā)送數(shù)據(jù)給客戶端
char request[] = "Hello, here is server!";
size_t bytes_write = write(client_sock, request, strlen(request));
if (bytes_write == -1)
{
perror("Failed to write data");
close(client_sock);
exit(EXIT_FAILURE);
}
// 接受客戶端發(fā)送的數(shù)據(jù)
char buffer[TCP_BUFFER_SIZE];
size_t bytes_read = read(client_sock, buffer, TCP_BUFFER_SIZE);
if (bytes_read == -1)
{
perror("Failed to read data");
close(client_sock);
exit(EXIT_FAILURE);
}
else
{
std::cout<<"Received data from client: "<<buffer<<std::endl;
break;
}
}
// 關(guān)閉
close(server_sock);
// ------------------------------------本機(jī)作為客戶端--------------------------------------------
// 配置服務(wù)器地址信息
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT2);
// 原來(lái)的客戶端地址作為服務(wù)端地址
server_addr.sin_addr.s_addr = client_addr.sin_addr.s_addr;
// 創(chuàng)建客戶端套接字
client_sock = socket(AF_INET, SOCK_STREAM, 0);
if(client_sock == -1)
{
perror("Failed to create socket");
exit(EXIT_FAILURE);
}
// 連接服務(wù)器
while(true)
{
// 連接服務(wù)器
if (connect(client_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("Failed to connect to server");
close(client_sock);
exit(EXIT_FAILURE);
}
// 打印出連接成功的服務(wù)器的IP地址和端口號(hào)
std::cout<<"Connected to server "<<inet_ntoa(server_addr.sin_addr)<<":"<<ntohs(server_addr.sin_port)<<std::endl;
// 向服務(wù)器發(fā)送數(shù)據(jù)
char request[] = "Hello, here is client!";
size_t bytes_write = write(client_sock, request, strlen(request));
if (bytes_write == -1)
{
perror("Failed to write data");
close(client_sock);
exit(EXIT_FAILURE);
}
// 從服務(wù)器接收數(shù)據(jù)
char buffer[TCP_BUFFER_SIZE];
size_t bytes_read = read(client_sock, buffer, TCP_BUFFER_SIZE);
if (bytes_read == -1)
{
perror("Failed to read data");
close(client_sock);
exit(EXIT_FAILURE);
}
else
{
std::cout<<"Received data from server: "<<buffer<<std::endl;
break;
}
}
// 關(guān)閉
close(client_sock);
return 0;
}