深入解析:Linux笔记---UDP套接字编程
1. 核心接口
1.1 创建套接字:socket()
用于创建一个套接字(通信端点),返回套接字描述符(类似文件描述符)。
#include
int socket(int domain, int type, int protocol);
- 参数:
- domain:地址族(协议族),如AF_INET(IPv4)、AF_INET6(IPv6)。
- type:套接字类型,如SOCK_STREAM(TCP,面向连接)、SOCK_DGRAM(UDP,无连接)。
- protocol:具体协议(通常为 0,由系统根据前两个参数自动选择,如 TCP 用IPPROTO_TCP,UDP 用IPPROTO_UDP)。
- 返回值:成功返回套接字描述符(非负整数),失败返回-1(需检查errno)。
1.2 绑定地址和端口:bind()
将套接字与特定的 IP 地址和端口号绑定(主要用于服务器端,指定监听的地址)。
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 参数:
- sockfd:socket()返回的套接字描述符。
- addr:指向sockaddr(或sockaddr_in)的指针,包含要绑定的 IP 和端口。
- addrlen:addr结构的长度(如sizeof(struct sockaddr_in))。
- 返回值:成功返回0,失败返回-1。
- 注意:
- 端口号需用htons()转换为网络字节序(大端)。
- IP 地址通常设为INADDR_ANY(表示监听所有本地网卡的 IP,值为0.0.0.0);若绑定特定网卡的IP,则需要使用htonl()转换;若IP为点分十进制字符串,则需要inet_addr()进行转换(自动转换为网络字节序)。
1.3 UDP发送:sendto()
向指定地址的 UDP 套接字发送数据报(无需提前连接)。
#include
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- 参数:sockfd(通信套接字)、buf(数据缓冲区)、len(数据长度)、flags(通常为 0)、dest_addr(目标地址)、addrlen(目标地址长度)。
- 返回值:成功返回发送的字节数,失败返回-1。
1.4 UDP 接收:recvfrom()
接收 UDP 数据报,并获取发送方地址(通过src_addr输出)。
#include
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(来源地址)、addrlen(来源地址的长度)。
- 返回值:成功返回接收的字节数(0表示对方关闭连接),失败返回-1。
1.5 关闭套接字:close() 或 shutdown()
- close():关闭套接字,释放资源,终止所有读写操作:
#include int close(int fd); // fd为套接字描述符 - shutdown():更灵活地关闭连接(可单独关闭读或写):
#include int shutdown(int sockfd, int how);- how:SHUT_RD(关闭读)、SHUT_WR(关闭写)、SHUT_RDWR(关闭读写,同close())。
1.6 辅助函数(地址转换)
- inet_addr():将点分十进制 IP(如"192.168.1.1")转换为网络字节序的 32 位整数:
#include in_addr_t inet_addr(const char *cp); - inet_ntoa:将网络字节序的 32 位整数转换为点分十进制 IP 字符串:
char *inet_ntoa(struct in_addr in); - 字节序转换:htons()(主机→网络,16 位)、htonl()(主机→网络,32 位)、ntohs()(网络→主机,16 位)、ntohl()(网络→主机,32 位)。
2. UDP客户/服务器通信流程

2.1 服务器端
服务器端的通用流程(日志可以忽略):
// (1)面向数据报, UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd 0)
{
buffer[n] = 0;
std::string client_ip = inet_ntoa(client_addr.sin_addr);
in_port_t client_port = ntohs(client_addr.sin_port);
// 对客户端传来的信息进行处理
_func(buffer, client_ip, client_port);
sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&client_addr, client_addr_len);
}
}
// (4)关闭套接字
close(_sockfd);
封装UDP服务端:
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#define BUFFER_SIZE 1024
#define DEFAULT_PROT 8888
#define EXITSIGNAL "exit"
using func_t = std::function;
std::string default_func(const std::string &message, const std::string &ip, in_port_t port)
{
std::cout 0)
{
buffer[n] = 0;
std::string client_ip = inet_ntoa(client_addr.sin_addr);
in_port_t client_port = ntohs(client_addr.sin_port);
std::string ret_message = _func(buffer, client_ip, client_port);
sendto(_sockfd, ret_message.c_str(), ret_message.size(), 0, (struct sockaddr *)&client_addr, client_addr_len);
}
}
}
private:
int _sockfd;
in_port_t _port;
bool isrunning;
func_t _func;
};
2.2 服务端ip与端口号
首先我们在命令行输入[ifconfig],来获取本地网卡的ip地址:

