概述
简单服务器
参考链接:https://github.com/aceld/libevent/blob/master/26-%E7%AE%80%E5%8D%95%E7%9A%84epoll%E6%9C%8D%E5%8A%A1%E5%99%A8.md
编译及执行操作。
#编译及运行
gcc server.c -o server
./server
代码带有注释,参考注释即可。
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define SERVER_PORT (8080)
#define EPOLL_MAX_NUM (2048)
#define BUFFER_MAX_LEN (4096)
char buffer[BUFFER_MAX_LEN];
void str_toupper(char *str)
{
int i;
for (i = 0; i < strlen(str); i ++) {
str[i] = toupper(str[i]);
}
}
int main(int argc, char **argv)
{
int listen_fd = 0;
int client_fd = 0;
/*
struct sockaddr_in
{
sa_family_t sin_ family; // 地址族
uint16_t sin_port; // 16位TCP/UDP端口号
struct in_addr sin_addr; // 32位IP地址
char sin_zero[8]; // 不使用
}
该结构体在#include<arpa/inet.h>中定义,主要解决sockaddr的缺陷,把
端口号和地址分开存储
*/
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
/*
typedef int socketlen_t
主要用于作为accept()的传参,记录struct sockaddr_un的长度
*/
socklen_t client_len;
int epfd = 0;
/*
typedef union epoll_data
{
void* ptr;
int fd;
unit32_t u32;
unit64_t u64
} epoll_data_t;
struct epoll_event
{
uint32_t events; // 明确表示事件类型,为EPOLLIN或EPOLLOUT等
epoll_data_t data; // 传参,比如传输socket句柄或者线程间通信
}
参考链接: https://www.dazhuanlan.com/2019/12/09/5dedf96a76470/
*/
struct epoll_event event, *my_events;
/*
int // sockfd是socket描述符,唯一标识一个socket
socket(
int protofamily, // 协议族,有AF_INET(即IPV4),AF_INET6(即IPV6),AF_LOCAL,AF_ROUTE等
int type, // 指定socket类型,有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
int protocol // 协议类型,有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等
)
注意,type和protocol并不是可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。
当protocol为0时,会自动选择type类型对应的默认协议
*/
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
/*
long htonl(long value)
{
return ((value <<24 )|((value<<8)&0x00FF0000)|((value>>8)&0x0000FF00)|(value>>24));
}
该函数将小端转换成大端,把一个32位数从主机字节顺序转换成网络字节顺序
*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*
uint16_t htons(uint16_t hostshort)
把机器上的整数转换成“网络字节序”, 网络字节序是大端,也就是整数的高位字节存放在内存的低地址处
*/
server_addr.sin_port = htons(SERVER_PORT);
/*
int // 0-->成功,-1-->失败
bind(
int sockfd, // socket描述符
const struct sockaddr *addr, // 指向要绑定的协议地址
socklen_t addrlen // 对应的地址长度
)
*/
bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
/*
int // 0-->成功,-1-->失败
listen(
int sockfd, // socket描述符
int backlog // 维护一个以该值作为上限的队列以跟踪完成的连接但服务器进程还没有接手处理或正在进行的连接
)
*/
listen(listen_fd, 10);
/*
int // epoll文件描述符
epoll_create (
int __size // 申请内核空间,表示在该epoll文件描述符下可以关注的最大socketfd数目
)
*/
epfd = epoll_create(EPOLL_MAX_NUM);
if (epfd < 0) {
perror("epoll create");
goto END;
}
/*
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
*/
event.events = EPOLLIN;
event.data.fd = listen_fd;
/*
#define EPOLL_CTL_ADD 1 // Add a file descriptor to the interface.
#define EPOLL_CTL_DEL 2 // Remove a file descriptor from the interface.
#define EPOLL_CTL_MOD 3 // Change file descriptor epoll_event structure.
int
epoll_ctl(
int __epfd, // 由epoll_create生成的epoll专用的文件描述符
int __op, // 进行的操作例如EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL
int __fd, // 关联的socket文件描述符
struct epoll_event *__event // 记录监听的数据或者事件等等
)
*/
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event) < 0) {
perror("epoll ctl add listen_fd ");
goto END;
}
my_events = malloc(sizeof(struct epoll_event) * EPOLL_MAX_NUM);
while (1) {
/*
int // 返回需要处理的事件数目,返回0表示已超时
epoll_wait(
int epfd, // epoll描述符
struct epoll_event * events, // 返回发生的事件
int maxevents, // 一次返回的最大事件数目,通常与预分配的事件数组大小相等
int timeout // 没有检测到事件发生时最多等待毫秒时间,若为0则立刻返回,若为-1则一直等待
)
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则
需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,
因为socket fd并未清空,只是事件类型清空。这一步非常重要。
*/
int active_fds_cnt = epoll_wait(epfd, my_events, EPOLL_MAX_NUM, -1);
for (int i = 0; i < active_fds_cnt; i++) {
// if fd == listen_fd
if (my_events[i].data.fd == listen_fd) {
/*
int // 返回客户端的socket描述符,包含其ip和port信息
accept(
int sockfd, // socket描述符
struct sockaddr *addr, // 客户端地址
socklen_t *addrlen // 记录客户端地址长度
)
*/
client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
char ip[20];
/*
htonl()-->"Host to Network Long"
ntohl()-->"Network to Host Long"
htons()-->"Host to Network Short"
ntohs()-->"Network to Host Short"
const char* // 点分十进制ip地址字符串
inet_ntop(
int family, // AF_INET(ipv4)或AF_INET6(ipv6)
const void *addrptr, // 数值格式的地址
char *strptr, // 字符串指针
size_t len // 字符串长度,以免溢出缓冲区。若不足以容纳结果,则返回空指针
)
将数值格式转化为点分十进制的ip地址格式
uint16_t
ntohs(
uint16_t netshort
)
将一个无符号短整形数从网络字节顺序转换为主机字节顺序
*/
printf("new connection[%s:%d]n", inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port));
event.events = EPOLLIN | EPOLLET;
event.data.fd = client_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event);
}
else if (my_events[i].events & EPOLLIN) {
printf("EPOLLINn");
client_fd = my_events[i].data.fd;
buffer[0] = '