网络编程学习

1.TCP四次挥手

①断开时,当A说不玩了,就进图FIN_WAIT_1状态,B收到A不玩了的消息,发送知道了,就进入CLOSE_WAIT状态。

A收到B说知道了,就进入FIN_WAIT_2状态,如果这时候B直接跑路,A将永远在这个状态。

TCP协议里面没有对这个状态处理,Linux有:可调整tcp_fin_timeout这个参数,设置一个超时时间。

②如果B没有跑路,发送B也不玩了的请求到达A时,A发送知道B也不玩了的ACK后,从FIN_WAIT_2状态结束,万一B收不到ACK?

B会重新发一个B不玩了,此时A最后等待一段时间TIME_WAIT,要足够长,长到如果B没收到ACK的话,B说不玩了会重发,A会重新发一个ACK并且足够时间到达B。

③A直接跑路,A的端口直接空出来,但B不知道,B原来发过很多包很可能在路上,如果A的端口被一个新的应用占用,这个新的应用会收到上个连接中B发过来的包,虽然序列是重新生成的,但这里要上一个双保险,防止产生混乱,也需要等足够长时间,等到原来B发送的所有包都死掉,再空出端口来。

④等待时间设为2MSL, MSL(Maximum Segment Lifetime),报文最大生存时间,是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

因为TCP报文基于IP协议,IP头上有一个TTL域,是IP数据包课经过的最大路由数,每经过一个处理它的路由器,此值就减1,当此值为0则数据包被丢弃,同时发送ICMP报文通知源主机。

协议规定MSL为2分钟,实际应用中常用的是30s,1min和2min等。

2.如何实现一个靠谱的协议?

①客户端发送一个包,服务器端都应该有个回复,如果服务器端超过一定时间没有回复,客户端就会重新发送这个包,直到有回复。

②上一个收到应答,再发送下一个。缺点是效率低。

③TCP协议为了保证顺序性,每一个包都有一个ID。在建立连接时,会商定起始的ID是什么,然后按照ID一个个发送。

为了保证不丢包,对于发送方的包都要进行应答, 但这个应答不是一个一个来的,而是会应答某个之前的ID,表示都收到了,称为累计确认或累计应答。

3.顺序问题与丢包问题

①假设4的确到了,5的ACK丢了,6,7的数据包丢了,该怎么办?

超时重试:对每一个发送了但没有ACK的包,都有设一个定时器,超过一定时间,就重新尝试。

②超时时间如何评估?

不宜过短,时间必须大于往返时间RTT,否则会引起不必要的重传。不宜过长,访问就变慢了。

自适应重传算法:需要TCP通过采样RTT时间,进行加权平均,算出一个值,而且这个值要不断变化,因为网络状况不断变化。还要采样RTT波动范围,计算出一个估计的超时时间。

③快速重传机制

当接收方收到一个序号大于下一个期望的报文段时,就会检测到数据流中的一个间隔,就会发送冗余的ACK,仍然ACK的是期望接收的报文段。

当客户端收到3个冗余的ACK后,就会在定时器过期之前,重传丢失的报文段。

4.流量控制问题

当接收方比较慢时,要防止低能窗口综合征:

只要空出一个字节来就赶快告诉发送方,然后马上又填满了,当窗口太小时,不更新窗口,直到达到一定大小或者缓冲区一半为空,才更新窗口。

5.拥塞控制问题

通过拥塞窗口cwnd的大小来控制,怕把网络塞满。

对于网络上,通道的容量=带宽×往返延迟。

如果我们设置发送窗口,使得发送但未确认的包为通道容量,就就能撑满整个管道。

TCP拥塞控制主要来避免两种现象:丢包超时重传

一旦出现说明发送速度太快了,要慢一点。但一开始不知道有多快,如何知道窗口调整到多大?

①慢启动

如同漏斗灌水,一开始慢慢的倒,发现总能倒进去,就越倒越快。

②拥塞控制

有一个阈值,当超过这个值时,就要小心点,不能倒这么快了,可能快满了,再慢下来。

③快速恢复

当接收端发现丢了一个中间包时,发送三次前一个包的ACK,发送端就会快速重传,不必等待超时再重传。TCP认为这种情况不严重,因为大部分没丢,只丢了一小部分。

6.TCP BBR拥塞算法

①公网上带宽不满也会丢包,并不能认为就是拥塞了。

②TCP拥塞控制要等到中间设备填满才发生丢包,再降低速度就晚了。

正确做法:

TCP只要填满管道就可以,不应该接着填,直到连缓存也填满。

7.套接字编程

①低地址高字节:

TCP/IP协议规定,网络数据流应采用大端字节序。

为了使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,调用以下库函数做网络字节序和主机字节序的转换。

h:host

n:network

I:32位长整数

s:16位短整数

#include <stdio.h>

int main(void) {
    int a = 321;
    char *p;
    p = (char *)&a;
    printf("addr=%p, val=%#x\n", p, *p);
    printf("addr=%p, val=%#x\n", p+1, *(p+1));
    printf("addr=%p, val=%#x\n", p+2, p[2]);
    printf("addr=%p, val=%#x\n", p+3, p[3]);
    printf("%#x\n", 321);
    return 0;
}

#include <stdio.h>
#include <arpa/inet.h>

int main(void) {
    int a = 0x12345678;
    printf("%#x\n", a);
    int b = htonl(a);
    printf("%#x\n", b);
    return 0;
}

8.socket地址的数据类型

socket API:一层抽象的网络编程接口,适用于各种底层网络协议,

如:IPv4,IPv6,及Unix Domain Socket。各种网络协议的地址格式不相同。

IPv4,IPv6,及Unix Domain Socket的地址类型分别定义为常数AF_INET,AF_INET6,AF_UNIX。

只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

9.基于TCP协议的网络程序

建立连接后,TCP协议提供全双工的通信服务,但是一般客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。

服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器应答,服务器调用write()将处理结果发送给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

如果客户端没有更多请求,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就 知道客户端关闭了连接,也调用close()关闭连接。

注意:任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍然可接收对方发来的数据。

server:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERV_PORT 8000
#define MAXLINE 80

int main(void) {
    struct sockaddr_in serveraddr, clientaddr;
    int listenfd, connfd;
    socklen_t clientaddr_len;

    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, 3);
    printf("Accepting connections...\n");
    while (1) {
        clientaddr_len = sizeof(clientaddr);
        connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddr_len);

        printf("Received from %s:%d\n", inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)), ntohs(clientaddr.sin_port));
        int n = read(connfd, buf, MAXLINE);
        for (int i = 0; i < n; i++) {
            buf[i] = toupper(buf[i]);
        }
        write(connfd, buf, n);
        close(connfd);
    }
    return 0;
}

client:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <strings.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>

#define SERV_PORT 8000
#define MAXLINE 80

int main(void) {
    struct sockaddr_in servaddr;
    char buf[MAXLINE] = {"hello tcp"};
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    write(sockfd, buf, strlen(buf));
    int n = read(sockfd, buf, MAXLINE);
    printf("Response from server:\n");
    write(1, buf, n);
    close(sockfd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值