注意,eth0处的ip地址是局域网地址,lo处的地址127.0.0.1 是 IPv4 协议中的环回地址(Loopback Address),专门用于本地主机(当前计算机)内部的网络通信,其核心特点是:数据发送到这个地址后,会直接被本机的网络协议栈接收,不会通过物理网卡发送到外部网络。
除此之外,如果你使用的是一台服务器,它应该还配备了公网ip。
因为我们的服务器端绑定的地址是INADDR_ANY,所以对于客户端来说:
- 位于服务器所在的机器上:三个ip地址皆可访问到服务端。
- 位于同一局域网的不同机器上:可以使用局域网ip和公网ip。
- 位于不同网域需跨网通信:只能使用公网ip。
如果我们绑定的是特定地址,那么就只能通过这个特定地址来访问服务端,可用的范围依然遵循上面的规则。
至于端口号,我们只需要在可用范围[1024 - 65535]内任选即可,但是注意不要和其他进程冲突。
2.3 客户机端
// (1)面向数据报,UDP套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd 0)
{
buffer[n] = 0;
std::cout << "Server Message# " << buffer << std::endl;
}
}
// (4)关闭套接字
close(_sockfd);
封装UDP客户端:
#pragma once
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#define BUFFER_SIZE 1024
#define DEFAULT_PROT 8888
using namespace LogModule;
class UDPClient
{
public:
UDPClient(const std::string &ip, in_port_t port)
: _server_addr_len(sizeof(_server_addr))
{
_server_addr.sin_addr.s_addr = inet_addr(ip.c_str());
_server_addr.sin_family = AF_INET;
_server_addr.sin_port = htons(port);
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd 0)
{
buffer[n] = 0;
std::cout << "Server Message# " << buffer << std::endl;
}
}
}
private:
int _sockfd;
struct sockaddr_in _server_addr;
socklen_t _server_addr_len;
};
2.4 客户端ip与端口号
客户端并不需要显式绑定ip与端口号,在客户端套接字首次发送消息时,操作系统会自动分配端口号并绑定ip地址与端口号。
那为什么服务端需要我们显式绑定呢?这是因为,隐式绑定的ip与端口号是动态的,可能变化的;而服务端需要固定的ip与端口号,方便用户进行访问。
2.5 客户服务器通信演示
// 客户端代码
#include "Client.hpp"
int main(int argc, char* args[])
{
if(argc != 3)
{
std::cerr << "Usage: Server + ip + port" << std::endl;
exit(errno);
}
in_port_t port = std::stoi(args[2]);
UDPClient client(args[1], port);
client.Start();
return 0;
}
// 服务端代码
#include "Server.hpp"
int main(int argc, char* args[])
{
if(argc != 2)
{
std::cerr << "Usage: Server + port" << std::endl;
exit(errno);
}
in_port_t port = std::stoi(args[1]);
UDPServer server(port);
server.Start();
return 0;
}

3. C/S音译中字典程序
即,客户端发送想要翻译的英文单词,服务器端进行翻译并返回中文。
客户端的代码不需要变,就和上面的回声程序保持一致即可,我们对服务器端进行如下调整:
3.1 Server.cpp
#include "Server.hpp"
#include "Dictionary.hpp"
int main(int argc, char* args[])
{
if(argc != 2)
{
std::cerr << "Usage: Server + port" << std::endl;
exit(errno);
}
Dictionary dict;
dict.Load();
in_port_t port = std::stoi(args[1]);
UDPServer server(port, [&dict](const std::string& message, const InetAddr& client){
return dict.Check(message);
});
server.Start();
return 0;
}
3.2 Server.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
#include "InetAddr.hpp"
#define BUFFER_SIZE 1024
#define DEFAULT_PROT 8888
#define EXITSIGNAL "exit"
using func_t = std::function;
std::string default_func(const std::string &message, const InetAddr& client)
{
std::cout 0)
{
buffer[n] = 0;
InetAddr client(client_addr);
std::string ret_message = _func(buffer, client);
sendto(_sockfd, ret_message.c_str(), ret_message.size(), 0, (struct sockaddr *)&client_addr, client_addr_len);
}
}
}
private:
int _sockfd;
in_port_t _port;
bool isrunning;
func_t _func;
};
3.3 InetAddr.hpp
#pragma once
#include
#include
#include
#include
#include "Log.hpp"
using namespace LogModule;
class InetAddr
{
public:
InetAddr(const struct sockaddr_in& addr)
{
_ip = inet_ntoa(addr.sin_addr);
_port = ntohs(addr.sin_port);
}
std::string Ip() const {return _ip;}
in_port_t Port() const {return _port;}
private:
std::string _ip;
in_port_t _port;
};
3.4 Dictionary.hpp
#pragma once
#include
#include
#include
#include
#include "Log.hpp"
const std::string esp = ": ";
const std::string default_path = "./dictionary.txt";
const std::string default_ret = "未知单词";
using namespace LogModule;
class Dictionary
{
public:
bool Load(const std::string& path = default_path)
{
std::ifstream dict_file(path);
if(!dict_file.is_open())
{
LOG(LogLevel::ERROR) _dict;
};
3.5 dictionary.txt
apple: 苹果
hello: 你好
shishen: 佐助
winter: 冬天
summer: 夏天
hate: 恨
banana: 香蕉
orange: 橘子
god: 神
car: 车
cup: 杯子
key: 钥匙


浙公网安备 33010602011771号