网络编程_5(超时检测+UNIX域套接字+抓包工具+包头分析)

本文围绕网络编程展开,介绍了超时检测方法,如用select、setsockopt和alarm设置超时。阐述了仅用于本机通信的域套接字。重点讲解抓包工具Wireshark,包括概念、安装、抓包操作、显示过滤规则等。还对以太网头、IP头、UDP头、TCP头进行分析,介绍三次握手和四次挥手原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一二章请点击:网络编程_1(网络基础+跨主机传输)
三四章请点击:网络编程_2(网络属性+UDP(UDP模型+广播组播))
第五章请点击:网络编程_3(TCP)
第六章请点击:网络编程_4(IO模型)

七、超时检测

1.在网络通讯中,有很多函数是阻塞函数,使进程阻塞。例如 accpet recv send recvfrom sendto等等。
2.为了避免进程在阻塞函数处无限制阻塞,可以设定超时时间,当超时后从阻塞函数立即返回,继续运行。

超时检测方法

1.select 可以通过函数设置超时时间 struct timeval;

struct timeval val= {5, 0};
ret = select(fd+1, &temp, NULL, NULL, &val);

2.通过setsockopt设置超时时间;

struct timeval val = {5, 0};
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &val, sizeof(val)); 	//设置接收超时时间;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &val, sizeof(val)); 	//设置发送超时时间;

设置超时时间后,recv函数和send函数会返 -1 , errno = EAGAIN or EWOULDBLOCK
3.使用alarm(3)定时器设置超时时间;

signal(SIGALRM, handler);
void handler(int sig)
{
	printf("超时\n");
}

八、域套接字

仅用于本机间的通信,bsp-lcd中的s类型

1.socket

头文件:      
	   #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int socket(int domain, int type, int protocol);
	   fd = socket(AF_UNIX, SOCK_STREAM, 0);
	   fd = socket(AF_UNIX, SOCK_DGRAM, 0);
//struct sockaddr_in 	ipv4 AF_INET的结构体
AF_UNIX:(man 7 unix)
           #define UNIX_PATH_MAX    108
           struct sockaddr_un {
               sa_family_t sun_family;               /* AF_UNIX */
               char        sun_path[UNIX_PATH_MAX];  /* pathname */ 	套接字的文件路径(绝对路径)/home/linux/sock
    注意:在服务器中,这个套接字的路径必须事先不存在,需要有bind函数产生(该文件必须由服务器产生);
    如果事先存在该路径文件,需要先删除该文件。
           };

2.access

功能:判断文件是否存在以及文件是否具有某些权限;
头文件:
       #include <unistd.h>
原型:
       int access(const char *pathname, int mode);
参数:
    const char *pathname:指定要判断的文件路径+文件名;
	int mode:
			R_OK:判断文件是否具有可读权限
        	 W_OK:判断文件是否具有可写权限
        	 X_OK:判断文件是否具有可执行权限 
        ------前三种,可以用按位或| 连接 ————————
        	 F_OK:判断文件是否存在;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

3.unlink

功能:删除文件;
头文件:
       #include <unistd.h>
原型:
       int unlink(const char *pathname);
参数:
    char *pathname:指定要删除的文件;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;

例子

客户端和服务器两边创建套接字时指定为UNIX编程即可,若选择SOCK_STREAM则其他同TCP,若选择SOCK_D则其他同UDP

