計算機網絡:【socket】【UDP】【地址轉換函數】【TCP】

一.socket

1.1socket接口

它返回的是一個文件描述符。創建socket文件描述符(TCP/UDP,客戶端+服務器)

? socket()打開一個網絡通訊端口,如果成功的話,就像 open()一樣返回一個文件描 述符;

? 應用程序可以像讀寫文件一樣用 read/write 在網絡上收發數據;

? 如果 socket()調用出錯則返回-1;

? 對于 IPv4, family 參數指定為 AF_INET;

? 對于 UDP 協議,type 參數指定為 SOCK_DGRAM, 表示面向數據報的傳輸協議

? protocol 參數的介紹從略,指定為 0 即可。?

Socket的第一個參數:domain(域/協議族)

Socket編程中,socket()函數的第一個參數指定通信的協議族(Protocol Family),決定了 socket 的底層通信協議類型。常見的協議族包括:

  • AF_INET
    IPv4協議族,用于基于IPv4的網絡通信(如互聯網或本地網絡)。這是最常用的選項。

    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    

  • AF_INET6
    IPv6協議族,支持IPv6地址格式的通信。

    int socket_fd = socket(AF_INET6, SOCK_STREAM, 0);
    

  • AF_UNIX/AF_LOCAL
    本地通信協議族,用于同一臺主機上的進程間通信(通過文件系統路徑)。

    int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    

  • AF_PACKET
    底層數據包接口(如直接訪問網絡層數據包,常用于網絡工具開發)。


參數意義

該參數定義了 socket 的地址類型,直接影響后續綁定(bind())或連接(connect())時使用的地址結構體。例如:

  • 使用 AF_INET 時,地址結構體為 struct sockaddr_in(包含IPv4地址和端口)。
  • 使用 AF_UNIX 時,地址結構體為 struct sockaddr_un(包含文件路徑)。

注意事項

  • AF_ vs PF_
    歷史上有 AF_(地址族)和 PF_(協議族)兩種前綴,但在現代系統中兩者通常等價(如 AF_INETPF_INET 可互換)。
  • 錯誤處理
    若參數無效(如不支持的協議族),socket() 會返回 -1,并設置 errnoEAFNOSUPPORT

socket 的第二個參數詳解

socket 的第二個參數指定套接字的類型,決定了數據傳輸的方式和協議特性。以下是常見的套接字類型及其用途:

SOCK_STREAM
  • 面向連接的字節流套接字,使用 TCP 協議。
  • 提供可靠、有序、雙向的數據傳輸。
  • 適用于需要數據完整性的場景,如 HTTP、FTP。
SOCK_DGRAM
  • 無連接的數據報套接字,使用 UDP 協議。
  • 傳輸速度快但不可靠,可能丟包或亂序。
  • 適用于實時性要求高的場景,如視頻流、DNS 查詢。
SOCK_RAW
  • 原始套接字,允許直接訪問底層協議(如 IP、ICMP)。
  • 需要管理員權限,常用于網絡探測或自定義協議開發。
SOCK_SEQPACKET
  • 提供有序、可靠、基于消息的傳輸(如 SCTP 協議)。
  • 結合了流式和數據報的特性,適用于電信領域。
SOCK_RDM
  • 可靠的數據報套接字,保證數據不丟失但可能亂序。
  • 較少使用,特定于某些協議(如 RDS)

注意事項

  • 第二個參數需與第一個參數(地址族,如 AF_INET)兼容。
  • 某些類型(如 SOCK_RAW)可能需要特殊權限。
  • 具體支持的類型取決于操作系統和協議棧。

?第三個參數

指定具體的傳輸協議。通常設置為0,表示根據domaintype自動選擇默認協議。

1.2bind接口

?綁定端口號(TCP/UDP,服務器)

? 服務器程序所監聽的網絡地址和端口號通常是固定不變的,客戶端程序得知服 務器程序的地址和端口號后就可以向服務器發起連接; 服務器需要調用 bind 綁定一 個固定的網絡地址和端口號;

? bind()成功返回 0,失敗返回-1。

? bind()的作用是將參數 sockfd 和 myaddr 綁定在一起, 使 sockfd 這個用于網絡 通訊的文件描述符監聽 myaddr 所描述的地址和端口號;

? struct sockaddr *是一個通用指針類型,myaddr 參數實際上可以接受 多種協議的 sockaddr 結構體,而它們的長度各不相同,所以需要第三個參數 addrlen 指定結構體的長度;?

