tcp

Created

2024-10-28 13:34:58

Updated

2024-10-28 13:38:34

Warning

未完待续

1 socket

  • 与网络中的其他应用进行通信的函数接口
  • 封装了传输层的协议, tcp udp
  • 既然是与网络中的其他应用程序通信, 那么我们可以推断socket套接字肯定有ip(确定主机)和port(确定应用),用来明确是哪个应用
Diagram

2 代码

client.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>

// tcp client
int main(int argc, const char* argv[])
{
    // 创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket error");
        exit(1);
    }

    // 连接服务器
    struct sockaddr_in serv_addr;
    // 将serv_addr 结构体 所占内存内容置为0, 等价于 bzero(&serv_addr, sizeof(serv_addr));
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    //网络传输数据采用的是大端 ,所以这里端口要转换为大端
    // h: host, to:to, n: net, s:short int (unsigned)
    // 将主机字节序转为网络字节序(大端)
    serv_addr.sin_port = htons(9999);
    // 将ip转换为网络字节序, 是将字符串(ip) 转为int型(大端方式)
    // 直接修改 &serv_addr.sin_addr.s_addr
    // inet_pton转换成功则返回1,转换失败返回 0,如果指定的地址类型协议簇不合法,将返回-1
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1)
    {
        perror("connect error");
        exit(1);
    }

    // 通信
    while(1)
    {
        // 写数据
        // 接收键盘输入
        char buf[512];
        printf("input: \n");
        fgets(buf, sizeof(buf), stdin);
        // 这里如果输入的小于512 会就最后的回车符号也发送过去.(详情见fgets的说明)
        // 你可以自己将最后的回车符处理掉...
        // 发送给服务器, +1 是把最后的\0 也发送过去.
        write(fd, buf, strlen(buf)+1);

        // 接收服务器端的数据
        int len = read(fd, buf, sizeof(buf));
        if(len == -1)
        {
            perror("read error");
            break;
        }
        else if( len == 0 )
        {
            printf("服务端关闭了连接 ...\n");
            break;
        } else if(len > 0)
        {
            printf("read buf = %s, len = %d\n", buf, len);
        }
    }
    close(fd);
    return 0;
}
server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>


int main(int argc, const char* argv[])
{
    // 创建用于监听的套节字
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd == -1)
    {
        perror("socket error");
        exit(1);
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;     // 地址族协议ipv4
    serv_addr.sin_port = htons(9999);   // 本地端口需要转换为大端
    // ip转为网络字节序(大端)
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 0表示用本机的任意IP

    int ret = bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }

    // 设置监听
    ret = listen(listen_fd, 64);
    if(ret == -1)
    {
        perror("listen error");
        exit(1);
    }

    // 等待并接受连接请求
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
    if(client_fd == -1)
    {
        perror("accept error");
        exit(1);
    }
    char client_ip_buf[64];
    // 网络字节序 转换为 主机字节序, 结果存储到client_ip_buf
    // 返回值是个指向client_ip_buf 的指针
    const char* client_ip=inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip_buf, sizeof(client_ip_buf));
    printf("client ip: %s, port: %d\n", client_ip, ntohs(client_addr.sin_port));

    // 通信
    while(1)
    {
        // 先接收数据
        char buf[1024] = {0};
        int len = read(client_fd, buf, sizeof(buf));
        if(len == -1)
        {
            perror("read error");
            break;
        }
        else if(len > 0)
        {
            // 顺利读出了数据
            printf("read buf = %s\n", buf);
            for(int i=0; i<len; ++i)
            {
                buf[i] = toupper(buf[i]);
            }
            printf(" toupper: %s\n", buf);

            // 数据发送给客户端
            write(client_fd, buf, strlen(buf)+1);
        }
        else if( len == 0 )
        {
            printf("client disconnect ...\n");
            break;
        }
    }

    close(listen_fd);
    close(client_fd);

    return 0;
}
gcc client.c -o client
gcc server.c -o server
./server
./client

3 tcp 报文

Diagram

4 握手-传输-挥手

Diagram Diagram

5 滑动窗口

6 修改缓冲区大小

6.1 系统tcp缓冲设置

查看配置
cat /proc/sys/net/ipv4/tcp_rmem
4096    87380   5369920
# 4096 是说接收缓冲区(读缓冲区)的最小值
# 87380 表示默认值 该值会覆盖rmem_default, 不是被覆盖, 网络上很多说被覆盖.. 真的服了
# 5369920 接收缓冲区空间的最大字节数(该值会被rmem_max覆盖)
cat /proc/sys/net/core/rmem_max  #一个socket的读缓冲区可由程序设置的最大值
212992
cat /proc/sys/net/core/rmem_default #一个socket的被创建出来时,默认的读缓冲区大小
212992
临时修改配置,重启后失效
sysctl -w net.ipv4.tcp_wmem="1 1 1"
sysctl -w net.core.wmem_max=5000

