#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024
#define PORT 8888
// 设置非阻塞套接字
void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main() {
int listen_sock, epoll_fd;
struct epoll_event ev, events[MAX_EVENTS];
// 创建监听套接字
if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置地址重用
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
if (bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
close(listen_sock);
exit(EXIT_FAILURE);
}
if (listen(listen_sock, SOMAXCONN) == -1) {
perror("listen");
close(listen_sock);
exit(EXIT_FAILURE);
}
// 创建epoll实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
close(listen_sock);
exit(EXIT_FAILURE);
}
// 添加监听套接字到epoll
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl");
close(listen_sock);
close(epoll_fd);
exit(EXIT_FAILURE);
}
printf("Server started on port %d\n", PORT);
while (1) {
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (int i = 0; i < nfds; ++i) {
// 处理新连接
if (events[i].data.fd == listen_sock) {
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(client_addr);
int conn_sock = accept(listen_sock,
(struct sockaddr*)&client_addr,
&addrlen);
if (conn_sock == -1) {
perror("accept");
continue;
}
set_nonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET; // 边缘触发模式
ev.data.fd = conn_sock;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
perror("epoll_ctl");
close(conn_sock);
}
printf("New connection: %d\n", conn_sock);
}
// 处理客户端数据
else {
char buffer[BUFFER_SIZE];
ssize_t bytes_read;
while ((bytes_read = read(events[i].data.fd, buffer, BUFFER_SIZE)) > 0) {
printf("Received %zd bytes from %d\n", bytes_read, events[i].data.fd);
write(events[i].data.fd, buffer, bytes_read); // 回显数据
}
if (bytes_read == 0 || (bytes_read == -1 && errno != EAGAIN)) {
printf("Connection closed: %d\n", events[i].data.fd);
close(events[i].data.fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
}
}
}
}
close(listen_sock);
close(epoll_fd);
return 0;
}
这里主要简单实现了一下TCP的链接,我这里链接的端口是8888,我采用两个客户端去链接这个服务器。
查看这个端口我们可以发现这个程序的进程是389853,采用的IPV4协议,还能看到FD的编号。可以看到这里有一个sockfd专门是用来监听的。还有两个fd是可以看到状态时ESTABLISHED建立链接的。
再上一步中我看得到了8888端口的DIP进程号,通过TOP -p PID查看资源使用情况。主要看VIRT表示虚拟内存,RES表示物理内存,SHR表示贡献内存,此时CPU的占用率为0,CPU累计时间也为0.进程的名称为epoll_server。
虚拟内存(VIRT)组成
- 包含进程所有地址空间:代码段、数据段、堆、栈、共享库、内存映射文件
+-------------------+-------+
| 内存区域 | 大小 |
+-------------------+-------+
| 程序代码段 | ~500K |
| 共享库映射 | ~1408K| <-- SHR列的值
| 堆栈预分配空间 | ~800K |
+-------------------+-------+
物理内存(RES)组成仅统计实际装入RAM的页帧:
+-------------------+-------+
| 内存区域 | 大小 |
+-------------------+-------+
| 共享库已加载部分 | ~1408K|
| 程序私有内存 | ~0K | <-- RES-SHR=0
+-------------------+-------+
这里可以发现实际上虚拟内存比实际内存多了堆区的预分配空间,和程序代码段。
PS:堆空间预分配:通过 malloc() 申请的虚拟内存不会立即转为物理内存,直到实际写入数据
netstat得到的信息并不多,lsof -i :8888中得到的信息更加多一些