第六卷 _网络编程_ 我的笔记


点击我, 到底部

版权声明

作者: martin

网络编程

TCP/IP 四层协议
在这里插入图片描述

OSI七层协议模型 (open system interconnection)

应用层————为应用数据提供服务

表示层————数据格式转化,数据加密

会话层————建立、维护和管理会话

传输层————建立、维护和管理端到端的链接,控制数据传输的方式

网络层————数据传输线路选择,IP地址及路由选择

数据链路层———物理通路的发送和数据包的划分,附加Mac地址到数据包

物理层———01比特流的转换

数据传输由顶向下,下层为上层提供服务

TCP/IP四层协议模型

应用层———负责处理特定的应用程序细节, 如ftp,http ,smtp,ssh 等

运输层———主要为两台主机上的应用提供端到端的通信, 如TCP,UDP。

网络层(互联网层)———处理分组在网络中的活动,比如分组的选路。

链路层(数据链路层/网络接口层)———包括操作系统中的设备驱动程序、
计算机中对应的网络接口卡,01比特流的转换

协议封装

下层协议通过封装为上层协议提供服务。
应用程序数据在发送到物理网络上之前,将沿着协议栈从上往下依次传递。
每层协议都将在上层数据的基础上加上自己的头部信息(有时也包括尾部信息),
以实现该层的功能。

在这里插入图片描述

TCP 协议头部

在这里插入图片描述

`源端口号和目的端口号: `再加上Ip首部的源IP地址和目的IP地址可以
唯一确定一个TCP连接

`数据序号:`表示在这个报文段中的第一个数据字节序号

`确认序号:`仅当ACK标志为1时有效。确认号表示期望收到的下一个字节的
序号(这个下面再详细分析)

`偏移:`就是头部长度,有4位,跟IP头部一样,以4字节为单位。
最大是60个字节

`保留位:`6位,必须为0

`6个标志位:`
URG-紧急指针有效
ACK-确认序号有效
PSH-接收方应尽快将这个报文交给应用层
RST-连接重置
SYN-同步序号用来发起一个连接
FIN-终止一个连接

`窗口字段:`16位,代表的是窗口的字节容量,
也就是TCP的标准窗口最大为2^16 - 1 = 65535个字节

`校验和:`源机器基于数据内容计算一个数值,
收信息机要与源机器数值 结果完全一样,从而证明数据的有效性。
检验和覆盖了整个的TCP报文段:这是一个强制性的字段,
一定是由发送端计算和存储,并由接收端进行验证的。

`紧急指针:`是一个正偏移量,与序号字段中的值相加表示紧急数据最后一个字节
的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式
选项与填充(必须为4字节整数倍,不够补0):
最常见的可选字段的最长报文大小MSS(Maximum Segment Size),每个连接方
通常都在一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。
该选项如果不设置,默认为536(20+20+536=576字节的IP数据报)

三次握手

(1)男孩喜欢女孩,于是写了一封信告诉女孩:我喜欢你,请和我交往吧!
写完信之后,男孩焦急地等待,因为不知道信能否顺利传达给女孩。

(2)女孩收到男孩的情书后,心花怒放,原来我们是两情相悦呀!
于是给男孩写了一封回信:我收到你的情书了,也明白了你的心意,
其实,我也喜欢你!我愿意和你交往!;
写完信之后,女孩也焦急地等待,因为不知道回信能否能顺利传达给男孩。

(3)男孩收到回信之后很开心,因为发出的情书女孩收到了,
并且从回信中知道了女孩喜欢自己,并且愿意和自己交往。
然后男孩又写了一封信告诉女孩:你的心意和信我都收到了,
谢谢你,还有我爱你!女孩收到男孩的回信之后,也很开心,
因为发出的情书男孩收到了。由此男孩女孩双方都知道了彼此的心意,
之后就快乐地交流起来了~~
所谓的`三次握手`即TCP连接的建立。这个连接必须是一方主动打开,
另一方被动打开的。

在这里插入图片描述

