【Linux】Socket编程UDP
一、socket编程相关接口函数(UDP)
需要包含头文件 <sys/socket.h>。
- socket函数
功能:创建通信端点(套接字)。
原型:
int socket(int domain, int type, int protocol);
参数说明:
- domain:指定通信域,常见值:AF_INET:IPv4 协议;AF_INET6:IPv6 协议;AF_UNIX:本地通信(UNIX域)。
- type:定义数据传输方式:SOCK_STREAM:面向连接的可靠传输(如TCP);SOCK_DGRAM:无连接的报文传输(如UDP);SOCK_RAW:原始套接字(直接访问网络层)。
- protocol:通常设为 0,表示自动选择默认协议(如 SOCK_STREAM 默认对应TCP)。
返回子:成功返回套接字描述符;失败返回 -1 并设置 errno。
- bind函数
功能:将套接字(socket)绑定到特定的本地 IP 地址和端口号上。
原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明:
- sockfd:通过 socket 韩硕返回值获得的套接字描述符。
- addr:指向 sockaddr 结构体的指针,包含要绑定的地址信息(IP 和 port)。
- addrlen:addr 结构体的长度,必须真确设置为 sizeof(struct sockaddr_in) 或类似大小。
返回值:成功返回 0;失败返回 -1 并设置 errno。
- recvfrom函数
功能:
- recvfrom用于从无连接套接字(如UDP)接收数据。
- 它能捕获数据内容及发送者的地址(如IP地址和端口号)。
- 适用于需要响应特定发送者的场景,例如聊天服务器或传感器数据采集。
原型:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);参数说明:
- sockfd:套接字描述符。
- buf:指向缓冲区的指针,用于存储接收到的数据。
- len:缓冲区大小(字节数),指定能接受的最大数据长度。
- flags:控制接收行为的标志位,常用值为0(无特殊行为)。
- src_addr:指向 sockaddr 结构体的指针,用于存储发送者的地址信息。
- addrlen:指向 socklen_t 变量的指针,指定 src_addr 缓冲区大小。
返回值: 成功返回接收到的字节数,失败返回 -1,并设置 errno;返回 0 表示连接已关闭。
注意:
- 当调用recvfrom时,系统会阻塞(等待)直到数据到达或超时(取决于套接字设置)。
- 接收到数据后:数据被复制到 buf 缓冲区,src_addr 填充个发送者地址,addrlen 更新为地址的实际长度。
- 如果数据报长度超过 len,多余部分会被丢弃(取决于协议)。
- 在 UDP 中,recvfrom 每次接收一个完整的数据报。
- sendto函数
功能:向指定目标地址发送数据包,无需预先建立连接(无连接模式)。
原型:ssize_t sendto(
int sockfd, // 套接字描述符
const void *buf, // 待发送数据的缓冲区
size_t len, // 数据长度(字节数)
int flags, // 发送标志(通常设为 0)
const struct sockaddr *dest_addr, // 目标地址结构体
socklen_t addrlen // 目标地址结构体长度
);参数说明:
- sockfd:已创建的套接字描述符。
- buf:指向待发送数据的指针。
- len:发送数据的长度。
- flags:控制发送行为的标志位,常用值:0:默认阻塞发送;MSG_DONTWATT:非阻塞发送;MSG_CONFIRM(Linux特有):确认链路有效。
- dest_addr:指向目标地址的结构体指针。
- addrlen:dest_addr 结构体的实际长度,例如:
sizeof(struct sockaddr_in)
。返回值: 成功返回实际发送的字节数,失败返回 -1 并设置 errno。
二、Echo Server
先随手创建一个不可拷贝的基类,之后实现的服务器类直接继承即可不可拷贝。
// nocopy.hpp#pragma onceclass nocopy
{
public:nocopy(){}nocopy(const nocopy&) = delete;nocopy& operator=(const nocopy&) = delete;~nocopy(){}
};
接下来我们将套接字进行简单的封装(后面还有修改):
#pragma once#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class InetAddr
{
public:InetAddr(struct sockaddr_in& addr):_addr(addr){_ip = inet_ntoa(addr.sin_addr); // 最后补充里讲解这个函数_port = ntohs(addr.sin_port);}std::string IP() { return _ip; }int Port() { return _port; }std::string PrintInfo() {std::string info = _ip;info += ":";info += std::to_string(_port); // 127.0.0.1:1234return info;}~InetAddr(){}
private:std::string _ip;int _port;struct sockaddr_in _addr;
};
最后我们使用上面的接口做一个简单的 UDP 服务器。
// Common.hpp#pragma onceenum {Usage_err = 1,Socket_err,Bind_err
};
// UdpServer.hpp#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"const static uint16_t defaultport = 6666;
const static int defaultfd = -1;
const static int defaultsize = 1024;using namespace LogModule;class UdpServer : public nocopy
{
public:UdpServer(const uint16_t port = defaultport):_port(port), _sockfd(defaultfd){}void Init() {_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0) {LOG(LogLevel::ERROR) << "socket err, " << errno << ": " << strerror(errno);exit(Socket_err);}LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;struct sockaddr_in local;memset(&local, 0, sizeof(local)); // 置零local.sin_addr.s_addr = INADDR_ANY; // 0local.sin_port = htons(_port);local.sin_family = AF_INET;int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if(n != 0) {LOG(LogLevel::ERROR) << "bind err, " << errno << ": " << strerror(errno);exit(Bind_err);}LOG(LogLevel::INFO) << "bind success!";}void Start() {char buffer[defaultsize];while(true) {struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, buffer, defaultsize - 1, 0, (struct sockaddr*)&peer, &len);if(n > 0) {InetAddr addr(peer);buffer[n] = 0;std::cout << "[" << addr.PrintInfo() << "]# " << buffer << std::endl;sendto(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&peer, len);}}}~UdpServer(){}
private:uint16_t _port;int _sockfd;
};
- Log.hpp 之前已经写了,这里不复制粘贴了。
- 云服务器不允许直接绑定公有IP,我们也不推荐编写服务器的时候,bind明确的IP,推荐直接写成 INADDR_ANY
/* Address to accept any incoming messages. */
#define INADDR_ANY ((in_addr_t) 0x00000000)
在网络编程中,当一个进程需要绑定一个网络接口以进行通信时,可以使用 INADDR_ANY 作为 IP 地址参数。这样做意味着该接口可以接受任何来自 IP 地址的连接请求,无论是本地主机还是远程主机。例如,服务器有多个网卡(每个网卡上有不同的 IP 地址),使用 INADDR_ANY 可以省去确定数据是从服务器上具体哪个网卡/IP地址上获取的。
// UdpClient.cc#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"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]);exit(Usage_err);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0) {std::cout << "socket error: " << strerror(errno) << std::endl;exit(Socket_err);}// client不需要进行bind吗?一定要!// 但是,不需要显示bind,client会在首次发送数据时自动bind// 为什么?server端的端口号,一定是众所周知,不可改变的,client 需要 port,bind 随机端口// 为什么?client 会非常多// 所以,client需要bind,但不需要显示bind,让本地OS自动随机bind,绑定随机端口struct sockaddr_in server;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_family = AF_INET;server.sin_port = htons(port);while(true) {std::string inbuffer;std::cout << "Please Enter# ";std:getline(std::cin, inbuffer);// 发送消息ssize_t n = sendto(sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&server, sizeof(server));if(n > 0) {// 接收信息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if(n > 0) {buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}else break; // 出错或连接断开,退出}else break;}close(sockfd);return 0;
}
// UdpServer.cc#include <memory>
#include "UdpServer.hpp"int main()
{std::unique_ptr<UdpServer> us_ptr = std::make_unique<UdpServer>();us_ptr->Init();us_ptr->Start();return 0;
}
三、DictServer
下面我们实现一个简单的英译汉的网络字典。
// Dict.txt
hello - 你好
goodbye - 再见
thank you - 谢谢
yes - 是
no - 不
apple - 苹果
water - 水
book - 书
house - 房子
family - 家庭
one - 一
two - 二
ten - 十
hundred - 百
thousand - 千
time - 时间
day - 天/日
week - 周/星期
month - 月
year - 年
teacher - 教师
doctor - 医生
student - 学生
engineer - 工程师
artist - 艺术家
computer - 电脑
internet - 互联网
software - 软件
data - 数据
innovation - 创新
happy - 快乐
sad - 悲伤
healthy - 健康
busy - 忙碌
tired - 疲惫
freedom - 自由
justice - 正义
knowledge - 知识
success - 成功
opportunity - 机会
// Dict.hpp#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>const std::string sep = " - ";
const std::string defaultpath = "./Dict.txt";class Dict
{
public:Dict(const std::string path = defaultpath):_path(path) {LoadDict();}std::string Translate(const std::string& key) {auto it = _dict.find(key);if(it == _dict.end()) return "Unknow";return it->second;}~Dict(){}
private:void LoadDict() {std::ifstream in(_path);if(!in.is_open()) {std::cerr << "dict open error" << std::endl;return;}std::string line;while(std::getline(in, line)) {if(line.empty()) break;auto pos = line.find(sep);if(pos == std::string::npos) continue;std::string key = line.substr(0, pos);std::string value = line.substr(pos + sep.size());_dict[key] = value;}in.close();}
private:std::string _path;std::unordered_map<std::string, std::string> _dict;
};
// DictServer.hpp#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <functional>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"const static uint16_t defaultport = 8888;
const static int defaultfd = -1;
const static int defaultsize = 1024;using func_t = std::function<void(const std::string& req, std::string* resp)>;using namespace LogModule;class DictServer : public nocopy
{
public:DictServer(func_t func, const uint16_t port = defaultport):_port(port), _sockfd(defaultfd), _func(func){}void Init() {_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0) {LOG(LogLevel::ERROR) << "socket err, " << errno << ": " << strerror(errno);exit(Socket_err);}LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;struct sockaddr_in local;memset(&local, 0, sizeof(local)); // 置零local.sin_addr.s_addr = INADDR_ANY; // 0local.sin_port = htons(_port);local.sin_family = AF_INET;int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));if(n != 0) {LOG(LogLevel::ERROR) << "bind err, " << errno << ": " << strerror(errno);exit(Bind_err);}LOG(LogLevel::INFO) << "bind success!";}void Start() {char buffer[defaultsize];while(true) {struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, buffer, defaultsize - 1, 0, (struct sockaddr*)&peer, &len);if(n > 0) {InetAddr addr(peer);buffer[n] = 0;std::cout << "[" << addr.PrintInfo() << "]# " << buffer << std::endl;std::string resp;_func(buffer, &resp);sendto(_sockfd, resp.c_str(), resp.size(), 0, (struct sockaddr*)&peer, len);}}}~DictServer(){}
private:uint16_t _port;int _sockfd;func_t _func;
};
// DictServer.cc#include <memory>
#include "Dict.hpp"
#include "DictServer.hpp"Dict dict;void Func(const std::string& req, std::string* resp) {*resp = dict.Translate(req);
}int main()
{std::unique_ptr<DictServer> ds_ptr = std::make_unique<DictServer>(Func);ds_ptr->Init();ds_ptr->Start();return 0;
}
// DictClient.cc#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"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]);exit(Usage_err);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0) {std::cout << "socket error: " << strerror(errno) << std::endl;exit(Socket_err);}// client不需要进行bind吗?一定要!// 但是,不需要显示bind,client会在首次发送数据时自动bind// 为什么?server端的端口号,一定是众所周知,不可改变的,client 需要 port,bind 随机端口// 为什么?client 会非常多// 所以,client需要bind,但不需要显示bind,让本地OS自动随机bind,绑定随机端口struct sockaddr_in server;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_family = AF_INET;server.sin_port = htons(port);while(true) {std::string inbuffer;std::cout << "Please Enter# ";std:getline(std::cin, inbuffer);// 发送消息ssize_t n = sendto(sockfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&server, sizeof(server));if(n > 0) {// 接收信息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);if(n > 0) {buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}else break; // 出错或连接断开,退出}else break;}close(sockfd);return 0;
}
DictServer 封装版
// Udp_Socket.hpp#pragma once#include <iostream>
#include <string>
#include <stdlib.h>
#include <cassert>
#include <string.h>#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;class UdpSocket
{
public:bool Socket() {_fd = socket(AF_INET, SOCK_DGRAM, 0);if(_fd < 0) {perror("socket");return false;}return true;}void Close() {close(_fd);}bool Bind(const std::string& ip, uint16_t port) {sockaddr_in local;local.sin_addr.s_addr = inet_addr(ip.c_str());local.sin_family = AF_INET;local.sin_port = htons(port);ssize_t n = bind(_fd, (sockaddr*)&local, sizeof(local));if(n < 0) {perror("bind");return false;}return true;}bool Recvfrom(std::string* buf, std::string* ip = nullptr, uint16_t* port = nullptr) {sockaddr_in peer;socklen_t len = sizeof(peer);char tmp[4096];ssize_t n = recvfrom(_fd, tmp, sizeof(tmp) - 1, 0, (sockaddr*)&peer, &len);if(n < 0) {perror("recvfrom");return false;}buf->assign(tmp, n);if(ip) *ip = inet_ntoa(peer.sin_addr);if(port) *port = ntohs(peer.sin_port);return true;}bool Sendto(const std::string& buf, const std::string& ip, uint16_t port) {sockaddr_in addr;addr.sin_addr.s_addr = inet_addr(ip.c_str());addr.sin_family = AF_INET;addr.sin_port = htons(port);ssize_t n = sendto(_fd, buf.c_str(), buf.size(), 0, (sockaddr*)&addr, sizeof(addr));if(n < 0) {perror("sendto");return false;}return true;}
private:int _fd;
};
// Udp_Server.hpp#pragma once#include "Udp_socket.hpp"#include <functional>using func_t = std::function<void(const std::string& req, std::string* resp)>;class UdpServer
{
public:UdpServer() {assert(_sock.Socket());}bool Start(const std::string& ip, uint16_t port, func_t func) {if(_sock.Bind(ip, port) == false) {return false;}while(true) {std::string req;std::string resp;std::string des_ip;uint16_t des_port = 0;if(!_sock.Recvfrom(&req, &des_ip, &des_port)) continue;func(req, &resp);_sock.Sendto(resp, des_ip, des_port);std::cout << "req: " << req << ", resp: " << resp << std::endl;}_sock.Close();return true;}
private:UdpSocket _sock;
};
// Udp_Client.hpp#include "Udp_socket.hpp"class UdpClient
{
public:UdpClient(const std::string& ip, uint16_t port):_ip(ip), _port(port) {assert(_sock.Socket());}bool Recvfrom(std::string* buf) {return _sock.Recvfrom(buf);}bool Sendto(const std::string& buf) {return _sock.Sendto(buf, _ip, _port);}~UdpClient() { _sock.Close(); }
private:UdpSocket _sock;// 服务器的 ip 和 portstd::string _ip;uint16_t _port;
};
// dict_server.cc#include "Udp_Server.hpp"
#include "Dict.hpp"Dict dict;void Func(const std::string& req, std::string* resp) {*resp = dict.Translate(req);
}int main(int argc, char* argv[])
{if(argc != 3) {std::cout << "Usage: " << argv[0] << " server_ip server_port!" << std::endl;return 1;}UdpServer us;us.Start(argv[1], atoi(argv[2]), Func);return 0;
}
// dict_client.cc#include "Udp_Client.hpp"int main(int argc, char* argv[])
{if(argc != 3) {std::cout << "Usage: " << argv[0] << " server_ip server_port!" << std::endl;return 1;}UdpClient client(argv[1], atoi(argv[2]));while(true) {std::string word;std::cout << "请输入英文单词: ";std::cin >> word;if(!std::cin) {std::cout << "Good Bye!" << std::endl;break;}client.Sendto(word);std::string ret;client.Recvfrom(&ret);std::cout << word << "意思是: " << ret << std::endl;}return 0;
}
四、简单聊天室
// Route.hpp#pragma once#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"using namespace LogModule;class Route
{
public:void MessageRoute(int sockfd, const std::string& message, InetAddr& peer) {if(!IsExist(peer)) AddUser(peer);std::string send_message = peer.PrintInfo() + "# " + message;std::cout << send_message << std::endl;for(auto& user : _online_user) {// if(user == peer) continue;sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr*)&(user.GetAddr()), sizeof(user.GetAddr()));}if(message == "QUIT") DeleteUser(peer);}
private:bool IsExist(InetAddr& peer) {for(auto& user : _online_user) {if(user == peer) return true;}return false;}void AddUser(InetAddr& peer) {LOG(LogLevel::INFO) << "聊天室新增一位用户: " << peer.PrintInfo();_online_user.push_back(peer);}void DeleteUser(InetAddr& peer) {for(auto it = _online_user.begin(); it != _online_user.end(); ++it) {if(*it == peer) {LOG(LogLevel::INFO) << "一位用户退出了聊天室: " << peer.PrintInfo();_online_user.erase(it);break;}}}
private:// 用户首次发出消息认为登入std::vector<InetAddr> _online_user; // 在线用户
};
// 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 "Log.hpp"
#include "InetAddr.hpp"using namespace LogModule;using func_t = std::function<void(int sockfd, const std::string& message, InetAddr& peer)>;
const static int defaultsockfd = -1;class UdpServer
{
public:UdpServer(uint16_t port, func_t func):_sockfd(defaultsockfd), _port(port), _func(func), _running(false){}void Init() {_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0) {LOG(LogLevel::ERROR) << "socket error!";exit(1);}LOG(LogLevel::INFO) << "socket success, sockfd: " << _sockfd;struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_addr.s_addr = INADDR_ANY;local.sin_family = AF_INET;local.sin_port = htons(_port);ssize_t n = bind(_sockfd, (const sockaddr*)&local, sizeof(local));if(n < 0) {LOG(LogLevel::ERROR) << "bind error!";exit(2);}LOG(LogLevel::INFO) << "bind success!";}void Start() {_running = true;while(_running) {char buffer[4096];struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);if(n > 0) {buffer[n] = 0;InetAddr client(peer);_func(_sockfd, buffer, client);}}}
private:int _sockfd;uint16_t _port;bool _running;func_t _func;
};
// ServerMain.cc#include "UdpServer.hpp"
#include "Route.hpp"
#include "ThreadPool.hpp"
#include <memory>using task_t = std::function<void()>;int main(int argc, char* argv[])
{if(argc != 2) {std::cout << "Usage: " << argv[0] << " server_port" << std::endl;return 3;}uint16_t port = atoi(argv[1]);std::unique_ptr<Route> route = std::make_unique<Route>();auto tp = ThreadPool<task_t>::GetInstance();std::unique_ptr<UdpServer> us = std::make_unique<UdpServer>(port, [&](int sockfd, const std::string& message, InetAddr& peer){task_t t = std::bind(&Route::MessageRoute, route.get(), sockfd, message, peer);tp->Push(t);});us->Init();us->Start();return 0;
}
// ClientMain.hpp#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include "InetAddr.hpp"int sockfd = -1;
struct sockaddr_in peer;void ClientQuit(int signo) {(void)signo;std::string message = "QUIT";sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr*)&peer, sizeof(peer));exit(0);
}void* Recver(void* arg) {char buffer[4096];while(true) {struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr*)&peer, &len);if(n > 0) {buffer[n] = 0;std::cerr << buffer << std::endl; // 方便查看效果}else {perror("recvfrom");break;}}std::cout << "我退出了!" << std::endl;return nullptr;
}int main(int argc, char* argv[])
{if(argc != 3) {std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;return 1;}std::string ip = argv[1];uint16_t port = atoi(argv[2]);signal(2, ClientQuit);sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0) {std::cerr << "socket error!" << std::endl;return 2;}std::cerr << "socket success, sockfd: " << sockfd << std::endl;bzero(&peer, sizeof(peer));peer.sin_addr.s_addr = inet_addr(ip.c_str());peer.sin_family = AF_INET;peer.sin_port = htons(port);pthread_t tid;pthread_create(&tid, nullptr, Recver, nullptr);std::string online = "我来啦!!!";sendto(sockfd, online.c_str(), online.size(), 0, (const sockaddr*)&peer, sizeof(peer));while(true) {std::cout << "Please Enter: ";std::string message;std::getline(std::cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr*)&peer, sizeof(peer));}close(sockfd);return 0;
}
五、补充内容
地址转换函数
这里只介绍IPv4的socket网络编程,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 函数,是把这个返回结果放到了静态存储区,这个时候不需要我们进行手动释放。
那么问题来了,如果我们多次调用这个函数,会有怎样的效果呢?参考下面代码:
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;char* ptr1 = inet_ntoa(addr1.sin_addr);char* ptr2 = inet_ntoa(addr2.sin_addr);printf("ptr1:%s, ptr2:%s\n", ptr1, ptr2);return 0;
}
因为 inet_ntoa 把结果放到了自己内部一个静态存储区,这样第二次调用时的结果会覆盖第一次的结果。
思考:
- 如果有多个线程调用 inet_ntoa 函数,是否会出现异常情况呢?
- 在 APUE 中,明确提出 inet_ntoa 不是线程安全函数。
- 在多线程环境下,推荐使用 inet_ntop 函数,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。