Linux--传输层通信--TCP协议(下)

版权声明:允许转载,请注明文章出处 https://blog.csdn.net/Vickers_xiaowei/article/details/86406906

TCP如何保证可靠传输

1、确认应答机制

发送的每条数据都需要确认回复一下
在这里插入图片描述

2、超时重传机制

发送方等待一段时间后还没有收到回复,就认为传输失败了,将数据重传。这个超时时间是递增的,次数有限,超过重传次数就认为网络断开了。

3、序号/确认序号------保证数据有序传输

4、反馈重发

(一)停等协议:发送数据之后,我就停下来等待回复报文,回复报文为确认帧(ACK),就发送下一条数据,回复报文是NAK,就自动重新发送该段报文。
(二)拉回方式(Go_Back_To_N):第N条数据出错,将第N条数据和第N条数据之后的数据都重新发送。
(三)选择重发:丢掉那条数据重新发送那条数据。 代价:需要接收端由足够大的接受缓冲区来存储出错数据之后的正确数据。比如第N条数据出错了,我现在已经接收到第N+100条数据了,那么我只需要对端重传第N条数据,那么第N条数据之后的100条数据都要存到我的接受缓冲区中去,因为接受数据只能按序号接受,并且选择重发实现起来存在更大的技术困难,因此相比较拉回方式很少使用选择重传。

TCP提升性能机制

tcp因为要保证可靠传输,因此性能有很大的消耗,为了提高tcp传输性能,有需要有其他的一些机制。

1、流量控制

通过不断设定窗口大小来告诉对方我能一次接收多少数据,最终达到流量控制的效果,避免因为发送太快,而处理太慢,导致接收方缓冲区塞满引起的大量的数据宝重传。

2、快速重传

在这里插入图片描述
一次发送多条数据,集中等待(快速重传):如果发送方没有接收到第一条报文的ACK1,但是收到了第二条数据的ACK2,那么他认为第一条也发送成功了,不需要重传,假如第一条数据丢了,那么接受方会连续发送3次ACK1说要的是第一条数据,但是并不会发送第二条已经收到的ACK2,发送方接收到重传请求就会进行数据重传。一次发送的数据有多少取决于协议中的窗口大小字段----窗口大小在进行数据传输前会进行协议。窗口大小实际也决定了TCP的传输吞吐量。

3、拥塞控制

一开始的时候,因为窗口比较大,发送方一次发送的数据比较多,但是网络状况不好,就有可能会大量丢包,丢包就需要重传,造成性能下降。
双方进行窗口大小协商之后,发送方还有一个东西叫拥塞窗口,并且拥塞窗口大小一开始的时候非常小,而实际发送大小取小的那一个,也就意味着拥塞窗口控制了tcp传输的一个慢启动,快速增长,一旦丢包,回到很低的窗口位置,再次轮回。

4、延时应答机制

对处理速度比较自信,对接收到的数据能够快速处理,只要稍微延时一下进行ACK应答,那么能够设置的窗口大小就会尽可能的大,一直保证一个tcp的最大吞吐量,假如是立即应答,那么有可能回复的这个窗口大小就会比较小。

5、捎带应答机制

将确认应答直接标记在即将发送的数据包内,那么这样不仅传输了数据还对上一次接受的数据进行应答(少传输一个应答包)

可同时处理多个TCP请求的服务器

多进程tcp服务端程序

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int create_worker(int cli_fd)
{
    int pid = fork();
    if  (pid < 0) 
    {
        return -1;
    }
    else if (pid == 0) 
    {
        while(1) 
        {
            char buff[1024] = {0};
            int ret = recv(cli_fd, buff, 1024, 0);
            if (ret <= 0) {
                close(cli_fd);
                exit(-1);
            }
            printf("client say:[%s]\n", buff);
            send(cli_fd, "what\?\?!!", 8, 0);
        }
    }else {
        close(cli_fd);
    }
    return 0;
}