1.3listen接口

開始監聽 socket (TCP, 服務器)

? listen()聲明 sockfd 處于監聽狀態, 并且最多允許有 backlog 個客戶端處于連接 等待狀態, 如果接收到更多的連接請求就忽略, 這里設置不會太大(一般是 5)

? listen()成功返回 0,失敗返回-1;

1.4accept接口

接收請求 (TCP, 服務器)

?

它返回的也是一個文件描述符,跟listensocket配合著使用

? 三次握手完成后, 服務器調用 accept()接受連接;

? 如果服務器調用 accept()時還沒有客戶端的連接請求,就阻塞等待直到有客戶端 連接上來;

? addr 是一個傳出參數,accept()返回時傳出客戶端的地址和端口號;

? 如果給 addr 參數傳 NULL,表示不關心客戶端的地址;

? addrlen 參數是一個傳入傳出參數(value-result argument), 傳入的是調用者提 供的, 緩沖區 addr 的長度以避免緩沖區溢出問題, 傳出的是客戶端地址結構體的實際長度(有可能沒有占滿調用者提供的緩沖區);?

1.5connect接口

建立連接 (TCP, 客戶端)

? 客戶端需要調用 connect()連接服務器;

? connect 和 bind 的參數形式一致, 區別在于 bind 的參數是自己的地址, 而 connect 的參數是對方的地址;

? connect()成功返回 0,出錯返回-1;

?1.6sockeaddr結構

socketAPI 是一層抽象的網絡編程接口 , 適用于各種底層網絡協議 , IPv4 IPv6, 以及后面的UNIXDomainSocket. 然而 , 各種網絡協議的地址格式并不相同 .

? IPv4 和 IPv6 的地址格式定義在 netinet/in.h 中,IPv4 地址用 sockaddr_in 結構 體表示,包括16 地址類型, 16 位端口號和 32 位 IP 地址.

? IPv4、IPv6 地址類型分別定義為常數 AF_INET AF_INET6. 這樣,只要取得某 種 sockaddr 結構體的首地址,不需要知道具體是哪種類型的 sockaddr 結構體,就可 以根據地址類型字段確定結構體中的內容.

? socket API 可以都用 struct sockaddr *類型表示, 在使用的時候需要強制轉化成sockaddr_in; 這樣的好處是程序的通用性, 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各種類型的 sockaddr 結構體指針做為參數;?

?

雖然 socket api 的接口是 sockaddr, 但是我們真正在基于 IPv4 編程時, 使用的數據結 構是sockaddr_in; 這個結構里主要有三部分信息: 地址類型, 端口號, IP 地址.?

?

in_addr 用來表示一個 IPv4 的 IP 地址. 其實就是一個 32 位的整數;?

通常這樣初始化

網絡地址為 INADDR_ANY, 這個宏表示本地的任意 IP 地址,因為服務器可能有 多個網卡,每個網卡也可能綁定多個 IP 地址, 這樣設置可以在所有的 IP 地址上監聽, 直到與某個客戶端建立了連接時才確定下來到底用哪個 IP 地址?

?二.UDP

2.1簡單的接口代碼(demo)

InetAddr.hpp

#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
public:InetAddr(struct sockaddr_in &addr):_addr(addr){_port = ntohs(addr.sin_port);_ip = inet_ntoa(addr.sin_addr);}std::string GetIp(){return _ip;}uint16_t GetPort(){return _port;}
private:std::string _ip;//點分十進制uint16_t _port;struct sockaddr_in _addr;
};

NoCopy.hpp

#pragma once#include<iostream>class NoCopy 
{
public:NoCopy(){}NoCopy(const NoCopy&) = delete;const NoCopy& operator=(const NoCopy&) = delete;~NoCopy(){}
};

Common.hpp

#pragma once enum ExitCode
{OK = 0,SOCKET_ERR,BIND_ERR
};

UdpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;// 禁止拷貝和賦值
class UdpServer : public NoCopy
{
public:UdpServer(int port = defaultport) : _port(port), _sockfd(defaultsockfd){}void Init(){// 1.創建socket_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error...";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;// 2.初始化結構體struct sockaddr_in local;bzero(&local,sizeof(local));//memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;//注意這里不要寫特定的ip值local.sin_port = htons(_port);// 3.bind,結構體填完,還要設置到內核中int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n!=0){LOG(LogLevel::FATAL) << "bind error...";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success...";}void Start(){while(true){char buffer[defaultsize];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(n > 0){InetAddr addr(peer);buffer[n] = 0;std::cout << "client: [" << addr.GetIp() << " "<<addr.GetPort()<<" say# ]" << buffer <<std::endl;sendto(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,len);}}}~UdpServer(){}private://std::string _ip;uint16_t _port;int _sockfd;
};

UdpClient.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <cstdlib>
#include <string.h>
#include <memory>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"using namespace LogModule;int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1.還是創建socketint sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error...";return 2;}LOG(LogLevel::DEBUG) << "sockfd success: " << sockfd;// 2.clent 也要進行bind,但是不要跟server一樣顯示的進行bind,因為服務器的port是眾所周知的,而client的port可能會非常多// 導致端口號沖突,bind的操作OS會隨機給我們分配端口號// 3.填充server信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // ip地址是點分十進制轉換為網絡序列的32為整數server.sin_family = AF_INET;server.sin_port = htons(server_port);while (true){std::string in;std::cout << "please enter# ";std::getline(std::cin, in);// std::cin>>in;//  std::cout<< 1 <<std::endl;int n = sendto(sockfd, in.c_str(), in.size(), 0, (struct sockaddr *)&server, sizeof(server));if (n < 0){LOG(LogLevel::ERROR) << "sendto failed";continue;}char buffer[1024];struct sockaddr_in peer;socklen_t peer_len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peer_len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}return 0;
}

UdpServer.cc

#include"UdpServer.hpp"int main()
{Enable_Console_Log_Strtegy();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>();usvr->Init();usvr->Start();return 0;
} 

運行結果:

服務器:

客戶端:?

2.2代碼小升級,網絡字典查詢

其實也就是在UdpServer端加上了一個上層調用的函數,讓我們能夠實現單詞之間的轉化

UdpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;using func_t = std::function<std::string(const std::string&,InetAddr& cli)>;// 禁止拷貝和賦值
class UdpServer : public NoCopy
{
public:UdpServer(func_t func,int port = defaultport) : _port(port), _sockfd(defaultsockfd),_func(func){}void Init(){// 1.創建socket_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error...";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;// 2.初始化結構體struct sockaddr_in local;bzero(&local,sizeof(local));//memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;//注意這里不要寫特定的ip值local.sin_port = htons(_port);// 3.bind,結構體填完,還要設置到內核中int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n!=0){LOG(LogLevel::FATAL) << "bind error...";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success...";}void Start(){while(true){char buffer[defaultsize];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(n > 0){InetAddr addr(peer);buffer[n] = 0;std::string result = _func(buffer,addr);std::cout << "client: [" << addr.GetIp() << " "<<addr.GetPort()<<" say# ]" << buffer <<std::endl;sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr*)&peer,len);}}}~UdpServer(){}private://std::string _ip;uint16_t _port;int _sockfd;func_t _func;
};

?Dict.hpp

#include<unordered_map>
#include<fstream>
#include"UdpServer.hpp"const static std::string defaultdict = "./dictionary.txt";
const static std::string sep = ":";class Dict
{
public:Dict(const std::string &path = defaultdict):_dict_path(path){}bool LoadWord(){std::ifstream in(_dict_path);if(!in.is_open()){LOG(LogLevel::FATAL) << "open file error...";return false;}std::string line;while(std::getline(in,line))//注意是從文件流里去讀取,一次讀一行{auto pos = line.find(sep);if(pos == std::string::npos){  LOG(LogLevel::FATAL) << "解析文件失敗";continue;}std::string english = line.substr(0,pos);std::string chinese = line.substr(pos + sep.size());if(english.empty()||chinese.empty()){LOG(LogLevel::FATAL) << "沒有有效內容";continue;//返回繼續進行解析文件}_dict[english] = chinese;LOG(LogLevel::DEBUG) << "加載" << line << "成功";}in.close();return true;}std::string Transact(std::string word,InetAddr &client){auto pos = _dict.find(word);if(pos == _dict.end()){LOG(LogLevel::DEBUG) << "進入到了翻譯模塊, [" << client.GetIp() << " : " << client.GetPort() << "]# " << word << "->None";return "None";}LOG(LogLevel::DEBUG) << "進入到了翻譯模塊, [" << client.GetIp() << " : " << client.GetPort() << "]# " << word << "->" << pos->second;return pos->second;}~Dict(){}
private:std::string _dict_path;std::unordered_map<std::string,std::string> _dict;
};

UdpServer.cc

#include"UdpServer.hpp"
#include"Dict.hpp"int main(int argc,char* argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strtegy();Dict dict;dict.LoadWord();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&dict](const std::string& word,InetAddr& cli)->std::string{return dict.Transact(word,cli);},port);usvr->Init();usvr->Start();return 0;
}

dictionary.txt:?

實現效果:?

?

2.3多線程下簡單聊天室

Route.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"using namespace LogModule;class Route
{bool IsExist(InetAddr& client){for (auto &user : _online_user){if (user == client){return true;}}return false;}void AddUser(InetAddr &client){LOG(LogLevel::INFO) << "新增一個在線用戶: " << client.StringAddr();_online_user.push_back(client);}void DeleteUser(InetAddr &peer){for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++){if (*iter == peer){_online_user.erase(iter);LOG(LogLevel::INFO) << "刪除一個在線用戶:" << peer.StringAddr() << "成功";break;}}}public:Route(){}void MessageRoute(int sockfd, std::string &message, InetAddr &peer){if(!IsExist(peer)){AddUser(peer);}std::string send_message = peer.StringAddr() + "# " + message; // 127.0.0.1:8080# 你好for(auto &user : _online_user){sendto(sockfd,send_message.c_str(),send_message.size(),0,user.GetAddr(),sizeof(*user.GetAddr()));}// 這個用戶一定已經在線了if (message == "QUIT"){LOG(LogLevel::INFO) << "刪除一個在線用戶: " << peer.StringAddr();DeleteUser(peer);}}~Route(){}private:std::vector<InetAddr> _online_user;
};

UdpServer.cc

#include"UdpServer.hpp"
#include"Route.hpp"
#include"ThreadPool.hpp"using namespace ThreadPoolModule;using task_t = std::function<void()>;int main(int argc,char* argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strtegy();//1.路由服務Route r;//2.線程池auto tp = ThreadPool<task_t>::GetInstance();std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>([&r,&tp](int sockfd,const std::string& message,InetAddr& cli){task_t t = std::bind(&Route::MessageRoute,&r,sockfd,message,cli);tp->Enqueue(t);},port);usvr->Init();usvr->Start();return 0;
}

