Socket 編程 UDP
- UDP 網絡編程
- V1 版本 - echo server
- V2 版本 - DictServer
- V3 版本 - 簡單聊天室
- 補充參考內容
- 地址轉換函數
- 關于 inet_ntoa
UDP 網絡編程
- 聲明:下面代碼的驗證都是用Windows作為客戶端的,如果你有兩臺云服務器可以直接粘貼我在Linux下的客戶端代碼。這里我的客戶端使用Windows寫的如果用發現你的Windows無法連接到服務器就要去修改你的云服務器的安全組,這里就不詳細贅述了,大家感興趣可以自行搜索
V1 版本 - echo server
- 代碼用到的log.hpp在我的專欄系統部分可以找到這里就不貼了
//UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
using namespace LogModule;
using func_t = std::function<std::string(std::string message)>;
class UdpServe
{
public:UdpServe(uint16_t port, func_t func): _port(port),_func(func){}~UdpServe() {}void Start(){_isrunning = true;while (_isrunning){// 1.接受消息sockaddr_in peer; // 客戶端socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);if (n > 0){buffer[n] = 0;std::string peer_ip=inet_ntoa(peer.sin_addr);int peer_port=ntohs(peer.sin_port);std::string ret = _func(std::string(buffer));LOG(LogLevel::INFO)<<"[" << peer_ip << ":" << peer_port<< "]# " << ret;}//2.發消息std::string sermes="serve say@"+std::string(buffer);//std::cout<<sermes<<std::endl;ssize_t n2=sendto(_sockfd,sermes.c_str(),sermes.size(),0,(sockaddr*)&peer,len);}}void Init(){// 1.創建我們的sockfd// 前兩個參數就規定了我們用的就是udpint n = socket(AF_INET, SOCK_DGRAM, 0);if (n < 0){LOG(LogLevel::FATAL) << "sock error";exit(2);}_sockfd=n;LOG(LogLevel::INFO) << "sock success";// 創建成功了// 進行綁定sockaddr_in local;// 首先進行清零bzero(&local, sizeof(local));// 填充字段local.sin_family = AF_INET;// 把本地轉為網絡序列local.sin_port = htons(_port);// 這里就是不顯示綁定而是綁定該太機器上任意的ip地址local.sin_addr.s_addr = INADDR_ANY;int ret = bind(_sockfd, (sockaddr *)&local, sizeof(local));if (ret < 0){LOG(LogLevel::FATAL) << "bind failsure";exit(2);}LOG(LogLevel::INFO) << "bind success";}private:int _sockfd;// std::string _ip;//服務器端一臺機器上可能有多個ip我們最好的就是不顯示綁定我們的ip地址uint16_t _port;bool _isrunning;func_t _func;
};
//UdpServe.cc
#include"UdpServe.hpp"
#include<memory>
std::string defaultfunc(std::string messges)
{return messges;
}int main(int argc,char*argv[])
{if(argc!=2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}ENABLE_CONSOLE_LOG_STRATEGY();uint16_t port=std::stoi(argv[1]);std::unique_ptr<UdpServe> udpserveptr=std::make_unique<UdpServe>(port,defaultfunc);udpserveptr->Init();udpserveptr->Start();return 0;
}
//UdpClient.cc Linux
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#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;}ENABLE_CONSOLE_LOG_STRATEGY();std::string serve_ip = argv[1];int serve_port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket failsure";exit(2);}// 客戶端不用顯示綁定在我們第一次發消息的時候就os會自動幫我們綁定客戶端機器的ip,端口號隨機生成sockaddr_in serve;bzero(&serve, sizeof(serve));socklen_t len = sizeof(serve);serve.sin_port = htons(serve_port);serve.sin_family = AF_INET;serve.sin_addr.s_addr = inet_addr(serve_ip.c_str());while (true){std::string mesage;std::cout << "Please Enter:" << std::endl;// 從標準輸入流讀取mesagegetline(std::cin, mesage);int n = sendto(sockfd, mesage.c_str(), mesage.size(), 0, (sockaddr *)&serve, len);(void)n;// 收消息char buffer[1024];sockaddr_in peer;bzero(&peer, sizeof(peer));socklen_t peerlen=sizeof(peer);int m= recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(sockaddr*)&peer,&peerlen);if(m>0){buffer[m]=0;std::cout<<buffer<<std::endl;}}return 0;
}
驗證效果:
//windows作為客戶端
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#include <io.h>
#include <fcntl.h>#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")std::string serverip = ""; // 填寫你的云服務器 ip
uint16_t serverport =; // 填寫你的云服務開放的端口號// 設置控制臺編碼為 UTF-8
void SetConsoleToUTF8() {SetConsoleOutputCP(65001); // 設置控制臺輸出編碼為 UTF-8SetConsoleCP(65001); // 設置控制臺輸入編碼為 UTF-8_setmode(_fileno(stdout), _O_U8TEXT); // 設置寬字符輸出模式_setmode(_fileno(stdin), _O_U8TEXT); // 設置寬字符輸入模式
}int main() {SetConsoleToUTF8(); // 必須在其他操作前調用WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);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());SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == INVALID_SOCKET) {std::wcout << L"socket error: " << WSAGetLastError() << std::endl;return 1;}std::wstring wmessage;char buffer[1024];while (true) {std::wcout << L"Please Enter@ ";std::getline(std::wcin, wmessage);if (wmessage.empty()) continue;// 寬字符轉 UTF-8int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), NULL, 0, NULL, NULL);std::string message(size_needed, 0);WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), &message[0], size_needed, NULL, NULL);sendto(sockfd, message.c_str(), (int)message.size(), 0,(struct sockaddr*)&server, sizeof(server));struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (s > 0) {buffer[s] = 0;// UTF-8 轉寬字符顯示int wsize_needed = MultiByteToWideChar(CP_UTF8, 0, buffer, s, NULL, 0);std::wstring wbuffer(wsize_needed, 0);MultiByteToWideChar(CP_UTF8, 0, buffer, s, &wbuffer[0], wsize_needed);std::wcout << wbuffer << std::endl;}}closesocket(sockfd);WSACleanup();return 0;
}
V2 版本 - DictServer
- 代碼用到的其余的模塊在我的專欄系統部分可以找到代碼這里就不貼了,只貼了服務端代碼和客戶端代碼
//UdpServe.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t = std::function<std::string(std::string message, InetAddr peer)>;
class UdpServe
{
public:UdpServe(uint16_t port, func_t func): _port(port),_func(func){}~UdpServe() {}void Start(){_isrunning = true;while (_isrunning){// 1.接受消息sockaddr_in peer; // 客戶端socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);std::string ret;if (n > 0){buffer[n] = 0;// std::string peer_ip=inet_ntoa(peer.sin_addr);// int peer_port=ntohs(peer.sin_port);InetAddr client(peer);ret = _func(std::string(buffer), client);// LOG(LogLevel::INFO)<<"[" << peer_ip << ":" << peer_port<< "]# " << ret;}// 2.發消息// std::string sermes="serve say@"+std::string(buffer);// std::cout<<sermes<<std::endl;ssize_t n2 = sendto(_sockfd, ret.c_str(), ret.size(), 0, (sockaddr *)&peer, len);}}void Init(){// 1.創建我們的sockfd// 前兩個參數就規定了我們用的就是udpint n = socket(AF_INET, SOCK_DGRAM, 0);if (n < 0){LOG(LogLevel::FATAL) << "sock error";exit(2);}_sockfd = n;LOG(LogLevel::INFO) << "sock success";// 創建成功了// 進行綁定sockaddr_in local;// 首先進行清零bzero(&local, sizeof(local));// 填充字段local.sin_family = AF_INET;// 把本地轉為網絡序列local.sin_port = htons(_port);// 這里就是不顯示綁定而是綁定該太機器上任意的ip地址local.sin_addr.s_addr = INADDR_ANY;int ret = bind(_sockfd, (sockaddr *)&local, sizeof(local));if (ret < 0){LOG(LogLevel::FATAL) << "bind failsure";exit(2);}LOG(LogLevel::INFO) << "bind success";}private:int _sockfd;// std::string _ip;//服務器端一臺機器上可能有多個ip我們最好的就是不顯示綁定我們的ip地址uint16_t _port;bool _isrunning;func_t _func;
};
//UdpServe.cc
#include "UdpServe.hpp"
#include "dict.hpp"
#include <memory>
#include "InetAddr.hpp"
std::string defaultfunc(std::string messges)
{return messges;
}int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}ENABLE_CONSOLE_LOG_STRATEGY();uint16_t port = std::stoi(argv[1]);// 字典Dict dict;dict.LoadDict();std::unique_ptr<UdpServe> udpserveptr = std::make_unique<UdpServe>(port, [&dict](std::string mess, InetAddr peer) -> std::string {return dict.Translate(mess,peer);});udpserveptr->Init();udpserveptr->Start();return 0;
}
//UdpClient.cc Linux
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#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;}ENABLE_CONSOLE_LOG_STRATEGY();std::string serve_ip = argv[1];int serve_port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket failsure";exit(2);}// 客戶端不用顯示綁定在我們第一次發消息的時候就os會自動幫我們綁定客戶端機器的ip,端口號隨機生成sockaddr_in serve;bzero(&serve, sizeof(serve));socklen_t len = sizeof(serve);serve.sin_port = htons(serve_port);serve.sin_family = AF_INET;serve.sin_addr.s_addr = inet_addr(serve_ip.c_str());while (true){std::string mesage;std::cout << "Please Enter:" << std::endl;// 從標準輸入流讀取mesagegetline(std::cin, mesage);int n = sendto(sockfd, mesage.c_str(), mesage.size(), 0, (sockaddr *)&serve, len);(void)n;// 收消息char buffer[1024];sockaddr_in peer;bzero(&peer, sizeof(peer));socklen_t peerlen=sizeof(peer);int m= recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(sockaddr*)&peer,&peerlen);if(m>0){buffer[m]=0;std::cout<<buffer<<std::endl;}}return 0;
}
//InetAddr.hpp
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string>
class InetAddr
{public:InetAddr(sockaddr_in &sock):_sockaddrin(sock){_ip=inet_ntoa(_sockaddrin.sin_addr);_port=ntohs(_sockaddrin.sin_port);}~InetAddr(){}std::string Ip(){return _ip;}uint16_t Port(){return _port;}private:sockaddr_in _sockaddrin;std::string _ip;uint16_t _port;};
//windows作為客戶端
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#include <io.h>
#include <fcntl.h>#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")std::string serverip = ""; // 填寫你的云服務器 ip
uint16_t serverport =; // 填寫你的云服務開放的端口號// 設置控制臺編碼為 UTF-8
void SetConsoleToUTF8() {SetConsoleOutputCP(65001); // 設置控制臺輸出編碼為 UTF-8SetConsoleCP(65001); // 設置控制臺輸入編碼為 UTF-8_setmode(_fileno(stdout), _O_U8TEXT); // 設置寬字符輸出模式_setmode(_fileno(stdin), _O_U8TEXT); // 設置寬字符輸入模式
}int main() {SetConsoleToUTF8(); // 必須在其他操作前調用WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);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());SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == INVALID_SOCKET) {std::wcout << L"socket error: " << WSAGetLastError() << std::endl;return 1;}std::wstring wmessage;char buffer[1024];while (true) {std::wcout << L"Please Enter@ ";std::getline(std::wcin, wmessage);if (wmessage.empty()) continue;// 寬字符轉 UTF-8int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), NULL, 0, NULL, NULL);std::string message(size_needed, 0);WideCharToMultiByte(CP_UTF8, 0, &wmessage[0], (int)wmessage.size(), &message[0], size_needed, NULL, NULL);sendto(sockfd, message.c_str(), (int)message.size(), 0,(struct sockaddr*)&server, sizeof(server));struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (s > 0) {buffer[s] = 0;// UTF-8 轉寬字符顯示int wsize_needed = MultiByteToWideChar(CP_UTF8, 0, buffer, s, NULL, 0);std::wstring wbuffer(wsize_needed, 0);MultiByteToWideChar(CP_UTF8, 0, buffer, s, &wbuffer[0], wsize_needed);std::wcout << wbuffer << std::endl;}}closesocket(sockfd);WSACleanup();return 0;
}
驗證效果:
V3 版本 - 簡單聊天室
//linux udpserve.hpp
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
using func_t1 = std::function<void(int sockfd,std::string message, InetAddr peer)>;
class UdpServe
{
public:UdpServe(uint16_t port, func_t1 func): _port(port),_func(func){}~UdpServe() {}void Start(){_isrunning = true;while (_isrunning){// 1.接受消息sockaddr_in peer; // 客戶端socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &len);std::string ret;if (n > 0){buffer[n] = 0;// std::string peer_ip=inet_ntoa(peer.sin_addr);// int peer_port=ntohs(peer.sin_port);InetAddr client(peer);_func(_sockfd,std::string(buffer), client);// LOG(LogLevel::INFO)<<"[" << peer_ip << ":" << peer_port<< "]# " << ret;}// 2.發消息// std::string sermes="serve say@"+std::string(buffer);// std::cout<<sermes<<std::endl;//ssize_t n2 = sendto(_sockfd, ret.c_str(), ret.size(), 0, (sockaddr *)&peer, len);}}void Init(){// 1.創建我們的sockfd// 前兩個參數就規定了我們用的就是udpint n = socket(AF_INET, SOCK_DGRAM, 0);if (n < 0){LOG(LogLevel::FATAL) << "sock error";exit(2);}_sockfd = n;LOG(LogLevel::INFO) << "sock success";// 創建成功了// 進行綁定sockaddr_in local;// 首先進行清零bzero(&local, sizeof(local));// 填充字段local.sin_family = AF_INET;// 把本地轉為網絡序列local.sin_port = htons(_port);// 這里就是不顯示綁定而是綁定該太機器上任意的ip地址local.sin_addr.s_addr = INADDR_ANY;int ret = bind(_sockfd, (sockaddr *)&local, sizeof(local));if (ret < 0){LOG(LogLevel::FATAL) << "bind failsure";exit(2);}LOG(LogLevel::INFO) << "bind success";}private:int _sockfd;// std::string _ip;//服務器端一臺機器上可能有多個ip我們最好的就是不顯示綁定我們的ip地址uint16_t _port;bool _isrunning;func_t1 _func;
};//linux udpserve.cc
#include "UdpServe.hpp"
#include <memory>
#include "InetAddr.hpp"
#include "Route.hpp"
#include "PthreadPool.hpp"
#include <functional>
using funcpopl = std::function<void()>;
using namespace PthreadModlue;
std::string defaultfunc(std::string messges)
{return messges;
}int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " port" << std::endl;return 1;}ENABLE_CONSOLE_LOG_STRATEGY();uint16_t port = std::stoi(argv[1]);// 1.創建route對象提供吧消息轉發給所有的在線用戶Route r;// 2.創建線程池auto pt = PthreadPoolModule::PthreadPool<funcpopl>::GetInstance();pt->Start();std::unique_ptr<UdpServe> udpserveptr = std::make_unique<UdpServe>(port, [&r, &pt](int sockfd, std::string message, InetAddr peer){funcpopl fun=std::bind(&Route::RouteMessage,&r,sockfd,message,peer);pt->Enqueue(fun);});udpserveptr->Init();udpserveptr->Start();return 0;
}//udpclient.cc linux
#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <memory>
#include "log.hpp"
#include "pthread.hpp"
using namespace LogModule;
using namespace PthreadModlue;
std::string serve_ip;
int serve_port;
int sockfd;
socklen_t len;
sockaddr_in serve;
pthread_t reid;
void recive()
{while (true){char buffer[1024];sockaddr_in peer;bzero(&peer, sizeof(peer));socklen_t peerlen = sizeof(peer);int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&peer, &peerlen);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}}
}
void sends()
{const std::string online = "inline";sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&serve, sizeof(serve));while (true){std::string mesage;std::cout << "Please Enter:" << std::endl;// 從標準輸入流讀取mesagegetline(std::cin, mesage);int n = sendto(sockfd, mesage.c_str(), mesage.size(), 0, (sockaddr *)&serve, len);(void)n;if (mesage == std::string("QUIT")){pthread_cancel(reid);break;}}
}int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}ENABLE_CONSOLE_LOG_STRATEGY();serve_ip = argv[1];serve_port = std::stoi(argv[2]);sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(LogLevel::FATAL) << "socket failsure";exit(2);}// 客戶端不用顯示綁定在我們第一次發消息的時候就os會自動幫我們綁定客戶端機器的ip,端口號隨機生成bzero(&serve, sizeof(serve));len = sizeof(serve);serve.sin_port = htons(serve_port);serve.sin_family = AF_INET;serve.sin_addr.s_addr = inet_addr(serve_ip.c_str());Pthread p1(recive);Pthread p2(sends);p1.Start();p2.Start();reid = p1.Ip();p1.Join();p2.Join();return 0;
}
//window udpclient.cc
#include <iostream>
#include <string>
#include <winsock2.h>
#include <process.h>
#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable : 4996)
#define BUFFER_SIZE 1024SOCKET sockfd;
sockaddr_in server_addr;
bool running = true;// 接收線程函數
unsigned __stdcall RecvThread(void* param) {char buffer[BUFFER_SIZE];int server_len = sizeof(server_addr);while (running) {int n = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,(sockaddr*)&server_addr, &server_len);if (n > 0) {buffer[n] = '\0';std::cout << "\n[Server] " << buffer << std::endl;std::cout << "Enter message: ";}}return 0;
}// 發送線程函數
unsigned __stdcall SendThread(void* param) {const std::string online = "online";sendto(sockfd, online.c_str(), online.size(), 0,(sockaddr*)&server_addr, sizeof(server_addr));while (running) {std::string message;std::cout << "Enter message: ";std::getline(std::cin, message);if (message == "QUIT") {running = false;break;}sendto(sockfd, message.c_str(), message.size(), 0,(sockaddr*)&server_addr, sizeof(server_addr));}// 清理closesocket(sockfd);WSACleanup();return 0;
}int main() {// 初始化WinsockWSADATA wsaData;if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {std::cerr << "WSAStartup failed\n";return 1;}// 創建套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == INVALID_SOCKET) {std::cerr << "Socket creation failed: " << WSAGetLastError() << "\n";WSACleanup();return 1;}// 硬編碼服務器信息const char* SERVER_IP = "116.205.122.71";const unsigned short SERVER_PORT = 8080;// 配置服務器地址memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);// 驗證地址有效性if (server_addr.sin_addr.s_addr == INADDR_NONE) {std::cerr << "Invalid server IP format!\n";closesocket(sockfd);WSACleanup();return 1;}std::cout << "=== Connecting to " << SERVER_IP << ":" << SERVER_PORT << " ===\n";// 創建線程HANDLE hRecv = (HANDLE)_beginthreadex(nullptr, 0, RecvThread, nullptr, 0, nullptr);HANDLE hSend = (HANDLE)_beginthreadex(nullptr, 0, SendThread, nullptr, 0, nullptr);// 等待線程結束WaitForSingleObject(hRecv, INFINITE);WaitForSingleObject(hSend, INFINITE);CloseHandle(hRecv);CloseHandle(hSend);return 0;
}
效果顯示:
補充參考內容
地址轉換函數
字符串轉 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_ntoa, 是否會出現異常情況呢?
- 在 APUE 中, 明確提出 inet_ntoa 不是線程安全的函數;
- 但是在 centos7 上測試, 并沒有出現問題, 可能內部的實現加了互斥鎖;
- 在多線程環境下, 推薦使用 inet_ntop, 這個函數由調用者提供一個緩沖區保存結果, 可以規避線程安全問題
因此我們上面的InetAddr類就可以改寫了
#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include<string.h>
class InetAddr
{
public://網絡轉主機InetAddr(sockaddr_in &sock): _addr(sock){//_ip=inet_ntoa(_sockaddrin.sin_addr);_port = ntohs(_addr.sin_port);char ipbuffer[64];inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));_ip = ipbuffer;}//主機轉網絡InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port){// 主機轉網絡memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);_addr.sin_port = htons(_port);}InetAddr(uint16_t port) : _port(port), _ip("0"){// 主機轉網絡memset(&_addr, 0, sizeof(_addr));_addr.sin_family = AF_INET;_addr.sin_addr.s_addr = INADDR_ANY;_addr.sin_port = htons(_port);}~InetAddr(){}std::string Ip(){return _ip;}uint16_t Port(){return _port;}bool operator==(const InetAddr &peer){return _ip == peer._ip && _port == peer._port;}std::string StringAddr(){return _ip + ":" + std::to_string(_port);}socklen_t NetAddrLen(){return sizeof(_addr);}const struct sockaddr_in &NetAddr() { return _addr; }private:sockaddr_in _addr;std::string _ip;uint16_t _port;
};