void sigcb(int signo) 
{
    //如果返回值大于0,代表有子进程退出,我们不确定现在到底有几个子进程
    //退出了,所以就一直循环,直到返回值为0位置,0就代表没有子进程退出
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[])
{
    signal(SIGCHLD, sigcb);
    //1. 创建监听socket(这个socket仅仅用于接收客户端的连接请求)
    //struct socket  ->>>struct file
    int lst_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lst_fd < 0) {
        perror("socket error");
        return -1;
    }
    //2. 为监听socket绑定地址信息(如果使用socket发送数据,则是从绑定的
    //这个地址和端口发送数据,)
    //htons--将两个字节的数据转换为网络字节序的数据
    //htonl--将四个字节的数据转换为网络字节序的数据
    //ntohs--将两个字节的网络字节序数据转换为当前的主机字节序数据
    //ntohl--将四个字节的网络字节序数据转换为当前的主机字节序数据
    struct sockaddr_in lst_addr;
    lst_addr.sin_family = AF_INET;
    lst_addr.sin_port = htons(9000);
    lst_addr.sin_addr.s_addr = inet_addr("192.168.122.132");

    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(lst_fd, (struct sockaddr*)&lst_addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    //3. 开始监听
    //  listen会在系统里边开辟一个队列,第二个参数就是定义这个队列的最大
    //  节点数,这个队列名字叫做连接成功队列
    //  第二个参数的功能:同一时间socket的最大并发连接数,记住不是最大连
    //  接数
    //
    //  一旦socket开始监听,那么就可以开始接收客户端的连接请求,
    //  如果有客户端的连接请求过来,操作系统会为这个客户端新建一个socket
    //  连接建立成功后这个socket将被放入连接成功队列
    //  接下来如果想要和这个客户端进行通信,那么就需要把这个socket从队列
    //  中取出来,进行操作
    if (listen(lst_fd, 5) < 0) {
        perror("listen error");
        return -1;
    }
    while(1) {
        //4. 从连接成功队列中,取出为客户端新连接创建的socket,与客户端
        //的数据通信都是通过这个socket来完成的。
        //  accept是一个阻塞型的函数,如果连接成功队列中没有新的socket
        //  那么它就一直阻塞在这里了,直到有客户端连接上来创建socket
        struct sockaddr_in cli_addr;
        int cli_fd = accept(lst_fd, (struct sockaddr*)&cli_addr, &len);
        if (cli_fd < 0) {
            perror("accept error");
            continue;
        }
        create_worker(cli_fd);
    }
    close(lst_fd);

    return 0;
}

多线程tcp服务端程序

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void *thr_start(void *arg)
{
    int clifd = (int)arg;

    while(1) {
        char buff[1024] = {0};
        ssize_t ret = recv(clifd, buff, 1024, 0);
        if (ret < 0) {
            perror("recv error");
            close(clifd);
            break;
        }else if (ret == 0) {
            printf("peer shutdown!!\n");
        }
        printf("client say:[%s]\n", buff);
        send(clifd, "what!!", 6, 0);
    }
    return NULL;
}
int create_worker(int clifd)
{
    //创建一个线程来单独处理与指定客户端的通信
    pthread_t tid;
    int ret;

    ret = pthread_create(&tid, NULL, thr_start, (void*)clifd);
    if (ret != 0) {
        printf("create pthread error\n");
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[])
{
    int lstfd, clifd, ret;
    socklen_t len;
    struct sockaddr_in lst_addr;
    struct sockaddr_in cli_addr;

    if (argc != 3) {
        //  ./ttcp_server 192.168.122.132 9000
        printf("Usage: ./ttcp_server ip port\n");
        return -1;
    }
    lstfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lstfd < 0) {
        perror("socket error");
        return -1;
    }
    lst_addr.sin_family = AF_INET;
    lst_addr.sin_port = htons(atoi(argv[2]));
    lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
    len = sizeof(struct sockaddr_in);
    ret = bind(lstfd, (struct sockaddr*)&lst_addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    if (listen(lstfd, 5) < 0) {
        perror("listen error");
        return -1;
    }
    while(1) {
        clifd = accept(lstfd, (struct sockaddr*)&cli_addr, &len);
        if (clifd < 0) {
            perror("accept error");
            continue;
        }
        create_worker(clifd);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Vickers_xiaowei/article/details/86406906
今日推荐