12.TCP通信 C++/Linux-2022-10-21

2.TCP通信

1.tcp三次握手、四次挥手
  • 标志位

    • SYN:请求建立连接
    • ACK:应答
    • FIN:断开连接
  • 第一次:

    • 客户端:
      • 携带标志位:SYN
      • 随机产生32位序号
        • 可以携带数据()
    • 服务器
      • 检测SYN值是否为1
  • 第二次:

    • 服务器:
      • ACK标志位 + 确认序号
        • 客户端随机序号+1(或者说加传输数据量,如客服端传来的数据为10,则加11)
      • 发起一个连接请求
        • SYN+32随即序号
    • 客服端:
      • 检测标志位:1
      • 校验:确认序号是否正确
  • 第三次:

    • 客户端:
      • 发送确认数据包
        • ACK+确认需要
          • 服务器的随机序号+1
    • 服务器
      • 检测:ACK是否为1
      • 检验:确认序号是否正确
  • 滑动窗口:缓存区

  • tcp-四次挥手

    • 标志位:FIN
    • 第一次
      • 客户端:
        • 发送断开请求:
          • FIN+序号
          • ACK+序号
        • 服务器端
          • 检测FIN值是否为1
          • ACK告诉之前发的数据接收到了多少
    • 第二次
      • 服务器
        • 给客户端确认数据包
          • ACK+确认编号
            • FIN对应序号+1+携带数据大小
      • 客户端:
        • 检测:ACK值
        • 检测确认序号
    • 第三次
      • 服务器端:
        • 发送断开连接的请求
          • FIN+序号
          • ACK+序号
      • 客户端
        • 数据检测
    • 第四次
      • 客户端应答服务器的关闭请求
2.函数说明
  • socket:创建套接字
    • 头文件:#include <sys/types.h> #include <sys/socket.h>
    • int socket(int domain,int type,int protocol);
      • domain: AF_INET //告诉系统使用哪个底层协议族TCP/IP用PPE_INET(ipv4),PE_INET6(ipv6)
      • type:指定服务类型,SOCK_STREAM(流,TCP),SOCK_UGRAM(报,UDP)
      • protocol:一般为0,表示使用默认协议
  • bind:将套接字绑定到一个地址
    • 头文件:#include <sys/types.h> #include <sys/socket.h>
    • int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
      • sockfd:是创建套接字返回的描述符
      • *addr:为指向一个struct sockaddr类型的结构体变量,此结构体成员用于设置要绑定的ip和端口
      • addrlen:为结构体大小
  • listen:创建监听队列
    • 头文件:#include <sys/types.h> #include <sys/socket.h>
    • int listen(int sockfd , int backlog);
      • sockfd:是创建套接字返回的描述符
      • backlog:为已完成连接队列个数
  • accept:在套接字已完成队列接受一个连接
    • int res = accept(sockfd,const struct sockaddr *addr,socklen_t addrlen);
      • sockfd:是创建套接字返回的描述符
      • *addr:为指向一个struct sockaddr类型的结构体变量,此结构体成员用于设置要绑定的ip和端口
      • addrlen:为结构体大小
3.tcp通信流程
  • 服务器端-2个文件描述符
    • 创建套接字-监听的:
      • int lfd = socket();
    • 绑定,lfd和本地的IP和端口绑定
      • bind(lfd,(struct sockaddr*))
      • struct sockaddr_in serv
    • 设置监听:
      • listen(lfd,backlog);
    • 等待并接受连接请求
      • int cfd = accpt(lfd,&client,&len);
    • 读数据
      • read(cfd,buf,sizeof(buf));
    • 发送数据
      • write(cfd,"xxx\",len)
    • 关闭文件描述符
      • close(cfd)
      • close(lfd)
  • 客户端-1个文件描述符
    • 创建套接字
      • int fd = socket();
    • 连接服务器
      • connect(fd,&servaddr,sizeof(servaddr));
    • 发送数据
      • write(fd,buf,strlen(buf));
    • 接收数据
      • read(fd,buf,sizeof(buf));
    • 关闭连接
      • close(fd);

e.g.server

#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 lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket error");
        exit(1);
    }

    // 绑定
    struct sockaddr_in serv_addr;
    // init
    memset(&serv_addr, 0, sizeof(serv_addr));
    // bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; // 地址族协议  ipv4
    serv_addr.sin_port = htons(9999);   // 本地端口, 需要转换为大端
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 0 是用本机的任意IP

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

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

    // 等待并接受连接请求
    struct sockaddr_in cline_addr;
    socklen_t clien_len = sizeof(cline_addr);
    int cfd = accept(lfd, (struct sockaddr*)&cline_addr, &clien_len);
    if(cfd == -1)
    {
        perror("accept error");
        exit(1);
    }
    
    char ipbuf[64];
    // int -> char*
    printf("cliient ip: %s, port: %d\n",
           inet_ntop(AF_INET, &cline_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
           ntohs(cline_addr.sin_port));

    // 通信
    while(1)
    {
        // 先接收数据
        char buf[1024] = {0};
        int len = read(cfd, 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(cfd, buf, strlen(buf)+1);
        }
        else if( len == 0 )
        {
            printf("client disconnect ...\n");
            break;
        }
    }

    close(lfd);
    close(cfd);

    return 0;
}

