一、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
先随手创建一个不可拷贝的基类,之后实现的服务器类直接继承即可不可拷贝。
cpp
// nocopy.hpp
#pragma once
class nocopy
{
public:
nocopy(){}
nocopy(const nocopy&) = delete;
nocopy& operator=(const nocopy&) = delete;
~nocopy(){}
};
接下来我们将套接字进行简单的封装(后面还有修改):
cpp
#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:1234
return info;
}
~InetAddr(){}
private:
std::string _ip;
int _port;
struct sockaddr_in _addr;
};
最后我们使用上面的接口做一个简单的 UDP 服务器。
cpp
// Common.hpp
#pragma once
enum {
Usage_err = 1,
Socket_err,
Bind_err
};
cpp
// 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; // 0
local.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地址上获取的。
cpp
// 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;
}
cpp
// 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 - 机会
cpp
// 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;
};
cpp
// 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; // 0
local.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;
};
cpp
// 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;
}
cpp
// 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 封装版
cpp
// 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;
};
cpp
// 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;
};
cpp
// 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 和 port
std::string _ip;
uint16_t _port;
};
cpp
// 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;
}
cpp
// 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;
}


四、简单聊天室
cpp
// 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; // 在线用户
};
cpp
// 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;
};
cpp
// 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;
}
cpp
// 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 函数,是把这个返回结果放到了静态存储区,这个时候不需要我们进行手动释放。
那么问题来了,如果我们多次调用这个函数,会有怎样的效果呢?参考下面代码:
cpp
#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 函数,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。