UdpServer.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
using namespace LogModule;
const static int defaultport = 8888;
const static int defaultsockfd = -1;
const static int defaultsize = 1024;using func_tt = std::function<void(int sockfd,const std::string&,InetAddr&)>;// 禁止拷貝和賦值
class UdpServer : public NoCopy
{
public:UdpServer(func_tt func,uint16_t port = defaultport) : _port(port), _sockfd(defaultsockfd),_func(func){}void Init(){// 1.創建socket_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error...";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd success: " << _sockfd;// 2.初始化結構體struct sockaddr_in local;bzero(&local,sizeof(local));//memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_addr.s_addr = INADDR_ANY;//注意這里不要寫特定的ip值local.sin_port = htons(_port);// 3.bind,結構體填完,還要設置到內核中int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n!=0){LOG(LogLevel::FATAL) << "bind error...";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success...";}void Start(){while(true){char buffer[defaultsize];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);if(n > 0){InetAddr addr(peer);buffer[n] = 0;_func(_sockfd,buffer,addr);//路由功能,外部傳函數}}}~UdpServer(){}private://std::string _ip;uint16_t _port;int _sockfd;func_tt _func;
};

客戶端也進行多線程修改

UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Thread.hpp"int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id;using namespace ThreadModule;void Recv()
{while (true){char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);if (m > 0){buffer[m] = 0;std::cerr << buffer << std::endl; // 2}}
}
void Send()
{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());const std::string online = "inline";sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));while (true){std::string input;//std::cout << "Please Enter# "; // 1std::getline(std::cin, input); // 0int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));(void)n;if (input == "QUIT"){pthread_cancel(id);break;}}
}// client 我們也要做多線程改造
// ./udpclient server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}server_ip = argv[1];server_port = std::stoi(argv[2]);// 1. 創建socketsockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "socket error" << std::endl;return 2;}// 2. 創建線程Thread recver(Recv);Thread sender(Send);recver.Start();sender.Start();recver.Join();sender.Join();// 2. 本地的ip和端口是什么?要不要和上面的“文件”關聯呢?// 問題:client要不要bind?需要bind.//       client要不要顯式的bind?不要!!首次發送消息,OS會自動給client進行bind,OS知道IP,端口號采用隨機端口號的方式//   為什么?一個端口號,只能被一個進程bind,為了避免client端口沖突//   client端的端口號是幾,不重要,只要是唯一的就行!// 填寫服務器信息return 0;
}