(1)首先客户端向服务器端发送一段TCP报文,其中:
标记位为SYN,表示“请求建立新连接”;序号为Seq=X(X一般为1);
随后客户端进入SYN-SENT阶段。

(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。
并返回一段TCP报文,其中:
标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,
服务器能正常接收客户端发送的数据,并同意创建新连接”
(即告诉客户端,服务器收到了你的数据);序号为Seq=y;
确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1
作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。

(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,
明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。
并返回最后一段TCP报文。其中:
标志位为ACK,表示“确认收到服务器端同意连接的信号”
(即告诉服务器,我知道你收到我发的数据了);序号为Seq=x+1,
表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
确认号为Ack=y+1,表示收到服务器端序号Seq,
并将其值加1作为自己的确认号Ack的值;随后客户端进入ESTABLISHED阶段。
服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,
明确了从服务器到客户端的数据传输是正常的。
结束SYN-SENT阶段,进入ESTABLISHED阶段。
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,
都是在彼此Ack和Seq值的基础上进行计算的,
这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,
便无法继续"握手",以此确保了"三次握手"的顺利完成。

滑动窗口

维持发送方/接收方缓冲区,缓冲区是用来解决网络之间数据不可靠的问题,
例如丢包,重复包,出错,乱序。在TCP协议中,
发送方和接受方通过各自维护自己的缓冲区。
通过商定包的重传机制等一系列操作,来解决不可靠的问题。
`问题一:`如果你是TCP设计者,如何保证数据包依次序传输?

解决方案: 发送 <=> 确认机制

在这里插入图片描述

问题二: 采用问题一的解决方案会带来效率上的弊端,
数据包在网络上的传输需要时间

解决方案:  一次发送多个包,同时确认多个

在这里插入图片描述

问题三: 我们每次需要发多少个包过去呢?发送多少包是最优解呢?

正常情况

我们能不能把第一个和第二个包发过去后,收到第一个确认包就把第三个包发
过去呢?而不是去等到第二个包的确认包才去发第三个包。
这样就很自然的产生了我们"滑动窗口"的实现。

在这里插入图片描述

在图中,我们可看出灰色1号2号3号包已经发送完毕,并且已经收到Ack。
这些包就已经是过去式。4、5、6、7号包是黄色的,表示已经发送了。
但是并没有收到对方的Ack,所以也不知道接收方有没有收到。
8、9、10号包是绿色的。是我们还没有发送的。这些绿色也
就是我们接下来马上要发送的包。 可以看出我们的窗口正好是7格。
后面的11-16还没有被读进内存。要等4号-10号包有接下来的动作后,
我们的包才会继续往下发送。

在这里插入图片描述

可以看到4号包对方已经被接收到,所以被涂成了灰色。“窗口”就往右移一格,
这里只要保证“窗口”是7格的。 我们就把11号包读进了我们的缓存。
进入了“待发送”的状态。8、9号包已经变成了黄色,表示已经发送出去了。
接下来的操作就是一样的了,确认包后,窗口往后移继续将未发送的包读进缓存,
把“待发送“状态的包变为”已发送“。

丢包情况

有可能我们包发过去,对方的Ack丢了。也有可能我们的包并没有发送过去。
从发送方角度看就是我们没有收到Ack。

在这里插入图片描述

一般情况:一直在等Ack。如果一直等不到的话,
我们也会把读进缓存的待发送的包也一起发过去。
但是,这个时候我们的窗口已经发满了。所以并不能把12号包读进来,
而是始终在等待5号包的Ack。

如果我们这个Ack始终不来怎么办呢? 采用超时重传机制解决:
发送端每发送一个报文段,就启动一个定时器并等待确认信息;
接收端成功接收新数据后返回确认信息。
若在定时器超时前数据未能被确认,TCP就认为报文段中的数据已丢失或损坏,
需要对报文段中的数据重新组织和重传。(重传超时时间: RTO)

在这里插入图片描述

四次挥手

在这里插入图片描述

1.客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,
是由发送端随机生成的,并且还将报文中的FIN字段置为1,
表示需要断开TCP连接。(FIN=1,seq=x,x由客户端随机生成)

2.服务端会回复客户端发送的TCP断开请求报文,其包含seq序列号,
是由回复端随机生成的,而且会产生ACK字段,
ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,
以便客户端收到信息时,知晓自己的TCP断开请求已经得到验证。
(FIN=1,ACK=x+1,seq=y,y由服务端随机生成)

3.服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,
服务端会先确保断开前,所有传输到A的数据是否已经传输完毕,
一旦确认传输数据完毕,就会将回复报文的FIN字段置1,
并且产生随机seq序列号。(FIN=1,ACK=x+1,seq=z,z由服务端随机生成)

4.客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,
包含随机生成的seq字段和ACK字段,
ACK字段会在服务端的TCP断开请求的seq基础上加1,
从而完成服务端请求的验证回复。
(FIN=1,ACK=z+1,seq=h,h为客户端随机生成)

至此TCP断开的4次挥手过程完毕。

UDP通信

1. TCP与UDP
    当使用网络套接字通信时,
    套接字的“域”都取AF_INET;
    套接字的type:
          SOCK_STREAM   此时,默认使用TCP协议进行通信。
          SOCK_DGRAM    此时,默认使用UDP协议进行通信。
    
    TCP通信,是一个有序的、可靠的、面向连接的
    UDP通信,是不保证有序到达的数据报服务。(在局域网内,使用UDP已很可靠)
2. 使用UDP通信
    与TCP通信使用上的区别:
    1)创建套接字时的type(参数2)不同。
         TCP通信,使用SOCK_STREAM
         UDP通信,使用SOCK_DGRAM
    
    2)发送数据和接收数据时,使用的接口不同
         TCP通信,发送数据,使用write(或send)
                       接收数据,使用read(或recv)
         UDP特性,发送数据,使用sendto      
                  接收数据,服务器端使用recvfrom   
                             客户端使用recv
                       
   3)不需要使用listen
   4)不需要先建立连接
   		(TCP客户端和服务器端分别使用connect和receive建立连接)
   
   步骤总结:
     基于UDP的网络套接字通信
     服务器端
        (1) 创建一个网络套接字
        (2) 设置服务器地址
        (3) 绑定该套接字,使得该套接字和对应的端口关联起来     
        (4) 循环处理客户端请求
              使用recvfrom等待接收客户端发送的数据
              使用sendto发送数据至客户端
   


     客户端
       (1) 创建一个套接字
       (2) 设置服务器地址
       (3) 使用sendto向服务器端(接收端)发送数据
       (4) 使用recv接受数据