服务器

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/un.h>
#include <unistd.h>
#define PATH "/home/linux/mydir/7_20101_socket/7_unix/unix_sock"
void cli_info(int fd, struct sockaddr_in cin);
int updatefds(int maxfd, fd_set readfds);
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    //允许本地端口快速重用
    int reuse = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    //判断路径是否存在
    //如果存在则删除文件
    if(!access(PATH, F_OK))
    {
        fprintf(stderr, "文件已经存在\n");
        unlink(PATH);
        printf("文件已经删除\n");
    }
    //2.bind
    //填充服务器信息
    struct sockaddr_un sun;
    sun.sun_family = AF_UNIX;
    strcpy(sun.sun_path , PATH);
    if(bind(sfd, (void*)&sun, sizeof(sun))<0)
    {
        perror("bind");
        exit(1);
    }
    //3.监听
    if(listen(sfd, 3)<0)
    {
        perror("listen");
        exit(1);
    }
    //创建集合
    fd_set readfds, temp;
    FD_ZERO(&readfds);
    FD_ZERO(&temp);
    FD_SET(sfd, &readfds);  //监听套接字
    FD_SET(0, &readfds);    //键盘事件
    int ret = -1;
    char buf[BUFSIZ] = "";
    struct timeval tv = {5, 0};
    int maxfd = sfd;
    while(1)
    {
        tv.tv_sec = 5;
        temp = readfds;
        ret = select(maxfd+1, &temp, NULL, NULL, NULL);
        if(ret < 0)
        {
            perror("select");
            exit(1);
        }
        else if(0 == ret)
        {
            fprintf(stderr, "time out\n");
            continue;
        }
        int i = 0;
        for(i=0; i < maxfd+1; i++)
        {
            if(!FD_ISSET(i, &temp))
            {
                continue;
            }
            if(0 == i)
            {
                //键盘事件
                bzero(buf, sizeof(buf));
                int sendfd = -1;
                int ret = scanf("%d %s", &sendfd, buf);
                while(getchar()!='\n');
                if(ret != 2)
                {
                    fprintf(stderr, "输入错误: fd+string\n");
                    continue;
                }
                if(!FD_ISSET(sendfd, &readfds))
                {
                    fprintf(stderr, "fd=%d不在集合中\n", sendfd);
                    continue;
                }
                do
                {
                    ret = send(sendfd, buf, strlen(buf), 0);
                }while(ret<0 && EINTR == errno);
                if(ret < 0)
                {
                    perror("send");
                    close(sendfd);
                    FD_CLR(sendfd, &readfds);
                    maxfd = updatefds(maxfd, readfds);
                }
                printf("发送成功\n");
            }
            else if(sfd == i)
            {
                //客户端连接事件
                int newfd = accept(sfd, NULL, NULL);
                if(newfd < 0)
                {
                    perror("accept");
                    exit(1);
                }
                printf("fd=%d客户端连接\n", newfd);
                //将newfd添加到readfds中
                FD_SET(newfd, &readfds);
                maxfd = maxfd>newfd?maxfd:newfd;
            }
            else
            {
                //客户端交互事件
                bzero(buf, sizeof(buf));
                do
                {
                    ret = recv(i, buf, sizeof(buf), 0);
                }while(ret<0 && EINTR==errno);
                if(ret < 0)
                {
                    perror("recv");
                    FD_CLR(i, &readfds);
                    close(i);

                    //更新maxfd
                    maxfd = updatefds(maxfd, readfds);
                }
                else if(0 == ret)
                {
                    FD_CLR(i, &readfds);
                    close(i);
                    printf("fd=%d 已断开链接\n", i);
                    //更新maxfd
                    maxfd = updatefds(maxfd, readfds);
                }
                else
                {
                    printf("收到来自%d的消息:%s\n", i, buf);
                }
            }
        }
    }
    close(sfd);
    return 0;
}
int updatefds(int maxfd, fd_set readfds)
{
    int i = 0;                                                               
    for(i=maxfd; i>2; i--)
    {
        if(FD_ISSET(i, &readfds))
        {
            return i;
        }
    }
    return -1;
}

客户端

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/un.h>
#include <unistd.h>
#define PATH "/home/linux/mydir/7_20101_socket/7_unix/unix_sock"
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
    //允许本地端口快速重用
    int reuse = 1;
    int len = sizeof(reuse);
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, len)<0)
    {
        perror("setsockopt");
        exit(1);
    }
    //2.绑定 非必须
    //3.连接服务器
    //填充服务器信
    struct sockaddr_un sun;
    sun.sun_family = AF_UNIX;
    strcpy(sun.sun_path, PATH);
    //int connect(int sockfd, \
    //const struct sockaddr *addr,  socklen_t addrlen);
    if(connect(fd, (void*)&sun, sizeof(sun))<0)
    {
        perror("connect");
        exit(1);
    }
    printf("连接成功\n");
    //创建集合
    fd_set rfds, temp;
    FD_ZERO(&rfds);
    FD_ZERO(&temp);
    FD_SET(0, &rfds);
    FD_SET(fd, &rfds);
    char buf[256] = "";
    //4.发送消息
    int ret = -1;
    while(1)
    {
        temp = rfds;
        ret = select(fd+1, &temp, NULL, NULL, NULL);
        if(ret < 0)
        {
            perror("select");
            exit(2);
        }
        else if(0 == ret)
        {
            fprintf(stderr, "超时\n");
            continue;
        }
        //键盘事件,说明终端有数据输入
        //可以用函数读取终端的数据,然后发送给服务器
        if(FD_ISSET(0, &temp))
        {
            bzero(buf, sizeof(buf));
            //fprintf(stderr, "请输入:");
            fgets(buf, 256-1, stdin);
            // ssize_t send(int sockfd, const void *buf, size_t len, int flags);
            do
            {
                ret = send(fd, buf, strlen(buf), 0);
            }while(ret<0 && errno == EINTR);
            if(ret < 0)
            {
                perror("send");
                exit(1);
            }
        }
        //服务器交互事件,说明服务器有数据发送到客户端
        //可以用recv函数读取数据
        if(FD_ISSET(fd, &temp))
        {
            bzero(buf, sizeof(buf));
            do
            {
                ret = recv(fd, buf, sizeof(buf), 0);
            }while(ret<0 && EINTR==errno);
            if(ret < 0)
            {
                perror("recv");
                exit(1);
            }
            else if(0 == ret)
            {
                fprintf(stderr ,"-----服务器已关闭-----\n");
                break;
            }         
            printf("%s\n", buf);
        }
    }
    close(fd);
    return 0;
}