?三.地址轉換函數

sockaddr_in 中的成員 struct in_addr sin_addr 表示 32 位 的 IP 地址

但是我們通常用點分十進制的字符串表示 IP 地址,以下函數可以在字符串表示 和 in_addr 表示之間轉換;

字符串轉 in_addr 的函數:

in_addr 轉字符串的函數:

其中 inet_pton 和 inet_ntop 不僅可以轉換 IPv4 的 in_addr,還可以轉換 IPv6 的 in6_addr,因此函數接口是 void *addrptr。?

inet_ntoa?

inet_ntoa 這個函數返回了一個 char*, 很顯然是這個函數自己在內部為我們申請了一塊 內存來保存 ip 的結果. 那么是否需要調用者手動釋放呢??

man 手冊上說, inet_ntoa 函數, 是把這個返回結果放到了靜態存儲區. 這個時候不需要 我們手動進行釋放. 那么問題來了, 如果我們調用多次這個函數, 會有什么樣的效果呢? 參見如下代碼:

?

因為 inet_ntoa 把結果放到自己內部的一個靜態存儲區, 這樣第二次調用時的結果會覆 蓋掉上一次的結果.

在多線程環境下, 推薦使用 inet_ntop, 這個函數由調用者提供一個緩沖區保存 結果, 可以規避線程安全問題;?

?四.TCP

多線程版本,多進程版本,線程池版本

