#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);}// 添加监听套接字到epollev.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中得到的信息更加多一些