一.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_INET
和PF_INET
可互換)。 - 錯誤處理:
若參數無效(如不支持的協議族),socket()
會返回-1
,并設置errno
為EAFNOSUPPORT
。
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
,表示根據domain
和type
自動選擇默認協議。
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結構

? 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;
};