一、socket 类(socket.hpp)
1. 类设计思想
这是一个 RAII(资源获取即初始化)风格的 socket 封装类,管理 socket 文件描述符的生命周期。
2. 核心特性
构造与析构
socket(int c=-1):m_sockfd(c){} // 构造时传入文件描述符,默认-1表示无效
~socket(){
if(m_sockfd!=-1)
close(m_sockfd); // 析构时自动关闭socket
}
禁用拷贝(重要设计)
socket& operator=(const socket& other)=delete;
socket(const socket& other)=delete;
-
禁用拷贝构造和拷贝赋值,因为 socket 文件描述符不能被两个对象共享
-
防止资源重复释放问题
支持移动语义
socket(socket&& other):m_sockfd(other.m_sockfd){
other.m_sockfd=-1; // 转移所有权后置为无效
}
-
允许资源所有权的转移
-
支持在容器中移动(如 vector、map)
常用方法
int get_fd()const{ return m_sockfd; } // 获取底层文件描述符
bool is_valid()const{ return m_sockfd!=-1; } // 检查是否有效
void close_socket(){ ... } // 手动关闭
二、socket 工具函数(socket_utils.hpp)
1. 创建 socket
int create_sockfd() {
return socket(AF_INET, SOCK_STREAM, 0); // 创建TCP socket
}
int create_nonblocking_sockfd() {
// 一次性设置非阻塞和close-on-exec标志
return socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
}
-
SOCK_NONBLOCK: 非阻塞模式 -
SOCK_CLOEXEC: exec时自动关闭,防止文件描述符泄漏
2. socket 选项设置
非阻塞模式
void set_socket_nonblocking(int sockfd, bool nonblock) {
int flags = fcntl(sockfd, F_GETFL, 0);
if(nonblock)
flags |= O_NONBLOCK;
else
flags &= ~O_NONBLOCK;
fcntl(sockfd, F_SETFL, flags);
}
地址重用(重要)
void set_socket_reuseaddr(int sockfd, bool reuse) {
int optval = reuse ? 1 : 0;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
}
-
SO_REUSEADDR: 允许立即重用处于 TIME_WAIT 状态的端口 -
避免重启服务器时"Address already in use"错误
Linger 选项
void set_socket_linger(int sockfd, bool onoff, int linger_time) {
struct linger so_linger;
so_linger.l_onoff = onoff ? 1 : 0;
so_linger.l_linger = linger_time; // 延迟关闭时间(秒)
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
}
-
控制关闭 socket 时的行为
-
l_onoff=1: 启用延迟关闭,等待数据发送完成或超时
端口重用
void set_socket_reuseport(int sockfd, bool reuse) {
int optval = reuse ? 1 : 0;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
}
-
Linux 3.9+ 特性
-
允许多个进程绑定相同端口,用于负载均衡
3. 地址操作
// 获取本地地址信息
struct sockaddr_in& get_local_addr(int sockfd) {
getsockname(sockfd, (struct sockaddr*)&local_addr, &addrlen);
return local_addr;
}
// 获取对端地址信息
struct sockaddr_in& get_peer_addr(int sockfd) {
getpeername(sockfd, (struct sockaddr*)&peer_addr, &addrlen);
return peer_addr;
}
4. 错误处理
std::string get_socket_error(int sockfd) {
int optval;
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
return std::string(strerror(optval)); // 将错误码转为字符串
}
三、使用示例
// 创建 socket
socket s(wangt_socket_untils::create_nonblocking_sockfd());
// 设置选项
wangt_socket_untils::set_socket_reuseaddr(s.get_fd(), true);
wangt_socket_untils::set_socket_linger(s.get_fd(), true, 5);
// 绑定地址
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
wangt_socket_untils::bind_socket(s.get_fd(),
(struct sockaddr*)&addr, sizeof(addr));
// 监听
wangt_socket_untils::listen_socket(s.get_fd(), 128);
// 移动语义使用
socket s2 = std::move(s); // s 转移给 s2,s 变为无效
整体代码
socket核心函数
#ifndef SOCKET_HPP
#define SOCKET_HPP
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<fcntl.h>
#include<string>
class socket{
int m_sockfd;
public:
socket(int c=-1):m_sockfd(c){}
~socket(){
if(m_sockfd!=-1)
close(m_sockfd);
}
socket& operator=(const socket& other)=delete;
socket(const socket& other)=delete;
socket(socket&& other):m_sockfd(other.m_sockfd){
other.m_sockfd=-1;
}
socket& operator=(socket&& other){
if(this!=&other){
close(m_sockfd);
m_sockfd=other.m_sockfd;
other.m_sockfd=-1;
}
return *this;
}
int get_fd()const{
return m_sockfd;
}
bool is_valid()const{
return m_sockfd!=-1;
}
void close_socket(){
if(m_sockfd!=-1){
close(m_sockfd);
m_sockfd=-1;
}
}
};
#endif
socket工具函数
#include "socket_utils.hpp"
#include <cstring>
namespace wangt_socket_untils{
int create_sockfd()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
return sockfd;
}
int create_nonblocking_sockfd()
{
int sockfd=socket(AF_INET,SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC,0);
return sockfd;
}
void set_socket_nonblocking(int sockfd,bool nonblock)
{
int flags=fcntl(sockfd,F_GETFL,0);
if(nonblock)
flags|=O_NONBLOCK;
else
flags&=~O_NONBLOCK;
fcntl(sockfd,F_SETFL,flags);
}
void set_socket_reuseaddr(int sockfd,bool reuse)
{
int optval=reuse?1:0;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
}
void set_socket_linger(int sockfd,bool onoff,int linger_time)
{
struct linger so_linger;
so_linger.l_onoff=onoff?1:0;
so_linger.l_linger=linger_time;
setsockopt(sockfd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));
}
void set_socket_reuseport(int sockfd,bool reuse)
{
int optval=reuse?1:0;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEPORT,&optval,sizeof(optval));
}
void bind_socket(int sockfd,const struct sockaddr* addr,socklen_t addrlen)
{
bind(sockfd,addr,addrlen);
}
void listen_socket(int sockfd,int backlog)
{
listen(sockfd,backlog);
}
void close_socket(int sockfd)
{
close(sockfd);
}
struct sockaddr_in& get_local_addr(int sockfd)
{
static struct sockaddr_in local_addr;
socklen_t addrlen=sizeof(local_addr);
getsockname(sockfd,(struct sockaddr*)&local_addr,&addrlen);
return local_addr;
}
struct sockaddr_in& get_peer_addr(int sockfd)
{
static struct sockaddr_in peer_addr;
socklen_t addrlen=sizeof(peer_addr);
getpeername(sockfd,(struct sockaddr*)&peer_addr,&addrlen);
return peer_addr;
}
std::string get_socket_error(int sockfd)
{
int optval;
socklen_t optlen=sizeof(optval);
if(getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&optval,&optlen)<0)
{
return std::string("getsockopt error");
}
if(optval!=0)
{
return std::string(strerror(optval));
}
return std::string();
}
}