Linux之套接字UDP實(shí)現(xiàn)網(wǎng)絡(luò)通信
1.引言
? 套接字(Socket)是計(jì)算機(jī)網(wǎng)絡(luò)中實(shí)現(xiàn)網(wǎng)絡(luò)通信的一種編程接口。它提供了應(yīng)用程序與網(wǎng)絡(luò)通信之間的一座橋梁,因?yàn)樗试S應(yīng)用程序通過網(wǎng)絡(luò)發(fā)送和接收相應(yīng)的數(shù)據(jù)以實(shí)現(xiàn)不同主機(jī)之間的通信。
?
通常套接字由以下兩部分組成:
1.網(wǎng)絡(luò)IP和端口號:IP用來標(biāo)識主機(jī),而端口號可以標(biāo)識到單臺主機(jī)的唯一進(jìn)程。
2.通信協(xié)議:套接字通過規(guī)定通信協(xié)議來制定數(shù)據(jù)傳輸和發(fā)送的規(guī)則。常見的有TCP和UDP等協(xié)議。
TCP是一種面向連接的協(xié)議,提供可靠的、有序的、基于字節(jié)流的數(shù)據(jù)傳輸。
UDP是一種無連接的協(xié)議,提供不可靠的、無序的、基于數(shù)據(jù)報(bào)的數(shù)據(jù)傳輸。
? 我們今天要實(shí)現(xiàn)的是通過UDP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)通信。UDP協(xié)議通信雖然無連接不可靠,可是足夠簡單到我們了解通信的基本原理。
那么話不多講,我們趕快看看我們學(xué)習(xí)完今天這一篇能夠?qū)崿F(xiàn)出怎么樣的結(jié)果吧:
我們通過實(shí)現(xiàn)客戶端和服務(wù)器端,實(shí)現(xiàn)了通過套接字UDP創(chuàng)建了一個服務(wù)器,之后通過客戶端鏈接并且通信的一個功能。
事不宜遲,我們馬上實(shí)現(xiàn)!
2.具體實(shí)現(xiàn)
? 首先我們需要明確具體的大思路: 先服務(wù)器端創(chuàng)建socket套接字,并recvfrom接收到??蛻舳艘矂?chuàng)建套接字綁定后確定到唯一IP和端口號之后即可進(jìn)行通信。
? 在具體實(shí)現(xiàn)之前我們首先需要一些必要的套接字接口
2.1需要知道的套接字接口
1.socket()
? socket函數(shù)是用于創(chuàng)建套接字的函數(shù),創(chuàng)建成功返回文件描述符fd,失敗返回-1;
int socket(int domain, int type, int protocol);
? 參數(shù)說明:
-
domain
:指定套接字的地址族(Address Family)
今天我們選擇:
AF_INET
:IPv4 地址族
-
type
:指定套接字的類型(Socket Type)
今天我們選擇:
-
SOCK_DGRAM
:無連接的數(shù)據(jù)報(bào)套接字,用于 UDP 協(xié)議
-
-
protocol
:可選參數(shù),指定具體的傳輸協(xié)議。常用的有:
? 今天我們選擇:
-
0
:自動選擇合適的協(xié)議
-
2.bind()
? 在Linux下,bind()
函數(shù)用于將一個套接字(socket)與特定的IP地址和端口號進(jìn)行綁定。
*int bind(int sockfd, const struct sockaddr addr,socklen_t addrlen);
參數(shù)說明:
-
sockfd
:要進(jìn)行綁定的套接字的文件描述符。 -
addr
:指向一個struct sockaddr
結(jié)構(gòu)體的指針,其中包含要綁定的IP地址和端口號信息。 -
addrlen
:addr
結(jié)構(gòu)體的長度。
在綁定bind的第二個參數(shù)中,我們也需要用到庫中定義好的sockaddr_in結(jié)構(gòu)體來初始化!
具體結(jié)構(gòu)體struct sockaddr_in說明:
結(jié)構(gòu)體中有三個值也需要初始化指定一下:
sin_family
:表示地址族(Address Family),一般為AF_INET
。
sin_port
:表示端口號。它是一個 16 位的整數(shù),使用網(wǎng)絡(luò)字節(jié)序(大端字節(jié)序)表示。在使用時,通常需要使用htons()
函數(shù)將主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。
sin_addr
:表示 IPv4 地址。它是一個struct in_addr
類型的結(jié)構(gòu)體,用于存儲 32 位的 IPv4 地址。一般服務(wù)端用INADDR_ANY,讓udp_server在啟動時候可以綁定任何ip.
? 客戶端用inet_addr函數(shù)將字符串轉(zhuǎn)化成32位無符號整數(shù)
3.recvfrom()
從套接字接收數(shù)據(jù),并獲取發(fā)送方的地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
參數(shù)說明:
-
sockfd
:接收數(shù)據(jù)的套接字的文件描述符。 -
buf
:指向接收數(shù)據(jù)的緩沖區(qū)。 -
len
:緩沖區(qū)的長度。 -
flags
:可選的標(biāo)志參數(shù),用于影響接收操作的行為,通常設(shè)為 0。 -
src_addr
:用于存儲發(fā)送方的地址信息(對于面向數(shù)據(jù)報(bào)的套接字)。它是一個struct sockaddr
結(jié)構(gòu)體的指針。 -
addrlen
:src_addr
結(jié)構(gòu)體的長度,作為輸入?yún)?shù)指定src_addr
緩沖區(qū)的大小,作為輸出參數(shù)返回實(shí)際地址的長度。
4.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
:要發(fā)送數(shù)據(jù)的套接字的文件描述符。 -
buf
:指向要發(fā)送數(shù)據(jù)的緩沖區(qū)。 -
len
:要發(fā)送數(shù)據(jù)的長度。 -
flags
:可選的標(biāo)志參數(shù),用于影響發(fā)送操作的行為,通常設(shè)為 0。 -
dest_addr
:指向目標(biāo)地址(接收方地址)的結(jié)構(gòu)體指針,可以是struct sockaddr
或其派生類型的指針。 -
addrlen
:dest_addr
結(jié)構(gòu)體的長度。
2.2服務(wù)器端server.hpp
? 在服務(wù)器的頭文件中,我們首先需要定義一個udpserver的類,服務(wù)器類中需要有服務(wù)器的初始化與啟動命令,當(dāng)然需要有構(gòu)造析構(gòu)等。默認(rèn)的私有成員是**_sock套接字和port端口**
const static uint16_t default_port = 8080;
class UdpServer
{
public:
UdpServer(uint16_t port = default_port)
:_port(port)
{
std::cout<< "server addr: "<<_port <<std::endl;
}
~UdpServer() {}
void InitServer() //初始化服務(wù)器
{
_sock = socket(AF_INET,SOCK_DGRAM,0);
if(_sock < 0)
{
std::cerr << " socket create err " << std::endl;
}
std::cout << "create socket success: " << _sock << std::endl;
struct sockaddr_in local; //利用庫中創(chuàng)建好的結(jié)構(gòu)體來初始化socket
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 本地主機(jī)序列轉(zhuǎn)網(wǎng)絡(luò)序列
local.sin_addr.s_addr = INADDR_ANY; //讓udp_server在啟動時候可以綁定任何ip
//綁定
if(bind(_sock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
std::cerr << " bind error" << std::endl;
exit(-1);
}
std::cout << "bind socket success: " << _sock << std::endl;
}
void Start() //執(zhí)行邏輯
{
char buffer[1024];
while(true)
{ //接收數(shù)據(jù)
//ssize_t recvfrom(套接字,緩沖區(qū),緩沖區(qū)大小,flag = 0,client的IP和port,實(shí)際結(jié)構(gòu)體大小);
struct sockaddr_in far; //遠(yuǎn)端定義結(jié)構(gòu)體
socklen_t len = sizeof(far);
int n = recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&far,&len);
if(n > 0) buffer[n] = '\0';
else continue;
std::string clientip = inet_ntoa(far.sin_addr); //ipv4的地址從二進(jìn)制轉(zhuǎn)化為點(diǎn)分十進(jìn)制的函數(shù)
uint16_t clientport = ntohs(far.sin_port); //將網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)化為一個本地主機(jī)字節(jié)序
std::cout<< clientip << "-" << clientport << "#" << buffer << std::endl;
//發(fā)送數(shù)據(jù)
//ssize_t sendto(套接字,發(fā)的數(shù)據(jù),數(shù)據(jù)大小,flag = 0,(struct sockaddr*)&far,sizeof(far));
sendto(_sock,buffer,sizeof(buffer),0,(struct sockaddr*)&far,sizeof(far));
}
}
private:
int _sock; //套接字
uint16_t _port; //端口
};
2.3服務(wù)器端server.cc
在服務(wù)器端的使用中,我們采用智能指針unique_ptr來幫助資源創(chuàng)建以及銷毀,在使用中,我們調(diào)用以上server.hpp中類的初始化與啟動函數(shù)即可.
//輸出格式說明:./udp_server port
static void usage(string proc)//使用手冊
{
std::cout << "Usage:\n\t" << proc << "port\n" <<std::endl;
}
int main(int argc,char* argv[]) //獲取到參數(shù)
{
if(argc != 2) //若輸入?yún)?shù)不是兩個的話,就彈出使用手冊
{
usage(argv[0]);
exit(-1);
}
uint16_t port = atoi(argv[1]); //獲取到端口直接進(jìn)行構(gòu)造后面
std::unique_ptr<UdpServer> ptr(new UdpServer(port));
ptr->InitServer();
ptr->Start();
return 0;
}
2.4客戶端Client.cc
在客戶端中我們首先需要知道主函數(shù)的服務(wù)端的ip和端口,也就是我們需要從輸入的參數(shù)來知道服務(wù)端是誰?之后由用戶輸入消息后發(fā)送給服務(wù)器端并輸出。
// 執(zhí)行格式:./udp_client ip serverport
static void usage(std::string proc) //使用手冊
{
std::cout << "Usage:\n\t" << proc << "port\n" <<std::endl;
}
int main(int argc,char* argv[])
{
if(argc != 3) //如果輸入?yún)?shù)個數(shù)不是3個就彈出使用手冊
{
usage(argv[0]);
exit(-1);
}
//從主函數(shù)獲取到了服務(wù)端的ip和端口
std::string serverip = argv[1];
uint16_t serverport = atoi(argv[2]);
int sock = socket(AF_INET,SOCK_DGRAM,0); //創(chuàng)建套接字
if(sock < 0)
{
std::cerr << "create socket errno" <<std::endl;
exit(-1);
}
//明確server是誰
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
//這里client一定需要綁定bind 不過由os來幫我們做,因?yàn)镺S需要隨機(jī)分配端口,防止沖突
//用戶輸入
while(true)
{
std::string message;
std::cout<< "please Enter# ";
std::cin >> message;
sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
//接收消息
char buffer[1024];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
int n = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
if(n > 0)
{
buffer[n] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
return 0;
}
最后執(zhí)行后我們便可以看出結(jié)果: 說明執(zhí)行成功!文章來源:http://www.zghlxwxcb.cn/news/detail-670218.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-670218.html
到了這里,關(guān)于Linux之套接字UDP實(shí)現(xiàn)網(wǎng)絡(luò)通信的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!