先來說說數(shù)據(jù)在網(wǎng)絡(luò)上的傳輸過程吧,我們知道系統(tǒng)其實終究是根據(jù)馮諾依曼來構(gòu)成的,而網(wǎng)絡(luò)數(shù)據(jù)是怎么發(fā)的呢?
其實很簡單,網(wǎng)絡(luò)有五層。如下:
如上圖,我們知道的是,每層對應(yīng)的操作系統(tǒng)中的那些地方,有些可能說是網(wǎng)絡(luò)有七層,其實和這個五層一樣的。下面我們說說數(shù)據(jù)是怎么運輸?shù)脑诰W(wǎng)絡(luò)中,如下圖:
如上圖,其實數(shù)據(jù)在網(wǎng)絡(luò)中是自頂向下,然后在通過以太網(wǎng)的網(wǎng)線傳輸?shù)搅硪粋€主機上,在自底向上,就可以收到了,前提是在同一個局域網(wǎng)中,如果不在一個局域網(wǎng),肯定會經(jīng)過路由器的,這里就不詳細說了,主要說說我們的udp協(xié)議。
我們知道了網(wǎng)絡(luò)的五層,那么每層其實都與對應(yīng)的協(xié)議等。udp協(xié)議對應(yīng)在傳輸層(運輸層)。那么我們來看看如何用udp協(xié)議實現(xiàn)套接字編程吧。先來看看代碼:
#include <iostream>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <unordered_set>
#include <unordered_map>
using namespace std;
#define NUM 1024
int main(int argc, char *argv[])
{
unordered_map<uint16_t,sockaddr_in> usdate;
// 創(chuàng)建套接字
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
cout << "sock enrro" << endl;
exit(1);
}
// 綁定
sockaddr_in se;
memset(&se, 0, sizeof(se));
se.sin_family = AF_INET;
se.sin_port = htons(atoi(argv[2]));
se.sin_addr.s_addr = inet_addr(argv[1]);
int ret = bind(sock, (sockaddr *)&se, sizeof(se));
if (ret < 0)
{
cout << "bind enrro" << endl;
exit(2);
}
// 服務(wù)端 1.0版本
// 可以開始讀取
// sockaddr_in reader;
// socklen_t size = sizeof(reader);
// char buffer[NUM];
// while (true)
// {
// ssize_t r = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&reader, &size);
// buffer[r] = '\0';
// if (r > 0)
// {
// cout << buffer << endl;
// }
// else
// break;
// sendto(sock, buffer, sizeof(buffer), 0, (sockaddr *)&reader, size);
// memset(buffer, 0, sizeof(buffer));
// }
// close(sock);
// 服務(wù)器 2.0版本 實現(xiàn)群聊
char buffer[NUM];
memset(buffer, 0, NUM);
sockaddr_in reader;
socklen_t size = sizeof(reader);
while (true)
{
ssize_t s = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&reader, &size);
usdate.insert(make_pair(reader.sin_port, reader));
cout << "插入成功" << endl;
cout << ntohs(reader.sin_port) <<" "<< inet_ntoa(reader.sin_addr)<< '#' << " "
<< ":" << buffer << endl;
if (s > 0)
{
for (const auto &e : usdate)
sendto(sock, buffer, sizeof(buffer), 0, (sockaddr *)&(e.second), sizeof(e.second));
memset(buffer, 0, NUM);
}
else
break;
}
close(sock);
return 0;
}
#include <iostream>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
using namespace std;
#define NUM 1024
void *reads(void *args)
{
// 線程分離
pthread_detach(pthread_self());
char buffer[NUM];
// 清空buffer
memset(buffer, 0, NUM);
int *sc = static_cast<int *>(args);
sockaddr_in reader;
socklen_t len = sizeof(reader);
while (true)
{
ssize_t s = recvfrom(*sc, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&reader, &len);
if (s > 0)
{
cout << buffer << endl;
memset(buffer, 0, NUM);
}
else
break;
}
return nullptr;
}
int main(int argc, char *argv[])
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
cout << "sock enrro" << endl;
exit(1);
}
char buffer[NUM];
memset(buffer, 0, NUM);
pthread_t tid;
pthread_create(&tid, nullptr, reads, &sock);
sockaddr_in clinet;
memset(&clinet, 0, sizeof(clinet));
clinet.sin_family = AF_INET;
clinet.sin_addr.s_addr = inet_addr(argv[1]);
clinet.sin_port = htons(atoi(argv[2]));
while (true)
{
cout << "你要輸入" << endl;
cin >> buffer;
ssize_t s = sendto(sock, buffer, sizeof(buffer), 0, (sockaddr *)&(clinet), sizeof(clinet));
if (s > 0)
memset(buffer, 0, NUM);
else
break;
}
close(sock);
return 0;
}
這是我的服務(wù)端和客戶端的代碼,分了單人聊天和多人聊天。下面就講解一下吧。
什么是端口號:主機中能表示一個唯一的進程的編號
什么是ip地址:其實ip地址是網(wǎng)絡(luò)層對應(yīng)的主機地址。
什么是mac地址:這個網(wǎng)卡的地址,一般出廠的時候就會確定,且不能修改
什么是套接字:IP+端口號
我們首先可以根據(jù)套接字找到網(wǎng)絡(luò)中唯一的一個主機上進程,此處不考慮ip地址重復(fù)問題。假設(shè)ip地址不重復(fù)。所以我們要進行udp套接字編程,首先要創(chuàng)建套接字。也就是我上圖代碼中的socket這個函數(shù),然后綁定地址和端口,這個就可以用我們main函數(shù)中的參數(shù)了。創(chuàng)建完套接字和綁定完成以后,我們就可以通信了,我們用的這些函數(shù)其實就是系統(tǒng)調(diào)用,是udp的一些函數(shù)暴露給用戶層的系統(tǒng)調(diào)用。
大概知道了怎么用udp編程,那么此時有些伙伴可能有些疑問了。我們在系統(tǒng)層面上pid也可以表示進程的唯一性,為啥不用PID表示端口號呢?其實也很簡單,原因就是網(wǎng)絡(luò)層是這么表示的,就好比你的名字一樣,在外面大家都叫你名字,回到家家里面的人都叫你小名,一樣的道理。
然后就是有些人可能沒有理解數(shù)據(jù)是怎么從下往上,從上往下的。其實很好理解,因我們在寫代碼的時候,我們是屬于用戶層的。而我們傳輸層的系統(tǒng)調(diào)用接口,那么說明他這個數(shù)據(jù)肯定是要進內(nèi)核的,而我們的另一個主機接收到信息后,我們用的打印函數(shù),又是用戶層的,所以就類似于一個輪回。所以這樣就可以很好的理解了。
然后就是一些編碼的注意事項了,在客戶端我用了多線程來實現(xiàn)了讀數(shù)據(jù)和寫數(shù)據(jù)的解耦,這個其實在單人聊天中沒什么影響,再多人聊天中就不可以了。假設(shè)單人聊天就要先發(fā)在讀。因為再多人聊天中,我們預(yù)期是一個人發(fā),多個人收,如果還是用這個代碼的話,那么 如果開啟服務(wù)端的時候,多個人你同時建立連接,那么此時如果有其中一個人發(fā)了信息,并且假設(shè)其他人都沒有發(fā)信息,那么此時就會導(dǎo)致其他人卡在寫的界面,因為他們沒有寫,所以沒辦法讀信息。所以此時我們這里必須要實現(xiàn)成多線程,一個寫,一個讀,讀寫解耦。兩個互不影響。建議使用線程,不用進程,且不說多進程可不可以實現(xiàn)這個功能,就算是實現(xiàn)了,那么此時它的消耗是很大的。(多進程也可以實現(xiàn)這個功能),如果用多進程,那么此時我們要考慮的是,如何回收這個子進程,肯定不可以阻塞等待,如果用waitpid且不是阻塞等待的話,那么此時我們要寫成循環(huán),要不斷去檢測子進程是否完成任務(wù)?;蚴强梢栽谧舆M程中在frok,讓孫子進程執(zhí)行任務(wù),子進程退出,此時就會形成孤兒進程,會被1號進程領(lǐng)養(yǎng),所以不用擔(dān)心資源泄露,但是這樣很明顯很麻煩,還不如用線程,且消耗還比進程小。文章來源:http://www.zghlxwxcb.cn/news/detail-758711.html
以上就是這篇文章的內(nèi)容,希望大家支持,如果對你有用,希望支持一下!?。?!文章來源地址http://www.zghlxwxcb.cn/news/detail-758711.html
到了這里,關(guān)于UDP網(wǎng)絡(luò)套接字編程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!