6.2 查看linux关于缓冲的源码

linux4.9源码 net/core/sock.h
#define TCP_SKB_MIN_TRUESIZE    (2048 + SKB_DATA_ALIGN(sizeof(struct sk_buff)))

#define SOCK_MIN_SNDBUF     (TCP_SKB_MIN_TRUESIZE * 2)
#define SOCK_MIN_RCVBUF      TCP_SKB_MIN_TRUESIZE
linux4.9源码 net/core/sock.c
switch (optname) {
    case SO_SNDBUF:
        // 如果 val> /proc/sys/net/core/wmem_max ,则设置成wmem_max 的值
        val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
        sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
        // 如果 val值的 2倍 还小于最小值, 那么 使用 最小值,否而就是 val值的2倍
        sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
        sk->sk_write_space(sk);
        break;
    case SO_RCVBUF:
        val = min_t(u32, val, sysctl_rmem_max);
set_rcvbuf:
        sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
        sk->sk_rcvbuf = max_t(int, val * 2, SOCK_MIN_RCVBUF);
        break;
}
得出结论
  1. 如果你设置的值 > 最大值,则 设置为 最大值的2倍
  2. 如果你设置的值的2倍 < 最小值,则 设置为 最小值
  3. 其他情况 ,则设置为 该值的2倍

6.3 代码修改缓冲

server.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>

void getsockopts(char* s ,int sockfd){
    printf("===%s===\n",s);
    int opt_val;
    socklen_t opt_len=sizeof(opt_val);
    if (getsockopt(sockfd,SOL_SOCKET, SO_SNDBUF, &opt_val, &opt_len)!=0){
        perror("getsocketopt error");
        exit(1);
    }
    printf("写缓冲区大小: %d\n", opt_val);
    opt_val=0;
    if (getsockopt(sockfd,SOL_SOCKET, SO_RCVBUF, &opt_val, &opt_len)!=0){
        perror("getsocketopt error");
        exit(1);
    }
    printf("读缓冲区大小: %d\n", opt_val);
    opt_val=0;
    if (getsockopt(sockfd,SOL_SOCKET, SO_SNDLOWAT, &opt_val, &opt_len)!=0){
        perror("getsocketopt error");
        exit(1);
    }
    printf("发送低潮限度大小: %d\n", opt_val);

}

void setsockopts(int sockfd,int nRecvBuf,int nSendBuf){
    // 接收缓冲区
    int ret=setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
    if (ret !=0){
        perror("setsocketopt error");
    }
    //发送缓冲区
    ret =setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
    if (ret !=0){
        perror("setsocketopt error");
    }
}
int main(int argc, const char* argv[])
{
    // printf("argcount: %d\n",argc);
    if (argc!=3){
    printf("请输入要设置的读缓冲和写缓冲\n");
    printf("usage: %s 读缓冲值 写缓冲值\n",argv[0]);
    exit(1);
    }
    // 创建用于监听的套节字
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd == -1)
    {
        perror("socket error");
        exit(1);
    }
    getsockopts("before set",listen_fd);
    // 这里不严谨,随便用用
    setsockopts(listen_fd,atoi(argv[1]),atoi(argv[2]));
    getsockopts("after set",listen_fd);
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(9999);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }

    // 设置监听
    ret = listen(listen_fd, 64);
    if(ret == -1)
    {
        perror("listen error");
        exit(1);
    }

    // 等待并接受连接请求
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
    if(client_fd == -1)
    {
        perror("accept error");
        exit(1);
    }
    char client_ip_buf[64];
    const char* client_ip=inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip_buf, sizeof(client_ip_buf));
    printf("client ip: %s, port: %d\n", client_ip, ntohs(client_addr.sin_port));

    // 通信
    while(1)
    {
        sleep(3);
    }
    close(listen_fd);
    close(client_fd);
    return 0;
}
./server2 212993 1
    argcount: 3
    ===before set===
    写缓冲区大小: 16384
    读缓冲区大小: 87380
    发送低潮限度大小: 1
    ===after set===
    写缓冲区大小: 4608 # 最小值
    读缓冲区大小: 425984 # 最大值的2倍
    发送低潮限度大小: 1

./server2 212991 1
    ===before set===
    写缓冲区大小: 16384
    读缓冲区大小: 87380
    发送低潮限度大小: 1
    ===after set===
    写缓冲区大小: 4608
    读缓冲区大小: 425982 # 设置的值的2倍
    发送低潮限度大小: 1

7 阻塞

阻塞 ,阻塞, 啥是阻塞?
1. 假设快递, 在没有蜂巢的情况下, 快递员带着包裹来了, 你人不在, GG
2. 有蜂巢后, 包裹到了, 快递员发现蜂巢满了, GG

Back to top