【LINUX網絡】網絡socet接口的基本使用以及實現簡易UDP通信

根據本系列上兩篇關于網絡的初識介紹,現在我們開始實現一個UDP接口,以加強對該接口的理解。

1 . 服務器端?

在本篇中,主要按照下面內容來實現:

創建并封裝服務端:了解創建服務端的基本步驟
創建并封裝客戶端,測試客戶端和服務端通信:了解創建客戶端的基本步驟和二者通信
測試云服務器與本地進行通信:從本地通信到真正實現網絡通信
根據上面的內容,本次設計的服務器功能就是接受客戶端發送的信息并向客戶端返回服務端收到的信息

老規矩,先設計整體的makefile,目的是生成兩個可執行文件。

CC=g++
LDFLAGE=-o
FLAGE=-std=c++17 -lpthreadServer_src=UDP_Server.cc
Client_src=UDP_Client.cc# SRC=UDP_Client.cc UDP_Server.cc
# OBJ=$(SRC:.cc=.o)Server=UDP_Server
Client=UDP_Client.PHONY:all
all:$(Server) $(Client)$(Server):$(Server_src)@$(CC) $^ $(LDFLAGE) $@ $(FLAGE)$(Client):$(Client_src)@$(CC) $^ $(LDFLAGE) $@ $(FLAGE)@echo "compilation success".PHONY:clean
clean:@rm -rf $(Server) $(Client)@echo "clean done"

?然后搭建Server端的框架:

服務器端首先肯定需要被初始化,然后再永不停息的start起來(預判里面可能會使用while(1)的死循環)

1.1 socket sockaddr?

什么是socket套接字:

  • 套接字是通信的端點,它是一種用于在網絡上進程間通信的機制。可以把它想象成一個管道或者接口,應用程序通過這個管道可以發送和接收網絡數據。

  • 套接字是操作系統提供的一種抽象概念,它屏蔽了底層網絡通信的復雜細節,使得程序員可以方便地進行網絡編程。例如,當我們在瀏覽器中訪問一個網頁時,瀏覽器和網頁服務器之間就通過套接字進行數據傳輸,包括請求網頁內容和返回網頁數據等操作。

現在就要正式開始對服務器的相關信息、接口進行設置。首先需要創建socket文件描述,可以使用socket接口,該接口原型如下:

int socket(int domain, int type, int protocol);

首先來學習socket接口及其參數,先把兩個頭文件加上去。

????????????????