3. sendto与recvfrom、recv
    1) sendto
        功能:UDP服务器或客户端用于发送数据
        原型:int  sendto (int sockfd,     //  套接字
                   void *buff,            // 发送缓存区
                   size_t len,            // 发送缓冲区的长度
                   init  flags,           //  标志,一般取0
                   struct sockaddr *to,   // 目的主机地址
                  socklen_t  tolen        // 目的主机地址长度
                      );
         返回值:成功,返回实际发送数据的字节数
                    失败,返回-1
                    
    2) recvfrom
         功能:UDP服务器用于接收数据
         原型: 与sendto类似。
            int  recvfrom (int sockfd,       //  套接字
                 void *buff,                 // 接收缓存区
                 size_t len,                 // 接受缓冲区的长度
                 init  flags,                //  标志,一般取0
                 struct sockaddr *to,        // 源主机地址
                 socklen_t  *tolen           // 源主机地址长度
                 );
         注意:参数6必须要初始化为对应地址的长度!

     3) recv
         功能:UDP客户端用于接收数据
         原型: ssize_t  recv (int sockfd,  void *buf,  size_t len, 
         					   int flags);             
         注意: 该调用的参数不需要指定地址。
    
因为当使用udp时,对应的套接字被自动绑定在一个短暂的动态的端口上。
4.实例
实例1: 服务器接收、客户端发送

client_01.c