九、抓包工具

1.wireshark的概念

1.1概念

  1. wireshark是非常流行的网络封包分析软件,功能十分强大。
  2. 可以截取各种网络封包,显示网络封包的详细信息。
  3. wireshark是开源软件,可以放心使用。
  4. 可以运行在Windows和Mac OS上。
  5. 使用wireshark的人必须了解网络协议,否则就看不懂wireshark了。

1.2. Wireshark不能做的

为了安全考虑,wireshark只能查看封包,而不能修改封包的内容,或者发送封包。

1.3. Wireshark VS Fiddler

1.4. 同类的其他工具

​ 1) 微软的network monitor
2) sniffer

1.5. wireshark的作用

  1. 网络管理员会使用wireshark来检查网络问题
  2. 软件测试工程师使用wireshark抓包,来分析自己测试的软件
  3. 从事socket编程的工程师会用wireshark来调试
  4. 总之跟网络相关的东西,都可能会用到wireshark.

2.安装

​ 1) wireshark的官方下载网站:www.wireshark.org/
​ 2) Linux sudo apt install wireshark
​ 3) 启动sudo wireshark
设置中文 Edit->Preferences->Appaerance->Language->Chinese

3.wireshark 抓包

3.1开始抓包

开始界面

在这里插入图片描述

  1. wireshark是捕获机器上的某一块网卡的网络包,当你的机器上有多块网卡的时候,你需要选择一个网卡。
  2. 点击Caputre->Interfaces… 出现下面对话框,选择正确的网卡。然后点击"Start"按钮, 开始抓包
Wireshark 窗口介绍

WireShark 主要分为这几个界面

  1. Display Filter(显示过滤器), 用于过滤
  2. Packet List Pane(封包列表), 显示捕获到的封包, 有源地址和目标地址,端口号。 颜色不同,代表
  3. Packet Details Pane(封包详细信息), 显示封包中的字段
  4. Dissector Pane(16进制数据)
  5. Miscellanous(地址栏,杂项)
    在这里插入图片描述

4.Wireshark 显示过滤

在这里插入图片描述

4.1 概念

  1. 使用过滤是非常重要的, 初学者使用wireshark时,将会得到大量的冗余信息,在几千甚至几万条记录中,以至于很难找到自己需要的部分。搞得晕头转向。
  2. 过滤器会帮助我们在大量的数据中迅速找到我们需要的信息。
  3. 过滤器有两种,
    a) 一种是显示过滤器,就是主界面上那个,用来在捕获的记录中找到所需要的记录
    b) 一种是捕获过滤器,用来过滤捕获的封包,以免捕获太多的记录。 在Capture -> Capture Filters 中设置

4.2 保存过滤

  1. 在Filter栏上,填好Filter的表达式后,点击Save按钮, 取个名字。比如"Filter 102",
    在这里插入图片描述
  2. Filter栏上就多了个"Filter 102" 的按钮。
    在这里插入图片描述

4.3 过滤表达式的规则

表达式规则

1) 协议过滤

比如TCP,只显示TCP协议。

2) IP 过滤

比如 ip.src 192.168.1.102 显示源地址为192.168.1.102,
ip.dst
192.168.1.102, 目标地址为192.168.1.102

3) 端口过滤

