一 wirkeshark 抓包工具
1.1 软件介绍
wireshark用于抓取经过我当前主机网卡的所有的数据包并且会自动分析数据包
网络管理员使用wireshark来检测网络问题
网络安全工程师使用wireshark来检查资讯安全相关的问题
开发者使用wireshark来为新的通讯协定除错。
普通使用者使用wireshark来学习网络协议的相关知识。
1.2 软件安装
安装此工具,一路下一步即可,有选择插件usbpcap需要打勾安装一下。
1.3 wireshark工具的使用
第一步:使用管理员权限打开软件
第二步 选择合适的网卡
或者在菜单栏中选择“捕获”,点击“选项”,选择适当的网卡
第三步 查看数据包信息
增加过滤条件
1.4 TCP三次握手和四次挥手
Tcp三次握手主要指的是TCP的连接过程
三次握手主要是在客户端的connect和服务器的listen,accpet函数之间完成的
TCP的四次挥手主要指的是TCP的断开连接的过程
四次挥手主要是在客户端服务器退出或者关闭文件描述符的时候完成的
二 TCP循环服务器
2.1 IO多路复用
当一个代码中有多个阻塞函数的时候,因为代码默认都有先后执行顺序,所以无法做到每一个阻塞函数独立执行,相互没有影响,如何解决这个问题?
如果按照默认阻塞形式,无法解决
如果设置为非阻塞,每一个函数都轮询查看缓冲区是否有数据,可以解决这个问题,但是轮询比较占cpu资源,所以不推荐
如果使用多线程或者多进程,需要考虑资源释放的问题,也不推荐
相对比较号的方式是使用IO多路复用
IO多路复用的思想是:
先构造一张有关描述符的表,保存要操作的描述符
然后调用一个函数,阻塞等待文件描述符准备就绪,
当有文件描述符准备就绪,则函数立即返回,执行相应的IO操作。
2.2 使用select实现IO多路复用
头文件:#include <sys/time.h> #include <sys/types.h> #include <unistd.h> 原型:int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 功能:允许一个程序操作多个文件描述符,阻塞等待文件描述符,准备就绪,如果有文件描述符准备就绪,函数立即返回,执行相应的IO操作。 参数: nfds:最大的文件描述符+1 readfds:保存读操作文件描述符的集合 writefds:保存写操作文件描述符的集合 exceptfds:操作其它或者异常的文件描述符的集合 timeout:超时 null:阻塞 返回值: 成功:准备就绪的文件描述符的个数 失败:返回-1 //将文件描述符fd从集合set中移除 void FD_CLR(int fd, fd_set *set); //判断文件描述符是否在集合set中 int FD_ISSET(int fd, fd_set *set); //将文件描述符fd添加到集合set中 void FD_SET(int fd, fd_set *set); //清空集合set void FD_ZERO(fd_set *set); 返回值: 存在:1 不存在:0
#include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <errno.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char const *argv[]) { //创建套接字 int sockfd = socket(AF_INET,SOCK_STREAM,0); //IPV4 流式套接字 具体协议类型 if(-1 == sockfd) { perror("socket"); return -1; } int opt = 1; setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置地址可以被重复绑定 struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("192.168.98.145"); //127.0.0.1回环ip,表示本机,测试时可以使用 server_addr.sin_port = 8888; //绑定信息 int ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)); if(ret == -1) { perror("bind"); return -1; } //设置监听队列 ret = listen(sockfd,10); if(ret == -1) { perror("listen"); return -1; } fd_set readfd,tmpfd; //定义集合 FD_ZERO(&readfd); //清空集合 FD_SET(sockfd,&readfd); //添加到集合 int maxfd = sockfd; int fd[1024] = {0},i =0; struct sockaddr_in client_addr; //用于保存客户端的信息 int length = sizeof(client_addr); char buf[32] = {0}; while(1) //循环服务器 { tmpfd = readfd; ret = select(maxfd + 1,&tmpfd,NULL,NULL,NULL); //监听集合是否可读,最后一个NULL表示阻塞 if(ret == -1) { perror("select"); return -1; } //如果有文件描述符可读 if(FD_ISSET(sockfd,&tmpfd)) //判断sockfd是否还留在集合里面,判断是否有客户端发起连接 { for(i = 0; i < 1024;i++) //选择合适的fd[i] { if(fd[i] == 0) { break; } } fd[i] = accept(sockfd,(struct sockaddr *)&client_addr,&length); if(-1 == fd[i]) { perror("accept"); return -1; } printf("接收到来自%s的客户端的连接fd = %d\n",inet_ntoa(client_addr.sin_addr),fd[i]); FD_SET(fd[i],&readfd); //将新的文件描述符加入到集合中 if(maxfd < fd[i]) { maxfd = fd[i]; } } else //有客户端发消息 { for(i = 0 ; i < 1024;i++) { if(FD_ISSET(fd[i],&tmpfd)) //判断哪个fd可读 { ret = recv(fd[i],buf,sizeof(buf),0); if(ret == -1) { perror("recv"); } else if(ret == 0) { close(fd[i]); //关闭TCP连接 FD_CLR(fd[i],&readfd); printf("客户端%d下线!\n",fd[i]); fd[i] = 0; } else { printf("收到%d客户端的消息%s\n",fd[i],buf); } memset(buf,0,sizeof(buf)); break; } } } } return 0; }
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <stdio.h> #include <errno.h> #include <string.h> #include<arpa/inet.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char const *argv[]) { //创建套接字 int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd == -1) { perror("socket"); return -1; } //向服务器发起连接 struct sockaddr_in server_addr; //保存服务器的信息 memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = 8888; server_addr.sin_addr.s_addr = inet_addr("192.168.98.145"); int ret = connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)); if(-1 == ret) { perror("connect"); return -1; } char buf[32] = {0}; while(1) { scanf("%s",buf); ret = send(sockfd,buf,strlen(buf),0); if(-1 == ret) { perror("send"); return -1; } if(strcmp(buf,"bye") == 0) { break; } memset(buf,0,sizeof(buf)); } close(sockfd); return 0; }
2.3 epoll
#include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <errno.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAXSIZE 256 int main(int argc, char const *argv[]) { //创建套接字 int sockfd = socket(AF_INET,SOCK_STREAM,0); //IPV4 流式套接字 具体协议类型 if(-1 == sockfd) { perror("socket"); return -1; } int opt = 1; setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置地址可以被重复绑定 struct sockaddr_in server_addr; memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("192.168.98.145"); //127.0.0.1回环ip,表示本机,测试时可以使用 server_addr.sin_port = 8888; //绑定信息 int ret = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr)); if(ret == -1) { perror("bind"); return -1; } //设置监听队列 ret = listen(sockfd,10); if(ret == -1) { perror("listen"); return -1; } //创建epoll对象 int epfd = epoll_create(MAXSIZE); if(-1 == epfd) { perror("epoll_create"); return -1; } struct epoll_event ev,events[MAXSIZE] = {0}; ev.data.fd = sockfd; //设置监听socket可读 ev.events = EPOLLIN; //将所有需要监听的socket添加到epfd中 ret = epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev); if(-1 == ret) { perror("epoll_ctl"); return -1; } int i; struct sockaddr_in client_addr; int length = sizeof(client_addr); char buf[32] = {0}; while(1) { int num = epoll_wait(epfd,events,MAXSIZE,-1); // -1表示阻塞 if(-1 == num) { perror("epoll_wait"); return -1; } for(i = 0; i < num;i++) { if(events[i].data.fd == sockfd) //有客户端发起连接 { int fd = accept(sockfd,(struct sockaddr *)&client_addr,&length); if(-1 == fd) { perror("accept"); return -1; } printf("接收来自%s的连接fd = %d\n",inet_ntoa(client_addr.sin_addr),fd); //为新的文件描述符注册事件 ev.data.fd = fd; ev.events = EPOLLIN; ret = epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev); if(-1 == ret) { perror("epoll_ctl"); return -1; } } else //客户端发消息 { if(events[i].events & EPOLLIN) //如果事件是可读的 { ret = recv(events[i].data.fd,buf,sizeof(buf),0); if(ret == -1) { perror("recv"); } else if(ret == 0) //客户端退出 ,注销事件 { printf("客户端%d下线!\n",events[i].data.fd); ev.data.fd = events[i].data.fd; ev.events = EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&ev); close(events[i].data.fd); } else { printf("收到 %d客户端的消息 %s\n",events[i].data.fd,buf); } } } } } return 0; }