1、setsockopt()函数
setsockopt 是用于设置套接字(socket)选项的系统调用,允许应用程序对套接字的行为进行更细粒度的控制。它通常用于配置网络通信的参数,例如超时、缓冲区大小、地址复用等。以下是详细的解析
#include
#include
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
sockfd :已经创建的套接字描述符(通过 socket() 返回)。
level :选项所在的协议层,常见的取值:
SOL_SOCKET:通用套接字选项(如 SO_REUSEADDR)。
IPPROTO_TCP:TCP 协议层选项(如 TCP_NODELAY)。
IPPROTO_IP:IP 协议层选项(如 IP_MULTICAST_TTL)。
optname :要设置的选项名称(具体含义依赖 level)。
optval :指向选项值的指针。
optlen :选项值的长度(以字节为单位)。
Level与对应的选项名称:
选项名 | 类型 | 作用 |
---|---|---|
SO_REUSEADDR | int | 端口复用 |
SO_KEEPALIVE | int | 启动保活机制 |
SO_RCVBUF/SO_SNDBUF | int | 设置接收/发送缓冲区大小 |
SO_RCVTIMEO/SO_SNDTIMEO | int | 设置接收/发送超时时间 |
SO_LINGER | struct | 是否优雅关闭 |
列如在该项目中用于设置优雅关闭的代码
if (0 == m_OPT_LINGER)
{
struct linger tmp = {0, 1};
setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
}
else if (1 == m_OPT_LINGER)
{
struct linger tmp = {1, 1};
setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
}
当然TCP以及IP选项也有对应的选项名称,因为项目不涉及,所以这里就不提了,大家有兴趣请自行查略。
多路复用是为了解决高并发场景下 ,主线程或进程不阻塞于某一个具体的IO操作,如我们的电脑既要读取键盘输入也要读取鼠标输入。
常用的多路复用函数有select、poll以及epoll,其中epoll是前两种的优化版本,具有更高的高并发场景下的性能瓶颈,其无需像前两者每次都轮询所有的文件描述符,从而大大减少内核和用户空间的交互开销,是高并发网络编程的基石之一。
常用函数:
struct epoll_event event;//用于储存就绪事件的结构体
//1.创建epoll实例
int epfd = epoll_create1(0); // 创建 epoll 实例
//2.向epoll实例中添加、删除对象
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//op:操作类型
//EPOLL_CTL_ADD:添加监听
//EPOLL_CTL_MOD:修改监听
//EPOLL_CTL_DEL:移除监听
//fd:需要监听的文件描述符
//event:监听的事件类型(如EPOLLIN、EPOLLOUT)和附加数据(EPOLL_data_t)
//3.等待事件发生,返回就绪的
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//events:用于储存就绪的事件
//maxevents:最多返回的事件
//timeout:最多等待的时间
//返回值:返回已就绪时间的个数
事件触发模式:
(1)水平触发(Level Triggered, LT)
(2)边缘触发(Edge Triggered, ET)
仅当状态变化时触发一次 (如从不可读变为可读)。
特点 :性能更高,但需确保一次性处理完所有数据,否则可能遗漏事件。
使用条件 :
文件描述符必须设置为 非阻塞模式 。
需循环读取直到返回 EAGAIN 或 EWOULDBLOCK。
使用注意事项
void WebServer::eventListen()
{
//网络编程基础步骤
m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(m_listenfd >= 0);
//优雅关闭连接
if (0 == m_OPT_LINGER)
{
struct linger tmp = {0, 1};
setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
}
else if (1 == m_OPT_LINGER)
{
struct linger tmp = {1, 1};
setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
}
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(m_port);
int flag = 1;
setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));
assert(ret >= 0);
ret = listen(m_listenfd, 5);
assert(ret >= 0);
utils.init(TIMESLOT);
//epoll创建内核事件表
epoll_event events[MAX_EVENT_NUMBER];
m_epollfd = epoll_create(5);
assert(m_epollfd != -1);
utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);
http_conn::m_epollfd = m_epollfd;
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);
assert(ret != -1);
utils.setnonblocking(m_pipefd[1]);
utils.addfd(m_epollfd, m_pipefd[0], false, 0);
utils.addsig(SIGPIPE, SIG_IGN);
utils.addsig(SIGALRM, utils.sig_handler, false);
utils.addsig(SIGTERM, utils.sig_handler, false);
alarm(TIMESLOT);
//工具类,信号和描述符基础操作
Utils::u_pipefd = m_pipefd;
Utils::u_epollfd = m_epollfd;
}
eventlisten函数的主要作用包含两个,首先是对套接字socket的设置,包括创造、绑定、相关属性设置等;其次,是对epoll多路复用的设置,比如添加事件等,在源码中没有直接使用相关的系统接口,而是对该系统调用进行了包装。
addfd()函数
主要作用就是添加事件到epoll中,主要用到的函数是epoll_ctr()
//将内核事件表注册读事件,ET模式,选择开启EPOLLONESHOT
void Utils::addfd(int epollfd, int fd, bool one_shot, int TRIGMode)
{
epoll_event event;
event.data.fd = fd;
if (1 == TRIGMode)
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
else
event.events = EPOLLIN | EPOLLRDHUP;
if (one_shot)
event.events |= EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking(fd);
}
addsig()函数
主要作用是为信号设置信号处理函数,主要用到的函数时sigaction()
//设置信号函数
void Utils::addsig(int sig, void(handler)(int), bool restart)
{
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = handler;
if (restart)
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
assert(sigaction(sig, &sa, NULL) != -1);
}
这几段代码的逻辑还是比较简单的,大家自行观看