tcp.port ==80, 端口为80的
tcp.srcport == 80, 只显示TCP协议的愿端口为80的。

4) http模式过滤

http.request.method==“GET”, 只显示HTTP GET方法的。

5) 逻辑运算符为 AND/ OR

常用的过滤表达式

过滤表达式用途
http只查看HTTP协议的记录
ip.src192.168.1.102 or ip.dst192.168.1.102源地址或者目标地址是192.168.1.102

5.封包列表(Packet List Pane)

  1. 封包列表的面板中显示,编号,时间戳,源地址,目标地址,协议,长度,以及封包信息。
  2. 你可以看到不同的协议用了不同的颜色显示。
  3. 你也可以修改这些显示颜色的规则, View ->Coloring Rules.
    在这里插入图片描述

6.封包详细信息 (Packet Details Pane)

这个面板是我们最重要的,用来查看协议中的每一个字段。
各行信息分别为

  1. Frame: 物理层的数据帧概况
  2. Ethernet II: 数据链路层以太网帧头部信息
  3. Internet Protocol Version 4: 互联网层IP包头部信息
  4. Transmission Control Protocol: 传输层的数据段头部信息,此处是TCP
  5. Hypertext Transfer Protocol: 应用层的信息,此处是HTTP协议

7.wireshark与对应的OSI七层模型

在这里插入图片描述

十、包头分析

1.以太网头

以太网头中封装了源mac地址以及目的mac地址,还有ip类型。以太网头又称之为mac头
在这里插入图片描述

2.IP头

在这里插入图片描述
TTL:time to live 生命周期,数据包能够经过几个路由器。当目标接包的时候,这个的ttl减到0就会被丢弃。
Linux:TTL64 Windows:TTL128.中国连接到世界任意一个点绝对够用。
所以丢包不是ttl耗尽造成的。
丢包是由于阻塞造成的,路由由一个等待队列,由丢包算法。

3.UDP头

source port 源端口号:2字节 16位
destination port 目标端口号:2字节
length 总长度:2字节
checksum 校验码:2字节
总共占8字节
在这里插入图片描述

4.TCP头

在这里插入图片描述
序号
序列号seq:占4个字节,用来标记数据段顺序
TCP把连接中发送的所有数据字节都编上号。
确认号:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号
序列号表示报文携带数据的第一个字节编号;
确认好指期待收到下一个字节的编号。
TCP flag
确认ACK:占1位,当ACK=1,确认号才有效
同步SYN:连接简历是用同步序列号
SYN = 1 ,ACK = 0表示请求连接
终止FIN:用来释放一个连接,如果FIN=1此报文发送方数据已经发完了。
通过确认重发机制,做到可靠传输。

三次握手

三次握手:在客户端尝试连接服务器时候会产生
在这里插入图片描述
第一次握手:客户端发送syn包(SYN=1,seq=x)给服务器,并进入SYN_SEND状态,等待服务返回确认;
第二次握手:服务器接收到syn包,确认客户端的SYN,发送ack包(ACK=1,ack=x+1),同时发送一个syn包(SYN=1,seq=y),并进入SYN-RECV状态。
第三次握手:客户端收到服务器的syn以及ack包,向服务器发送ack包(ACK=1, ack=y+1),此时三次握手的包发送完毕,客户端和服务器进入ESTAB-LISHED状态。完成三次握手。

四次挥手

断开连接,可能是服务器发起的,也可能是客户端发起的
在这里插入图片描述
第一次挥手,主动关闭的一方发送一个FIN(FIN=1,seq=u)给被动方,进入FIN-WAIT-1状态;
第二次挥手,被动方接受到FIN包,给主动方发送一个ACK包(ACK=1, ack=1+u),并进入CLOSE-WAIT状态。主动方接收到ACk包,进入FIN-WAIT-2状态,如果有数据没有发送完毕,则继续发送,直到发送完毕;
第三次挥手,被动关闭方发送一个FIN包(FIN=1,seq=w),进入LAST-ACK状态。
第四次挥手,主动关闭方收到FIN包,回复一个ACK包(ACK=1,ack=w+1)。此时主动关闭方等待2个MSL(最大报文寿命 linux=60s)时间后关闭连接,被动关闭方收到主动关闭方的ACK后关闭连接
为什么要等待2个MSL?
被动方可能会收不到ack包,如果主动方发完ack包就退出的话,就无法重新在发一个ack包了,所以需要等待2个MSL,确保被动方收到ack包。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值