TcpServer.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include "Command.hpp"
#include "ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;using func_tt = std::function<void()>;class TcpServer : public NoCopy
{
public:TcpServer(uint16_t port) : _port(port), _isruning(false){}void Init(){// 1.創建listensocket_listensock = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){LOG(LogLevel::FATAL) << "socket error...";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "socket success: " << _listensock;// 2.bind// struct sockaddr_in server;// bzero(&server, sizeof(server));// server.sin_addr.s_addr = INADDR_ANY;// server.sin_family = AF_INET;// server.sin_port = htons(_port);InetAddr server(_port);int n = ::bind(_listensock, CONV(&server.NetAddr()), server.GetAddrLen());if (n < 0){LOG(LogLevel::FATAL) << "bind error...";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";// 3.設置監聽狀態,listenif (::listen(_listensock, 5) < 0){LOG(LogLevel::FATAL) << "listen error...";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";}void Start(){_isruning = true;while (_isruning){// 4.獲取鏈接,acceptstruct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = ::accept(_listensock, CONV(&peer), &len);if (sockfd < 0){LOG(LogLevel::FATAL) << "accept error...";continue;}InetAddr client(peer);LOG(LogLevel::DEBUG) << "accept success ,name: " << client.StringAddr();// 5.提供服務// Service(sockfd);// close(sockfd);// ProcessConnection(sockfd,peer);//ThreadConnection(sockfd, peer);ThreadPoolConnection(sockfd,client);}}void Service(int sockfd,InetAddr& peer){char buffer[1024];while (true){ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n == 0) // read 如果返回值是 0,表示讀到了文件結尾(對端關閉了連接!){LOG(LogLevel::FATAL) << peer.StringAddr() << "client quit";break;}else{LOG(LogLevel::FATAL)<< peer.StringAddr() << "read error...";break;}}}// void Service(int sockfd,InetAddr& peer)// {//     Command com;//     char buffer[1024];//     while(true)//     {//         // 1. 先讀取數據//         // a. n>0: 讀取成功//         // b. n<0: 讀取失敗//         // c. n==0: 對端把鏈接關閉了,讀到了文件的結尾 --- pipe//         ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);//         if (n > 0)//         {//             // buffer是一個英文單詞 or 是一個命令字符串//             buffer[n] = 0; // 設置為C風格字符串, n<= sizeof(buffer)-1//             std::string echo_string = com.Execute(buffer, peer);//             write(sockfd, echo_string.c_str(), echo_string.size());//         }//         else if (n == 0)//         {//             LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";//             close(sockfd);//             break;//         }//         else//         {//             LOG(LogLevel::DEBUG) << peer.StringAddr() << " 異常...";//             close(sockfd);//             break;//         }//     }// }class ThreadData{public:ThreadData(int sockfd, InetAddr &ie, TcpServer *t) : _sockfd(sockfd), _client(ie), _tsv(t){}~ThreadData(){}int _sockfd;InetAddr _client;TcpServer *_tsv;};//線程池版本void ThreadPoolConnection(int sockfd,InetAddr& peer){ThreadPool<func_tt>::GetInstance()->Enqueue([this,sockfd,&peer](){this->Service(sockfd,peer);});}// 多線程服務static void *Routine(void *args){pthread_detach(pthread_self());ThreadData *tdv = static_cast<ThreadData *>(args);tdv->_tsv->Service(tdv->_sockfd,tdv->_client);close(tdv->_sockfd);delete tdv;return nullptr;}void ThreadConnection(int sockfd, struct sockaddr_in &peer){InetAddr client(peer);pthread_t tid;// std::shared_ptr<ThreadData> tdsv = std::make_shared<ThreadData>(sockfd,client,this);ThreadData *tdv = new ThreadData(sockfd, client, this);pthread_create(&tid, nullptr, Routine, (void *)tdv);}// 多進程服務void ProcessConnection(int sockfd, struct sockaddr_in &peer){pid_t id = fork();if (id < 0){close(sockfd);return;}else if (id == 0){// 子進程再去創建子進程,孫子進程// 子進程,子進程除了看到sockfd,能看到listensockfd嗎??//  我們不想讓子進程訪問listensock!close(_listensock);if (fork() > 0){exit(0); // 大于0,是子進程}// 到這里是孫子進程,是孤兒進程,系統去回收InetAddr client(peer);LOG(LogLevel::DEBUG) << "process connection: " << client.StringAddr();Service(sockfd,client);close(sockfd);exit(0);}else{// 父進程去關閉創建的子進程close(sockfd);// 這里要等待子進程pid_t rid = waitpid(id, nullptr, 0);(void)rid;}}~TcpServer(){}private:uint16_t _port;int _listensock;bool _isruning;
};

TcpClient.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <strings.h>
#include <string.h>
#include <cstdlib>
#include <memory>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"using namespace LogModule;
void Usage(const std::string &process)
{std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return 1;}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);// 1.建立套接字int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket error...";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "socket success: " << sockfd;// 2.不需要用戶bind// 3.連接InetAddr local(server_ip, server_port);int n = ::connect(sockfd, local.GetAddr(), local.GetAddrLen());if (n < 0){LOG(LogLevel::FATAL) << "connect error...";exit(CONNECT_ERR);}LOG(LogLevel::DEBUG) << "connect success: " << sockfd;// 4.鏈接成功,通信while (true){std::string client_buffer;std::cout << "Please Enter# ";std::getline(std::cin, client_buffer);ssize_t n = write(sockfd, client_buffer.c_str(), client_buffer.size());char server_buffer[1024];ssize_t size = read(sockfd, server_buffer, sizeof(server_buffer)-1);if(size > 0){server_buffer[size] = 0;std::cout << server_buffer << std::endl;}}close(sockfd);return 0;
}

實現一個簡單的遠程命令

Command.hpp

