(9)下:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同。小例子的实验
(4)本实验代码的蓝本,是伊圣雨老师里的课本里的代码,略加改动而来的。
++以下是 服务器端的代码:
++ 每当收到客户端的报文时,就测试一下对应的 epoll 事件里的事件标志,不读取报文内容,所以设置为 ET 边缘触发模式。
++ 对应的代码版本 :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h> //增加这俩头文件
#include <errno.h>
#define EPOLL_SIZE 50
int main(int argc,char * argv[])
{ //验证 EPOLLHUP 等标志的服务器端, argc = 2
int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t adr_sz;
struct epoll_event event, * ep_events;
if(argc != 2) { printf("参数不是2个\n");exit(1); }
serv_sock = socket(PF_INET,SOCK_STREAM,0);
printf("创建了监听套接字,描述符为: %d\n",serv_sock);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET; // 协议
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
serv_adr.sin_port = htons(atoi(argv[1])); //端口号
bind( serv_sock,
(struct sockaddr *)&serv_adr, sizeof(serv_adr) ) ;
listen(serv_sock,5);
epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
event.events = EPOLLIN; // 监听套接字仍为水平触发模式
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
while (1) // 此循环在正常情况下是不会退出的。
{ event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(-1 == event_cnt) { break; } // 出错则结束循环,进程退出
puts("从 epoll_wait() 返回了");//统计epoll_wait()的返回次数
for(i = 0 ; i < event_cnt ; i++) //依次处理所有发生了事件的套接字
{ if(ep_events[i].data.fd == serv_sock)//监听套接字
{ adr_sz = sizeof(clnt_adr);
clnt_sock = accept( serv_sock,
(struct sockaddr *)&clnt_adr,&adr_sz );
event.events = EPOLLIN | EPOLLHUP |EPOLLRDHUP | EPOLLET ;
// 通信套接字用边缘触发是因为不准备读取报文
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("创建了通信套接字 id: %d\n", clnt_sock);
} else { // 依次测试通讯套接字上有事件时具有的 epoll标志。
uint32_t revents = ep_events[i].events;
int fd = ep_events[i].data.fd;
if( revents & EPOLLIN )
printf("通信套接字 %d 上有 EPOLLIN 事件\n", fd);
if( revents & EPOLLHUP )
printf("通信套接字 %d 上有 EPOLLHUP 事件\n", fd);
if( revents & EPOLLRDHUP )
printf("通信套接字 %d 上有 EPOLLRDHUP 事件\n", fd);
}
} // for(...)
} // while(...)
return 0;
}
(5) 接着给出客户端的版本,这是一个 linux 版本的客户端,很简单的小程序:
++ 记录其源代码版本:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(int argc,char * argv[])
{ // 回声客户端,三个参数,argc = 3
int sock, str_len;
struct sockaddr_in serv_adr;
if(argc != 3) { printf("参数不是3个\n");exit(1); }
sock = socket(PF_INET,SOCK_STREAM,0);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
// serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
// inet_addr() 的语义不明,不好
// 处理文本地址,只需使用 inet_pton() 与 inet_ntop() 即可。
// int inet_pton(int af, const char *src, void *dst);
inet_pton(AF_INET, argv[1], &serv_adr.sin_addr.s_addr);
serv_adr.sin_port = htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) != -1)
puts("客户端套接字连接至服务器成功\n");
sleep(60); // 延迟 60s 以观察实验结果
close(sock); // 此行可注释,以验证 四次握手与 RST 报文的出现时机
exit(0);
}
(6)后来实验中发现,客户端采用 linux 版本,会导致 wireshark 无法抓包,只好再编写 windows 版本的客户端,源代码如下:
++ 代码版:
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma warning(disable : 4996) // 禁用关于 inet_addr 的过时警告
int main()
{
WSADATA m_wsadata; // 在 windows平台使用 socket前,须做一下初始化,
WSAStartup(0x0202, &m_wsadata); // 最后用 WSACleanup() 释放库。
SOCKET sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN server_in;
memset(&server_in, 0, sizeof(SOCKADDR_IN)); //连接服务器
server_in.sin_family = AF_INET;
server_in.sin_port = htons(9000); // 连接至 80 端口
server_in.sin_addr.s_addr = inet_addr("192.168.1.126");
connect(sClient, (struct sockaddr*)&server_in, sizeof(SOCKADDR_IN));
for (int i = 0; i < 1000000000; i++); // 延时一下
closesocket(sClient); // 关闭套接字的函数
WSACleanup(); // 用这个函数关闭库
return 0;
}
++ 使用 windows 的套接字,还要设置一下 vs2019 ,要不然上面的代码会找不到外部函数。
++ 以及:
(7) 以下给出实验结果,配合 wireshark 抓包:
(8)
谢谢