? ? ? ? 第一個參數表示這個socket套接字想選擇的域或者協議家族(domain),domain(域) 是套接字的一個重要屬性,它指定了套接字所使用的協議族(protocol family)。協議族決定了套接字能夠與哪些類型的網絡地址進行通信(不同類型的網絡地址遵循不同的協議,以下是常用的協議族:

AF_UNIX AF_LOCAL都是用于本地通信的協議族,AF_INET AF_INET是適用于網絡的協議族,前者適應的是IPv4,后者是IPv6。

第二個參數type:

比如常見的,TCP是面向字節流的傳輸方式,type選SOCK_STREAM ; UDP是數據包傳送方式,選擇SOCK_DGRAM。可以說,我們今天是在實現UDP,所以先認識了UDP的特性——事實上,是SOCK_DGRAM的特性決定了UDP的特性

根據Linux手冊描述:

  1. SOCK_STREAM:Provides sequenced, reliable, two-way, connection-based byte streams(提供序列化的、可靠的、雙工的、面向有連接的字節流)

  2. SOCK_DGRAM:Supports datagrams (connectionless, unreliable messages of a fixed maximum length)(支持數據包,即無連接、不可靠的固定長度信息)
    ?

通信模式可分為:

  1. 全雙工:雙方同時收發,如同電話通話。

  2. 半雙工:雙方均可收發,但同一時間只能一方傳輸,類似對講機。

  3. 單工:數據單向傳輸,如電視臺廣播。

第三個參數

第三個參數表示指定采用的具體協議。通常傳入0表示讓系統自動選擇適合domaintype參數的默認協議

返回值

該接口返回值為一個新套接字的文件描述符(LINUX下一切皆文件,套接字也是一個文件),否則返回-1并設置錯誤碼

理解socket和sockaddr的關系:

? ? ? ? 初始化一個socket后,我們已經有了這樣一個endpoint for communication(通信端點),但是需要把這個用文件描述符描述的通信端點 綁定 到一個具體的網絡地址(網絡上的一個地址,包括IP地址和端口號等信息。,所以我們還要綁定現在這個socket到我們具體的sockaddr上去。

? ? ? ? 地址多種多樣,可能是AF_INET的地址,也可能是AF_UNIX的地址。

本來AF_INET是用socketaddr_in標記,AF_UNIX用socketaddr_un標記,但是為了實用性把他們兩個給繼承到了一個統一的socketaddr里,所以就變成了socketaddr來標記所有的套接字,在使用的時候通過強轉來找到對應的結構。

在邏輯上再次理解domain域和type?

由AF_INET+SOCK_DGRAM形成的UDP?

由AF_UNIX+TCP形成的本地通信。?

?????????

?代碼實現:

 void InitServer(){ENABLE_FILE_LOG;//日志文件使能//1.創建套接字int sock_fd = ::socket(AF_INET,SOCK_DGRAM,0);if(sock_fd<0){Die(1);LOG(LogLevel::FATAL)<<"socket: "<<strerror(errno);}//創建成功,看看套接字LOG(LogLevel::INFO)<<"socket success , socket fd is :"<<sock_fd;//2.填充網絡信息并bind}

關于LOG:【LINUX操作系統】日志系統——自己實現一個簡易的日志系統-CSDN博客

可以轉到socket參數的宏中去看一下:

????????????????

????????

1.2 關于bind

該接口的第一個參數表示需要綁定的套接字對應的文件描述符,第二個參數表示套接字結構,第三個參數表示套接字結構的大小

????????如果綁定成功,該接口返回0,否則返回-1并設置錯誤碼

????????對于第一個參數來說,就是希望被綁定的套接字;第三個參數表示傳入的第二個sockaddr的長度,因為第二個參數一般情況下存在強轉,需要知道具體這個信息標簽有多長。

下面就第二個參數詳細介紹:

????????在[Socket編程基礎]部分提到sockaddr可以理解為sockaddr_in結構和sockaddr_un的父類,而因為本次創建的是網絡通信,所以要使用的結構就是sockaddr_in(in后綴表示inet,un后綴表示unix),既然參數部分是sockaddr結構而不是sockaddr_in,那么在傳遞實參時就需要進行強制類型轉換。

簡單實現一個強轉的宏

#define CONV(addr) (const sockaddr*)(addr) 

現在,我們已經在自己的函數棧上創建好了套接字,也完成了綁定。唯獨就是in_addr的信息還沒有綁定。

????????那么,既然需要用戶傳遞sockaddr_in結構,那么這個結構中就存在一些屬性需要用戶去設置————地址類型、端口號、IP地址共三種字段需要我們去設置。

????????????????????????????????????????????????

注意,此時我們還在“自己和自己玩”,還和其他機器通信毫無關系。sockaddr的這個“派生類”描述的都是自己這個套接字的信息!填充位留在那里就好了

觀察一下sockaddr_in(紅字中的派生類)的結構,知道結構才能賦值。

其中關于sin(sock inet)有一個宏,結構如下:

????????

傳進去的是sin,##表示將兩邊的符號相結合,所以第一個宏定義的參數其實是sin_family

,表示協議簇。也就是我們說的domain。

明明socket的時候都填了一次,怎么還要填一次?

創建套接字的時候域或者協議簇選擇一次(告訴操作系統套接字類型),sockaddr_in內部再賦值一次,兩個要一樣才能讓操作系統綁定上!

最后,關于第三個結構體sin_addr

????????????????????????

C語言不支持結構體直接整體賦值,因此我們需要內部數據一個一個賦值,不過此處的in_addr只有一個數據,就很方便直接賦值。

作為服務器,自己的端口號和IP地址肯定是會被知道的:

?

注意,socketaddr_in還需要引入頭文件<netinet/in.h> <arpa/inet.h>,結合前面兩個,構成網絡四大頭文件? ? ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

可以用指令man inet_addr查到。

不過可不能簡單把_port寫進去,還需要滿足網絡字節序

????????????????????????

????????IP用十進制點分法的string表示只是方便觀察,但內核存儲不應該是4字節?所以肯定要想辦法改變。既要從字符串變成一個uint32_t的數據,還要符合網絡字節序列,只使用一個簡單的htonl(host to net long)肯定是不現實的。

????????新的接口 inet_addr:直接將一個const char*的東西返回成一個符合網絡字節序和規則的IP地址。

?inet_addr_t是in_addr的返回值,也就是IP在網絡字節序的存在形式,也是

也是對應結構體的數據類型

??????????????????????????????????????

? ??

現在只需要我們把初始的port和IP設置進來即可。?

? ? ??

127.0.0.1 是一個特殊的 IPv4 地址,稱為 回環地址(Loopback Address)。它用于標識本地計算機本身:

  • 127.0.0.1 用于在本地計算機上進行網絡通信,而無需通過外部網絡。

(相當于這個IP發出去時:自己的網卡放出去然后不進入網絡,直接又自己的網卡接受信息)

  • 它常用于測試和調試網絡應用程序,確保程序能夠在本地環境中正常運行。

選擇端口號8080,這是一個不會被使用的端口(0-1023都是綁定好的協議端口號)

為什么sockaddr需要有端口號和IP?

????????因為報文是需要返回的,返回的時候需要知道是哪個socket發出來的(寄包裹總得知道是誰寄出去的)

? ? ? ? 所以報頭必須有原IP和原端口號,服務器一定會把這兩個信息也推送給對方。

現在終于完成了bind。bind之后,這個套接字才算設置進內核中

最后,在進行填數據之前,因為有各種占位的原因,建議先把這個sockaddr_in清空。

?


1.3 Start

? ? ? ? ? ? ? ? ? ?

Start的整體思路:一個isrunning的狀態標記是否啟動,然后需要在一個while(true)下面,不停的通過recvfrom接口和sendto接口以收發消息(還需要一個緩沖數組首發傳的消息)。(這是一個全雙工的接口,支持又讀又寫)

???????????????????????

在while true里進行這個接受。

1.4 recvfrom 與 sendto

recvfromsendto 是兩個用于網絡編程的系統調用,它們是針對數據報套接字(如使用 SOCK_DGRAM 類型的套接字)進行通信的接口。

src_addr addrlen都是輸出型參數,recvfrom作為接受方,需要知道誰來連接了我們這個socket(收到包裹的時候,都會有一個快遞單號,上面寫了誰寄出來的)。flags設置為0表示是阻塞式接受,所以如果我們設置為0,進程會卡在原地等待此次網絡通信。

sendto:

兩者的返回值都是成功接受/發送的長度

想從服務器回消息到主機去,就需要一個sendto接口回消息。

  void Start(){_isrunning = true;while(true){char inbuffer[SIZE];sockaddr_in* peer;socklen_t peer_len = sizeof(peer);int n =  recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(peer),&peer_len);if(n>0){std::string echo = "#echo: ";LOG(LogLevel::DEBUG)<<inbuffer;echo += inbuffer;int n = sendto(_sockfd,echo.c_str(),echo.length(),0,CONV(peer),peer_len);}}_isrunning = false;}

順便修正一下之前的一個小bug:之前是直接使用的指針,沒有開辟空間。

?下次別這么玩了。直接sockaddr_in一個該多好,整的又是段錯誤又是綁定失敗的..........

補充:可以說,recvfrom和sendto的最后兩個參數都是去描述通信的另一端的,前面的sock_fd都是描述自己這一端的?

驗證實驗成果:?

結合指令:netstat -unap?

netstat 是一個非常有用的網絡工具,用于顯示網絡連接、路由表、接口統計信息、偽裝連接以及多播成員信息等。


2. 客戶端

????????服務器是被別人聯的,所以剛剛的而客戶端的peer根本不需要知道是誰,直接當作輸出型參數即可。而客戶端必須知道服務器的IP和端口,客戶端需要主動給服務器發數據,去申請內容。

這樣的模式稱之為CS(Client Server )模式:

????????CS模式是一種基本的網絡通信模型,它定義了客戶端和服務器之間的通信關系。這種模式在現代網絡應用中非常普遍,因為它提供了一種可靠、可擴展和安全的方式來組織網絡服務。通過CS模式,客戶端可以方便地訪問服務器提供的服務,而服務器則可以集中管理和控制服務的提供。

此處我們不在使用.hpp去封裝他,直接寫到main函數里:

首先,客戶端必須要拿到具體的服務器IP和服務器端口號(實際中可能這兩個東西是封裝在客戶端內部的)

發現此時還是要封裝Die等宏,所以索性都引進到一個Commn頭文件里。

之前的sockaddr_in中的結構體也做過類似思路的行為:

????????

按照之前的邏輯,我們完成如下:

int main(int argc,char* argv[])
{   ENABLE_CONSOLE_LOG;if(argc!=3){LOG(LogLevel::ERROR)<<"Usage "<<argv[0]<<": 127.0.0.1 8080";Die(1);}//0.獲取服務器的套接字信息std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);//1.創建套接字int sock_fd = socket(AF_INET,SOCK_DGRAM,0);if(sock_fd<0){LOG(LogLevel::FATAL)<<"socket fail";Die(2);}//2.填寫server信息//因為后續要在sendto中直接給server通信sockaddr_in server_socket;server_socket.sin_family = AF_INET;server_socket.sin_port = ::htons(server_port);server_socket.sin_addr.s_addr = ::inet_addr(server_ip.c_str());// //3.進行bind,并且設置進內核// int ret = bind(sock_fd,CONV(&server_socket),sizeof(server_socket));while(true){std::cout<<"Please Enter"<<std::endl;std::string Message;std::getline(std::cin,Message);int n =  ::sendto(sock_fd,Message.c_str(),Message.length()-1,0,CONV(&server_socket),sizeof(server_socket));}}

注意,我們注釋掉了bind那一步:

客戶端不需要bind

????????實際上,客戶端并不需要綁定IP地址和端口。

注意,不需要不綁定表示:不需要程序員手動綁定,而是由OS自動綁定

理由如下:

比如你的手機,同時有淘寶客戶端,LOL客戶端,美團客戶端,如果三個公司的工程師A\B\C描述自己的sockaddr的時候,都把同一個端口號描述成自己客戶端的端口號,就會出現矛盾。

????????如果客戶端由程序員綁定,那么假設有兩個公司上線的客戶端使用的端口是一樣的,就會出現一個軟件先打開之后可以正常收到服務器發送的數據,但是另外一個軟件的服務器就無法正確發送信息到對應的軟件上即一個端口只能對應一個進程,但是一個進程可以有多個端口。

????????那么,客戶端難道不需要端口嗎?并不是,如果客戶端沒有端口,那么服務器只能通過IP地址找到具體客戶端設備,但是找不到對應的進程,既然如此,客戶端的端口怎么確定?實際上這個端口由操作系統自行隨機分配。所以,端口號會在第一次sendto之后自動綁定。

?那么服務器端又為什么需要程序員手動綁定端口號??

???????因為服務器端口號如果是隨機的,而軟件中請求服務器的端口號是固定的,那么一個軟件可能在某一天可以正常收到服務器發送的數據,但是下一次因為服務器端口號是變化的,就無法正常收到信息。也就是說,端口號高概率會內置在客戶端中,如果服務器一直變化,就很難通信。

綜上所述,服務器端需要程序員手動綁定IP地址和端口號,而客戶端不需要程序員手動綁定IP地址和端口號,由操作系統自行分配并綁定啟動

? ? ? ? ?服務器端口號不僅不改變,還要盡量做到“眾所周知”,方便大家來連接。

一般在公司中,一個準備上線的項目都需要去公司后臺申請,申請到了對應的端口才能使用。

????????所以在這段代碼中,sock_fd描述的是發送方的動作,server_socket是希望接受發送動作的信息的地址。

? ? ? ? ?因此,兩個東西本來含義就不匹配,在含以上肯定不能bind。結合上面的內容我們可以知道,這個sock_fd最終被OS自動綁定。

最終調試可運行代碼效果:

?

?第一版本到此結束,源碼在這里:

EchoServer_V1 · lsnmjp/code of cpp Linux 算法 - 碼云 - 開源中國

現在能本地通信了,從此我們似乎以及不再需要之前的SYSTEM V體系了.......

準備聯入網絡。


3. demo代碼優化

3.1 從sockaddr_in中取數據

作為服務器,我們希望在傳輸出型變量peer出去之后將peer利用起來。

所以我們學習使用接口:

?????????????????

其中ntohs肯定可以用來獲得port

其次,想要將IP轉化為char*方便我們看,所以有:inet_ntoa

所以:

int n =  recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,CONV(&peer),&peer_len);//此時peer已經獲得了對應的數據
uint16_t client_port = ::ntohs(peer.sin_port); //網絡字節序列轉主機序列
std::string client_IP = ::inet_ntoa(peer.sin_addr);//轉換到主機序列;轉換成字符串

再在服務器echo的時候包裝一下:

57817就是我們的客戶端自動被OS綁定的端口號!


3.2 是否需要IP地址

修改服務器端的綁定網絡:

剛剛一直使用的是127.0.0.1的本地網卡,現在試試手動輸入一個——如果以機器的公網IP為例輸入會發現:

之所以出現這個問題,是因為云服務器的公網IP地址是不允許用戶自行綁定

解決這個問題之前,我們思考一下,啟動一個服務器真的必須要綁定IP嗎?

?

????????一臺服務器可能有多個IP地址,此時如果服務器固定IP地址,那么此時就會出現服務器只能接收傳送到固定IP地址的信息,就算服務器有很多IP地址也只有一個IP地址可以使用,很明顯這個效果并不符合UDP協議的特點,因為UDP協議是面向無連接的,既然都不需要連接,為什么還需要指定IP地址,所以啟動服務器不需要指定IP地址。

????????????????????????

宏INADDR_ANY表示可以接受該機器所擁有的所有IP。注意,并不是通過INADDR_ANY去找機器,找機器的時候還是通過客戶端所內置的IP地址。但是在找到之后,在服務器的視角,只要是屬于自己的IP,都照單全收。

并且在構造函數和private下面可以不再需要IP變量。

???????????????????????

此時的狀態是,server不需輸入IP,但是client依然靠已知的IP去找server。

當下代碼,已經可以在多個LInux上跑了,也就是可以讓多個Linux機器之間互動。

甚至也可以在Linux和win下通過網絡通信(了解):

win的內核層一模一樣:

3.3 字節序列以及設置地址封裝

為了便于之后的sockaddr_in地址和我們希望看到的string ip與uint16_t port更直觀,我們進行以下封裝:

#pragma once#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>namespace Inet_Addr
{class InetAddr{private:void Host2Net()//如果先拿到主機,我們希望轉向網絡{_net_addr.sin_family = AF_INET;_net_addr.sin_port = ::htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY;}//如果先拿到網絡,我們希望將地址轉向主機void PortNet2Host(){_port = ::ntohs(_net_addr.sin_port);}void IPNet2Host(){_ip = }public:InetAddr(){}InetAddr(const struct sockaddr_in& net_addr):_net_addr(net_addr)//如果先拿到網絡,我們希望將地址轉向主機{PortNet2Host();IPNet2Host();}InetAddr(uint16_t port):_port(port)//如果先拿到主機,我們希望將地址轉向網絡{Host2Net();}private:struct sockaddr_in _net_addr; // 網絡字節標準uint16_t _port;               // 主機標準std::string _ip;              // 主機標準};
}

此處的ip本可以用之前學習的inet_ntoa轉換。

但其實,作為一個返回char*的C語言函數并不安全。指針會在inet_ntoa函數內部維護一段靜態空間,在多線程情況,這個空間可能被覆蓋。

3.3.1 線程安全接口

我們采用一種更加線程安全的inet_ntop方法:

  void IPNet2Host(){char buffer[64];_ip = ::inet_ntop(AF_INET,&_net_addr.sin_addr,buffer,sizeof(_net_addr));}

會把轉換好的數據先放在buffer里,然后再賦值給_ip。

因為buffer是在線程棧是創造的,所以不會矛盾。


繼續封裝:?

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Commn.hpp"namespace Inet_Addr
{class InetAddr{private:void Host2Net()//如果先拿到主機,我們希望轉向網絡{_net_addr.sin_family = AF_INET;_net_addr.sin_port = ::htons(_port);_net_addr.sin_addr.s_addr = INADDR_ANY;}//如果先拿到網絡,我們希望將地址轉向主機void PortNet2Host(){_port = ::ntohs(_net_addr.sin_port);}void IPNet2Host(){char buffer[64];_ip = ::inet_ntop(AF_INET,&_net_addr.sin_addr,buffer,sizeof(_net_addr));}public:InetAddr(){}InetAddr(const struct sockaddr_in& net_addr):_net_addr(net_addr)//如果先拿到網絡,我們希望將地址轉向主機{PortNet2Host();IPNet2Host();}InetAddr(uint16_t port):_port(port)//如果先拿到主機,我們希望將地址轉向網絡{Host2Net();}uint16_t GetPort(){return _port;}std::string& GetIP(){return _ip;}struct sockaddr* NetAddr(){return CONV(&_net_addr);}socklen_t NetAddrLen(){return sizeof(_net_addr);}private:struct sockaddr_in _net_addr; // 網絡字節標準uint16_t _port;               // 主機標準std::string _ip;              // 主機標準};
}

到客戶端和服務器中去封裝:

????????

改動后的完整代碼:

#ifndef UDP_SERVER__HPP
#define UDP_SERVER__HPP#include <iostream>
#include <memory>
#include <string>
#include <string.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Commn.hpp"using namespace LogModule;
using namespace Inet_Addr;// global
int gsockfd = -1;
const static std::string gdefaultIP = "127.0.0.1";
const static uint16_t gdefaultport = 8080;class UdpServer
{
public:// UdpServer( const std::string& IP = gdefaultIP,uint16_t port = gdefaultport)UdpServer(uint16_t port = gdefaultport): _sockfd(gsockfd), _inetaddr(port)//,_port(port)//,_IP(IP){}void InitServer(){// ENABLE_FILE_LOG;//日志文件使能ENABLE_CONSOLE_LOG;// 1.創建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){Die(1);LOG(LogLevel::FATAL) << "socket: " << strerror(errno);}// 創建成功,看看套接字LOG(LogLevel::INFO) << "socket success , socket fd is :" << _sockfd;// 2.填充網絡信息并bind : 設置進了內核中//  struct sockaddr_in* in_addr_;//  in_addr_ = new sockaddr_in();// 2.1 填充in_addr的信息//  bzero(in_addr_, sizeof(sockaddr_in));//清理//  in_addr_->sin_family = AF_INET;//  in_addr_->sin_port = ::htons(_port);//端口號//  //in_addr_->sin_addr.s_addr = ::inet_addr(_IP.c_str());//1. string ip->4bytes 2. network order//  in_addr_->sin_addr.s_addr = INADDR_ANY;// bindint n = bind(_sockfd, CONV(_inetaddr.NetAddr()), _inetaddr.NetAddrLen());if (n < 0){LOG(LogLevel::ERROR) << "bind fail";exit(1);}LOG(LogLevel::INFO) << "bind success";}void Start(){_isrunning = true;while (true){char inbuffer[SIZE];sockaddr_in peer;socklen_t peer_len = sizeof(peer);int n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, CONV(&peer), &peer_len);// // 此時peer已經獲得了對應的數據// uint16_t client_port = ::ntohs(peer.sin_port);      // 網絡字節序列轉主機序列// std::string client_IP = ::inet_ntoa(peer.sin_addr); // 轉換到主機序列;轉換成字符串if (n > 0){InetAddr client(peer);inbuffer[n] = 0;LOG(LogLevel::DEBUG) << "Client says@ " << inbuffer;std::string echo = "#echo: ";std::string backinfo = client.GetIP() + " " + std::to_string(client.GetPort()) + echo;// std::string client_info = client_IP;// client_info += " : ";// client_info += std::to_string(client_port);// client_info += '  ';// echo += client_info;backinfo += inbuffer;int ret = sendto(_sockfd, backinfo.c_str(), backinfo.length(), 0, CONV(client.NetAddr()), client.NetAddrLen());// if(ret>0)// {//     LOG(LogLevel::DEBUG)<<"server sendto success";// }// else// {//     LOG(LogLevel::DEBUG)<<"server sendto fail";// }}}_isrunning = false;}~UdpServer(){if (_sockfd != gsockfd)close(_sockfd);}private:int _sockfd; // 訪問套接字的文件描述符InetAddr _inetaddr;// uint16_t _port; //端口號// std::string _IP;//十進制點分IP地址bool _isrunning = false;
};#endif

全部代碼:

EchoServer_V2 · lsnmjp/code of cpp Linux 算法 - 碼云 - 開源中國

白框上面是xshell打開的client,白框下面是vscode打開的server端。

4. 下集預告?

下一集我們將利用上述思維和代碼進行一些簡單的業務,比如字典、聊天室等

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/910423.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/910423.shtml
英文地址,請注明出處:http://en.pswp.cn/news/910423.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MySQL的索引事務

索引 是什么 類似于目錄&#xff0c;提高查詢的速度&#xff0c;但是本身會占用空間&#xff0c;增刪數據的時候也需要維護索引。所以查詢操作頻繁的時候可以創建索引。如果非條件查詢列&#xff0c;或經常做插入、修改操作&#xff0c;或磁盤空間不足時&#xff0c;不考慮創…

安卓9.0系統修改定制化____第三方美化 bug修復 移植相關 輔助工具 常識篇 八

在修改rom中。有時候不可避免的需要對系統進行美化以及一些第三方系統的bug修復。在操作前需要了解系統的一些基本常識。例如同平臺移植 跨平臺移植以及內核移植 apk反編譯等等相關的知識。今天解析的這款工具雖然不是直接面向安卓9.0.但對于了解以上的一些必備常識還是不錯的 …

云服務器與物理服務器對比:選擇最適合的業務服務器解決方案

更多云服務器知識&#xff0c;盡在hostol.com 在現代 IT 基礎設施中&#xff0c;云服務器與物理服務器是兩種常見的服務器解決方案。隨著云計算技術的迅猛發展&#xff0c;越來越多的企業開始轉向云服務器&#xff0c;但也有一些企業仍然堅持使用物理服務器&#xff0c;尤其是…

【redis使用場景——緩存——雙寫一致性】

redis使用場景——緩存——雙寫一致性 雙寫一致性問題的本質與場景典型不一致場景分析??并發寫操作導致的不一致????讀寫交叉導致的不一致????主從同步延遲導致的不一致?? 解決延遲雙刪策略&#xff08;推薦&#xff09;優點??&#xff1a;??缺點??&#xff…

【ArcGIS】在線影像底圖調用

【ArcGIS】在線影像底圖調用 一、 歷史影像的調用二、ArcGIS online底圖調用三、結語 一、 歷史影像的調用 ESRI官方推出了World Imagery Wayback是一個提供全球范圍內歷史影像的在線服務。 官網地址&#xff1a;https://livingatlas.arcgis.com/wayback/ 操作步驟&#xff1…

密度估計:從零星足跡重建整體畫像

想象你是一位偵探&#xff0c;案發現場只留下幾個零散的腳印。**如何通過這些碎片&#xff0c;推斷嫌疑人的身高體重&#xff1f;甚至預測他下一步的藏身之處&#xff1f;** 這種從局部反推整體的能力&#xff0c;正是**密度估計&#xff08;Density Estimation&#xff09;** …

B004基于STM32F401單片機簡易交通燈實訓數碼管顯示設計仿真資料

視頻演示地址:https://www.bilibili.com/video/BV1GvNDzFEd9/ 運行環境 仿真軟件:proteus8.17(切記別的版本不能運行) 編程軟件:MDK525 STM32 cubmx版本:6.11.1(切記別的版本不能運行) 原理圖畫圖軟件:AD10 功能說明&#xff1a; 以STM32F401CB單片機為核心簡易交通燈功能如下。…

沒掌握的知識點記錄

1、微內核的主要優點在于結構清晰、內核代碼量少&#xff0c;安全性和可靠性高、可移植性強、可伸縮性、可擴展性高&#xff1b;其缺點是難以進行良好的整體優化、進程間互相通信的開銷大、內核功能代碼不能被直接調用而帶來服務的效率低。 2、題目&#xff1a; 分頁內存管理…

linux 遠程終端執行qt應用顯示到接入的物理顯示器上

在顯示器打開終端執行&#xff1a; xhost local: 在遠程終端執行&#xff1a; export DISPLAY:0然后在終端執行qt應用就可以。 xhost local: 功能&#xff1a;允許本地用戶&#xff08;local:&#xff09;訪問 X 服務器&#xff08;X11 圖形系統&#xff09;。 原理&#xf…

【AI驅動網絡】

一、AI 驅動網絡 1.1 什么是網絡 1.1.1、網絡的定義 ?網絡是由若干節點?(如計算機、服務器、移動設備等)和連接這些節點的鏈路?(有線或無線傳輸介質)構成的系統,用于實現地理位置分散的獨立設備之間的信息交換、資源共享與協同工作。在計算機領域,網絡是信息傳輸、…

Python期末速成

一.基礎內容 賦值語句&#xff1a; a 1 b "mayday" 標識符規則&#xff1a; 1.字母&#xff0c;數字&#xff0c;下劃線&#xff0c;漢字組成。但數字不能開頭 2.不能是保留字 3.特殊符號不行&#xff0c;*&#xffe5;^等 注釋是在語句前面加&#xff03; …

【時時三省】(C語言基礎)指針變量例子

山不在高&#xff0c;有仙則名。水不在深&#xff0c;有龍則靈。 ----CSDN 時時三省 存放地址的變量是指針變量&#xff0c;它用來指向另一個對象&#xff08;如變量、數組、函數等&#xff09;。 那么&#xff0c;怎樣定義和使用指針變量呢? 先分析一個例子。 例題 通過…

MATLAB代碼演示,TDOA定位的優化算法,提升Z軸的定位精度|復現《基于最小二乘法的室內三維定位算法研究》

本文復現文章: 王桂杰,焦良葆,曹雪虹.基于最小二乘法的室內三維定位算法研究[J].計算機技術與發展,2020,30(04):69-73.按照文章的核心算法,復現了TDOA下的最小二乘在三維環境中的改進定位方法,方法可以明顯提升Z軸的定位精度 文章目錄 概述運行結果展示matlab代碼完整代碼概…

React useState 原理

Fiber架構 React16 之后 提升顯示性能 電腦屏幕參數刷新率 表示1s刷新次數 頁面渲染 和 JS代碼執行 共享一個線程 互斥 保持上一幀圖像表現&#xff1a;卡頓 reconcilier改為 stack 和 fiber Fiber數據結構 執行單元 瀏覽器優先執行用戶響應相關或者界面渲染相關事件&#…

【Datawhale組隊學習202506】零基礎學爬蟲 01 初始爬蟲

系列文章目錄 01 初始爬蟲 02 數據解析與提取 文章目錄 系列文章目錄前言1 爬蟲和Python2 爬蟲的矛盾2.1 爬蟲與反爬2.2 robots核心字段重要規則說明非標準擴展指令協議生效條件局限性驗證工具 2.3 一個爬蟲demo 3 Web請求與HTTP協議3.1 一個web請求的全過程3.2 判斷頁面源代碼…

前端面試十之vuex

Vuex 是一個專為 Vue.js 應用程序設計的狀態管理模式和庫&#xff0c;它集中管理應用的所有組件的狀態&#xff0c;并以相應的規則保證狀態以一種可預測的方式發生變化。以下是關于 Vuex 的詳細介紹&#xff1a; 1. 核心概念 State&#xff08;狀態&#xff09; 它是 Vuex 中存…

Django中為api自定義一些裝飾器:如參數校驗等

在Django中使用了rest_framework時&#xff0c;一般我們會定義ModelSerializer來校驗request.data中參數是否存在和參數類型。 但當我們只是想簡單校驗一些api的url上是否存在某些參數時&#xff0c;該怎么辦&#xff1f;當然我們也可以通過定義Serializer來實現&#xff0c;但…

uni-app項目實戰筆記21--uniapp緩存的寫入和讀取

一、緩存的寫入 uni.setStorageSync("storageClassList",classifyList.value) 二、緩存的讀取&#xff0c;如果緩存不存在&#xff0c;則返回空數組 const storageClassList uni.getStorageSync("storageClassList") || []; 三、對讀取到的數據進行轉…

Zama密碼分析資助計劃

1. 引言 2025年5月&#xff0c;Zama團隊正式啟動了 Zama 密碼分析資助計劃&#xff08;Cryptanalysis Grant Program&#xff09;&#xff0c;以支持那些致力于“破解”系統的研究人員&#xff1a; 無論是通過密碼分析、側信道攻擊、故障注入&#xff0c;還是其他創新性方法。…

【數據結構與算法】數據結構初階:詳解順序表和鏈表(一)

&#x1f525;個人主頁&#xff1a;艾莉絲努力練劍 ?專欄傳送門&#xff1a;《C語言》、《數據結構與算法》 &#x1f349;學習方向&#xff1a;C/C方向 ??人生格言&#xff1a;為天地立心&#xff0c;為生民立命&#xff0c;為往圣繼絕學&#xff0c;為萬世開太平 前言&am…