#pragma once
#include <iostream>
#include <string>
#include <set>
#include <unistd.h>
#include <fstream>
#include "InetAddr.hpp"
#include "Log.hpp"using namespace LogModule;class Command
{
public:Command(){// 嚴格匹配_WhiteListCommands.insert("ls");_WhiteListCommands.insert("pwd");_WhiteListCommands.insert("ls -l");_WhiteListCommands.insert("touch haha.txt");_WhiteListCommands.insert("who");_WhiteListCommands.insert("whoami");}bool IsSafeCommand(const std::string &cmd){auto iter = _WhiteListCommands.find(cmd);return iter != _WhiteListCommands.end();}std::string Execute(const std::string &cmd, InetAddr &addr){// 1. 屬于白名單命令if(!IsSafeCommand(cmd)){return std::string("壞人");}std::string who = addr.StringAddr();// 2. 執行命令// std::ifstream in;// in.open(cmd.c_str());FILE *fp = popen(cmd.c_str(), "r");if(nullptr == fp){return std::string("你要執行的命令不存在: ") + cmd;}std::string res;char line[1024];while(fgets(line, sizeof(line), fp)){res += line;}pclose(fp);std::string result = who + "execute done, result is: \n" + res;LOG(LogLevel::DEBUG) << result;return result;}~Command(){}
private:std::set<std::string> _WhiteListCommands;
};

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

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

相關文章

機器人軌跡跟蹤控制與動力學模型詳解

1. 機器人控制的本質&#xff1a;通過關節扭矩執行軌跡 機器人控制的核心目標是讓機器人關節精確跟蹤期望軌跡 ( q d , q ˙ d , q d ) (q_d, \dot{q}_d, \ddot{q}_d) (qd?,q˙?d?,q?d?)。為此&#xff0c;控制器需根據當前狀態 ( q , q ˙ ) (q, \dot{q}) (q,q˙?)計…

智能辦公與科研革命:ChatGPT+DeepSeek大模型在論文撰寫、數據分析與AI建模中的實踐指南

隨著人工智能技術的快速發展&#xff0c;大語言模型如ChatGPT和DeepSeek在科研領域的應用正在為科研人員提供強大的支持。這些模型通過深度學習和大規模語料庫訓練&#xff0c;能夠幫助科研人員高效地篩選文獻、生成論文內容、進行數據分析和優化機器學習模型。 ChatGPT和Deep…

運營商場景下的實時脫敏方案:PB 級日志流的分布式處理架構

在數字化浪潮中&#xff0c;運營商積累了海量數據&#xff0c;涵蓋用戶信息、通信記錄、業務運營數據等。這些數據不僅是運營商業務運營的關鍵資產&#xff0c;也是創新服務、精準營銷的核心驅動力。然而&#xff0c;隨著數據量呈指數級增長&#xff0c;運營商每日需處理 PB 級…

docker+n8n的工作流中無法使用本地ollama服務的問題

使用docker創建n8n服務后&#xff0c;工作流中不想用大模型付費API測試&#xff0c;想用本地大模型來跑&#xff0c;剛好電腦上裝了ollama&#xff0c;就試了下添加ollama節點來替代大模型付費API&#xff0c;結果就遇到了以下問題 ollama正常運行中 但是工作流會卡在這&…

通過交互式可視化探索波動方程-AI云計算數值分析和代碼驗證

波動方程是一個基本的數學模型&#xff0c;它描述了各種類型的波&#xff08;包括機械波、聲波、電磁波和流體波&#xff09;如何通過不同的介質傳播&#xff0c;這使得它對于物理學、工程學和其他科學學科中聲學、光學、醫學成像和電信等領域的預測和設計都至關重要。 波動方程…

10授權

目錄 本節大綱 一、權限管理 1. 認證 2. 授權 二、授權核心概念 三、權限管理策略 1. 基于 URL 權限管理 權限表達式 2. 基于 方法 權限管理 EnableGlobalMethodSecurity 四、基本用法 五、原理分析 六、實戰 1. 簡介 2. 庫表設計 3. 創建 springboot 應用 本節…

線性規劃模型

線性規劃算是數學建模中最基礎的模型了&#xff0c;其典型特征就是線性和有限資源&#xff0c;即在一組線性約束條件下&#xff0c;求解一個線性目標函數的最大值或最小值問題&#xff1a; 其中x 是決策變量向量&#xff0c;c 是目標函數系數向量&#xff0c;a 和 b 分別是約束…

華為云Flexus+DeepSeek征文|體驗華為云ModelArts快速搭建Dify-LLM應用開發平臺并創建知識庫大模型工作流查詢數據庫數據

華為云FlexusDeepSeek征文&#xff5c;體驗華為云ModelArts快速搭建Dify-LLM應用開發平臺并創建知識庫大模型工作流查詢數據庫數據 什么是華為云ModelArts 華為云ModelArts ModelArts是華為云提供的全流程AI開發平臺&#xff0c;覆蓋從數據準備到模型部署的全生命周期管理&am…

WPF中Style和Template異同

在WPF&#xff08;Windows Presentation Foundation&#xff09;中&#xff0c;Style和Template是兩個核心概念&#xff0c;用于控制UI元素的外觀和行為&#xff0c;但它們的職責和使用場景有明顯區別。以下是詳細分析&#xff1a; 一、基本概念 1. Style&#xff08;樣式&am…

針對 DVWA 中 Command Injection 模塊的亂碼問題及解決辦法

目錄 根本原因 解決辦法 優化說明 適用范圍 系統兼容性 在 DVWA 的 Command Injection 模塊中執行系統命令時&#xff0c;返回結果出現亂碼&#xff08;如圖1所示&#xff09;。 根本原因 DVWA 默認使用 UTF-8 編碼&#xff0c;而部分系統命令&#xff08;如 Windows 的…

Linux獲取ImageNet數據集方法及小規模imagenet

一、數據集下載 ImageNet官方鏈接&#xff1a;ImageNet Linux命令直接下載&#xff1a; 訓練集 wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_train.tar --no-check-certificate驗證集 wget https://image-net.org/data/ILSVRC/2012/ILSVRC2012_img_val.t…

JAVA八股文:異常有哪些種類,可以舉幾個例子嗎?Throwable類有哪些常見方法?

Throwable、Error 與 Exception 所有的異常類型都繼承自 java.lang.Throwable。 其中 Error&#xff08;比如 OutOfMemoryError、StackOverflowError、類加載失敗等&#xff09;表示 JVM 自身或運行環境的問題&#xff0c;不應該也通常無法由應用程序去捕獲或恢復&#xff0c;…

.NetCore+Vue快速生產框架開發詳細方案

文章目錄 1. 項目概述 1.1 項目背景1.2 項目目標1.3 核心功能 2. 技術棧選擇 2.1 后端技術棧2.2 前端技術棧2.3 開發工具 3. 系統架構設計 3.1 整體架構3.2 后端架構設計3.3 前端架構設計3.4 微服務考慮 4. 后端.NET核心設計 4.1 項目結構4.2 核心模塊設計4.2.1 用戶模塊4.2.2 …

WPF學習筆記(18)觸發器Trigger

觸發器 1. 概述2. 詳解2.1. Trigger 用法2.2. MultiTrigger 用法2.3. DataTrigger 用法2.4. EventTrigger 用法 總結 1. 概述 官方文檔&#xff1a;https://learn.microsoft.com/zh-cn/dotnet/api/system.windows.trigger?viewnetframework-4.8 2. 詳解 在Style中可以指定觸…

記本好書:矩陣力量:線性代數全彩圖解+微課+Python編程

書名&#xff1a;矩陣力量&#xff1a;線性代數全彩圖解微課Python編程 作者&#xff1a;姜偉生 出版社&#xff1a;清華大學出版社 出版時間&#xff1a;2023-06-01 ISBN&#xff1a;9787302632511 品牌方&#xff1a;清華大學出版社有限公司 發現一本好書&#xff0c;但是一…

?Webpack打包流程

Webpack打包流程的核心步驟包括初始化配置、解析入口文件、構建依賴圖、模塊轉換、資源優化和輸出文件?。該流程通過遞歸分析模塊依賴關系&#xff0c;結合加載器和插件處理各類資源&#xff0c;最終生成優化后的靜態文件。 ?核心流程概述? ?初始化配置?&#xff1a;讀取…

入門pytorch-聯邦學習

本文聯邦學習的代碼引用于https://github.com/shaoxiongji/federated-learning 本篇文章相當于帶大家讀一遍聯邦學習的代碼&#xff0c;同時加深了大家對聯邦學習和Pytorch框架的理解。 這里想簡單介紹一下聯邦學習。 聯邦學習說白了&#xff0c;就是假如有 N N N個數據擁有…

半導體和PN結

1. 什么是半導體&#xff1f; 導體&#xff0c;電阻率小&#xff0c;即電流容易通過的材料&#xff1b;Cu 絕緣體&#xff0c;導電性低&#xff0c;即電流不易通過的材料&#xff1b;塑料 半導體&#xff0c;常溫下導電性能介于導體和絕緣體之間&#xff0c;是一種導電可控的…

如何分析大語言模型(LLM)的內部表征來評估文本的“誠實性”

如何分析大語言模型(LLM)的內部表征來評估文本的“誠實性” 基于這樣一個假設:模型在生成誠實和不誠實回答時,其內部狀態會存在系統性差異 LAT :線性人工斷層掃描 我們通過一個生活化的例子來理解如何通過分析大語言模型的內部表征評估文本的“誠實性”。 場景類比:判…

【算法】動態規劃 矩陣 :62. 不同路徑

62. 不同路徑 一個機器人位于一個 m x n 網格的左上角 &#xff08;起始點在下圖中標記為 “Start” &#xff09;。 機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角&#xff08;在下圖中標記為 “Finish” &#xff09;。 問總共有多少條不同的路徑&…