#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define BUFF_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    int ret;
    int c;
    char buff[BUFF_SIZE];

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(9000);

    strcpy(buff, "hello world");
    ret = sendto(sockfd, buff, strlen(buff) + 1, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (ret == -1) {
        perror("sendto");
        exit(errno);
    }

    printf("ret = %d\n", ret);

    return 0;
}

server_01.c

#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define BUFF_SIZE 1024

int main() {
    int server_sockfd;
    int client_sockfd;
    char ch;
    int ret;
    int recv_len;
    char buff[BUFF_SIZE];

    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int client_addr_len = sizeof(struct sockaddr_in);

    server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(9000);

    ret = bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (ret == -1) {
        perror("bind");
        exit(1);
    }

    while (1) {
        printf("server waiting\n");
        recv_len = recvfrom(server_sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&client_addr, &client_addr_len);
        if (recv_len < 0) {
            perror("recvfrom");
            exit(errno);
        }

        printf("received: %s\n", buff);
    }
    
    close(server_sockfd);
    
    return 0;
}

运行结果
在这里插入图片描述

实例2:服务器收发、客户方发送、接收。

server_02.c

#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define BUFF_SIZE 1024

static void str2up(char* str) {
    while (*str) {
        if (*str >= 'a' && *str <= 'z') {
            *str = *str - 'a' + 'A';
        }

        str++;
    }
}

int main() {
    int server_sockfd;
    int client_sockfd;
    char ch;
    int ret;
    int recv_len;
    int send_len;
    char buff[BUFF_SIZE];

    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    int client_addr_len = sizeof(struct sockaddr_in);

    server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(9000);

    ret = bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (ret == -1) {
        perror("bind");
        exit(1);
    }

    while (1) {
        printf("server waiting\n");
        recv_len = recvfrom(server_sockfd, buff, sizeof(buff), 0, (struct sockaddr*)&client_addr, &client_addr_len);
        if (recv_len < 0) {
            perror("recvfrom");
            exit(errno);
        }

        printf("received: %s\n", buff);

        str2up(buff);
        send_len = sendto(server_sockfd, buff, strlen(buff) + 1, 0, (struct sockaddr*)&client_addr, client_addr_len);
        if (send_len == -1) {
            perror("sendto");
            exit(errno);
        }
    }

    clone(server_sockfd);

    return 0;
}

client_02.c

#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define BUFF_SIZE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    int ret;
    int c;
    char buff[BUFF_SIZE];
    socklen_t addr_len;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_addr.sin_port = htons(9000);

    strcpy(buff, "hello world");
    ret = sendto(sockfd, buff, strlen(buff) + 1, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (ret == -1) {
        perror("sendto");
        exit(errno);
    }

    printf("send %d bytes\n", ret);

    ret = recv(sockfd, buff, sizeof(buff), 0);
    if (ret == -1) {
        perror("recv");
        exit(errno);
    }

    printf("received %d bytes\n", ret);
    printf("received %s\n", buff);


    return 0;
}

运行结果
在这里插入图片描述

同步IO和异步IO

场景1: 小明去打开水,而开水塔此时没有水,小明在现场一直等待开水到来,
或者不断的轮询查看是否有开水,直到有开水取到水为止,
这是同步IO的一种案例!

同步IO的特点:

同步IO指的是用户进程触发I/O操作并等待或者轮询的去查看I/O操作是否就绪。
同步IO的执行者是IO操作的发起者。
同步IO需要发起者进行内核态到用户态的数据拷贝过程,所以这里必须阻塞

场景2: 小明去打开水,而开水塔此时没有水,开水塔的阿姨叫小明把水壶
放到现场,来水后会帮他打好水,
并打电话叫他来取,这是异步IO的一种案例!

异步IO的特点:

异步IO是指用户进程触发I/O操作以后就立即返回,继续开始做自己的事情,
而当I/O操作已经完成的时候会得到I/O完成的通知。
异步IO的执行者是内核线程,内核线程将数据从内核态拷贝到用
户态,所以这里没有阻塞
点击我, 到顶部
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值