e.g.client

#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;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(9999);
    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];
        fgets(buf, sizeof(buf), stdin);
        // 发送给服务器
        write(fd, buf, strlen(buf)+1);

        // 接收服务器端的数据
        int len = read(fd, buf, sizeof(buf));
        printf("read buf = %s, len = %d\n", buf, len);
    }
    return 0;
}

4.多进程通信
  • 共享
    • 读时共享,写时复制
    • 文件描述符
    • 内存映射区–mmap
  • 父进程
    • 等待接受客户端连接 --accept
      • 有链接:创建一个子进程fork()
      • 将通信的文件描述符关闭
  • 子进程
    • 通信
      • 使用accept返回值-fd
    • 关掉监听的文件描述符:浪费资源
  • 进程个数限制
    • 受硬件限制
    • 文件描述符默认也是有上限的1024
  • 子进程资源回收
    • wait/waitpid
    • 使用信号回收
      • 信号捕捉:
        • signal
        • sigaction-推荐
      • 捕捉信号:SIGCHLD

e.g.server

#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

void do_sigchild(int num)
{
    while (waitpid(0, NULL, WNOHANG) > 0);
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int i, n;
    pid_t pid;

    //临时屏蔽sigchld信号
    sigset_t myset;
    sigemptyset(&myset);
    sigaddset(&myset, SIGCHLD);
    // 自定义信号集 -》 内核阻塞信号集
    sigprocmask(SIG_BLOCK, &myset, NULL);


    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    // 设置端口复用
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

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

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    Listen(listenfd, 20);

    printf("Accepting connections ...\n");
    while (1) 
    {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);

        // 有新的连接则创建一个进程
        pid = fork();
        if (pid == 0) 
        {
            Close(listenfd);
            while (1) 
            {
                n = Read(connfd, buf, MAXLINE);
                if (n == 0) 
                {
                    printf("the other side has been closed.\n");
                    break;
                }
                printf("received from %s at PORT %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                        ntohs(cliaddr.sin_port));

                for (i = 0; i < n; i++)
                    buf[i] = toupper(buf[i]);

                Write(STDOUT_FILENO, buf, n);
                Write(connfd, buf, n);
            }
            Close(connfd);
            return 0;
        } 
        else if (pid > 0) 
        {
            struct sigaction act;
            act.sa_flags = 0;
            act.sa_handler = do_sigchild;
            sigemptyset(&act.sa_mask);
            sigaction(SIGCHLD, &act, NULL);
            // 解除对sigchld信号的屏蔽
            sigprocmask(SIG_UNBLOCK, &myset, NULL);

            Close(connfd);
        }  
        else
        {
            perr_exit("fork");
        }
    }
    return 0;
}
5.多线程

e.g.server

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

struct s_info 
{                     //定义一个结构体, 将地址结构跟cfd捆绑
    struct sockaddr_in cliaddr;
    int connfd;
};

void *do_work(void *arg)
{
    int n,i;
    struct s_info *ts = (struct s_info*)arg;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看

    while (1) 
    {
        n = Read(ts->connfd, buf, MAXLINE);                     //读客户端
        if (n == 0) 
        {
            printf("the client %d closed...\n", ts->connfd);
            break;                                              //跳出循环,关闭cfd
        }
        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
                ntohs((*ts).cliaddr.sin_port));                 //打印客户端信息(IP/PORT)

        for (i = 0; i < n; i++) 
        {
            buf[i] = toupper(buf[i]);                           //小写-->大写
        }

        Write(STDOUT_FILENO, buf, n);                           //写出至屏幕
        Write(ts->connfd, buf, n);                              //回写给客户端
    }
    Close(ts->connfd);

    return NULL;
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    pthread_t tid;
    struct s_info ts[256];      //根据最大线程数创建结构体数组.
    int i = 0;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //创建一个socket, 得到lfd

    bzero(&servaddr, sizeof(servaddr));                             //地址结构清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                   //指定本地任意IP
    servaddr.sin_port = htons(SERV_PORT);                           //指定端口号 8000

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定

    Listen(listenfd, 128);      //设置同一时刻链接服务器上限数

    printf("Accepting client connect ...\n");

    while (1) 
    {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞监听客户端链接请求
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;

        pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
        pthread_detach(tid);                                                    //子线程分离,防止僵线程产生.
        i++;
        if(i == 256)
        {
            break;
        }
    }

    return 0;
}

代码来自黑马培训的视频

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值