美文网首页
cpp网络基础

cpp网络基础

作者: 简书网abc | 来源:发表于2024-07-07 01:18 被阅读0次

1, 网络字节序转换

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

int main(int argc, char *argv[]) {
    // 使用无符号字符类型, 否则会越界.
    unsigned char buf[4] = {192, 168, 1, 2};
    printf("buf: {%d, %d, %d, %d}\n", *buf, *(buf+1), *(buf+2), *(buf+3));

    // buf指针步长原本为1字节, 转为(unsigned int *),
    // 步长变为4字节, 再通过*取值得到一个整型
    unsigned int num = *(unsigned int *)buf;
    printf("num: %d\n", num);

    // 小端转大端字节序,即本机字节序转网络字节序
    unsigned int sum = htonl(num);
    unsigned char *p = (unsigned char *)&sum;
    printf("p: {%d, %d, %d, %d}\n", *p, *(p+1), *(p+2), *(p+3));
    // output: p: {2, 1, 168, 192}  // 输出进行了翻转

    // short占用两个字节,共16个比特位,能表示的数为2^16,总共65536.
    unsigned short a = 0x0102;
    unsigned short b = htons(a);
    printf("b=%x\n", b);  // C语言中%X的意思是以十六进制数形式输出整数

    return 0;
}

2, 点分十进制串与网络大端数据互转

#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
    char buf[] = "192.168.1.4"; // 点分十进制串
    unsigned int num = 0;
    // 将点分十进制串转换为网络大端数据.
    inet_pton(AF_INET, buf, &num);
    unsigned char * p = (unsigned char *)&num;
    printf("(%d, %d, %d, %d)\n", *p, *(p+1), *(p+2), *(p+3));

    printf("sizeof num: %d\n", sizeof num);
    // output: sizeof num: 4  // 得4个字节

    char ip[16] = "";
    // 网络大端数据转为点分十进制.
    const char *resStr = inet_ntop(AF_INET, &num, ip, 16);
    printf("res: (%s)", resStr);
    return 0;
}

3,tcp客户端代码

#include <arpa/inet.h>
#include <stdio.h>
#include <cstdlib>
#include <sys/unistd.h>
#include <cstring>
int main(int argc, char *argv[]) {
    //1, 创建套接字
    int sock_fd;
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(sock_fd == -1)
    {
        perror("socket");
        exit(0);
    }
    //2, 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    // 点分十进制字符串 -> 网络大端整形数据
    inet_pton(AF_INET, "192.168.1.101", &addr.sin_addr.s_addr);
    int ret = connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect");
        exit(0);
    }

    //3, 通过socket读写数据
    while(1)
    {
        const char* pt = "你好, 服务器...\n";
        // 发送数据
        write(sock_fd, pt, strlen(pt)+1);
        char buf[1024] = "";
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(sock_fd, buf, n);
        n = read(sock_fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
        write(STDOUT_FILENO, "\n", 1);
    }
    // 4. 关闭文件描述符
    close(sock_fd);
    return 0;
}

4, tcp服务端单进程版

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/unistd.h>
#include <cstdlib>
#include <cstring>
int main(int argc, char *argv[]) {
    // 1. 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        exit(0);
    }
    // 2. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);
    addr.sin_addr.s_addr = INADDR_ANY;  // 代表绑定的是一个通配地址.
    // 绑定一个固定的地址
    // inet_pton(AF_INET, "192.168.1.101", &addr.sin_addr.s_addr);
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }

    // 3. 监听
    ret = listen(lfd, 128);
    if(ret == -1)
    {
        perror("listen");
        exit(0);
    }

    // 4. 等待并接受客户端的连接
    struct sockaddr_in addrcli;
    int clilen = sizeof(addrcli);
    int cfd = accept(lfd, (struct sockaddr*)&addrcli, &clilen);
    char ipbuf[16];
    printf("客户端的IP: %s, 端口: %d\n",
           inet_ntop(AF_INET, &addrcli.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
           ntohs(addrcli.sin_port));

    if(cfd == -1)
    {
        perror("accept");
        exit(0);
    }

    // 5. 通信
    while(1)
    {
        // 收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        // 这个函数是阻塞的
        int len = read(cfd, buf, sizeof(buf));
        printf("接收的数据, 客户端说: %s\n", buf);

        if(len == 0)    // 返回0,表示断开了连接
        {
            printf("客户端已经断开了连接...\n");
            break;
        }

        // 回复数据
        write(cfd, buf, len);
    }

    // 6. 关闭文件描述符
    close(cfd);
    close(lfd);
    return 0;
}

5, tcp服务端单进程简版

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/unistd.h>
#include <cstring>
#include "lib/wrap.h"
/**
 * 滑动窗口
 * 1, mtu 1500 描述网卡最大发包字节长度
 * 2, mss 1024 建立连接时会有(前两次会有,最大报文数据长度)
 * 3, win 6144 描述本机窗口大小,即缓存区可接受的字节大小.
 *
 * @param argc
 * @param argv
 * @return
 */
int main(int argc, char *argv[]) {
    int lfd = Tcp4bind(8001, NULL);
    // 3. 监听
    Listen(lfd, 128);

    // 4. 等待并接受客户端的连接
    struct sockaddr_in addrcli;
    int clilen = sizeof(addrcli);
    int cfd = Accept(lfd, (struct sockaddr*)&addrcli, &clilen);
    char ipbuf[16];
    printf("客户端的IP: %s, 端口: %d\n",
           inet_ntop(AF_INET, &addrcli.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
           ntohs(addrcli.sin_port));

    // 5. 通信
    while(1)
    {
        // 收数据
        char buf[10240];
        bzero(buf, sizeof(buf));
        // 这个函数是阻塞的
        int len = Readline(cfd, buf, sizeof(buf));
        printf("接收的数据[%d]个, 客户端说: [%s]\n", len,buf);

        if(len == 0)    // 返回0,表示断开了连接
        {
            printf("客户端已经断开了连接...\n");
            break;
        }

        // 回复数据
        Write(cfd, buf, len);
    }

    // 6. 关闭文件描述符
    Close(cfd);
    Close(lfd);
    return 0;
}

6, tcp服务端多进程版

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/unistd.h>
#include <cstring>
#include <cstdlib>
#include <sys/wait.h>
#include "lib/wrap.h"
/**
 * 滑动窗口
 * 1, mtu 1500 描述网卡最大发包字节长度
 * 2, mss 1024 建立连接时会有(前两次会有,最大报文数据长度)
 * 3, win 6144 描述本机窗口大小,即缓存区可接受的字节大小.
    父进程阻塞在accept() 函数上等待客户端的连接, 
突然客户端断开了连接, 服务器端的子进程随之退出,
    服务器端的子进程给父进程发信号 SIGCHLD , 
在父进程中注册了信号捕捉, 当前信号产生之后, 父进程开始处理,
 因为信号的优先级很高, 因此会中断进程原来的执行逻辑 ===> 
父进程的accept() 阻塞被终断了
        1. 处理信号 --> 回收子进程
        2. 当前这一轮回收完毕, 退出回收函数
        3. 程序执行回到被信号中断的位置 ==> accept()
            1). 程序就不会继续阻塞了, 直接返回-1, 错误号 errno == EINTR
 * @param argc
 * @param argv
 * @return
 */

int working_in_child(int cfd);  // 子进程读写函数
void free_process(int sig);     // 父进程注册的回收函数

int main(int argc, char *argv[]) {
    // 0, 阻塞 SIGCHLD信号,
    sigset_t set;
    sigemptyset(&set);  // 清空并初始化信号集
    sigaddset(&set, SIGCHLD);   // 添加一个信号到集合
    // 进行信号阻塞操作, 确保该信号在注册回调函数后进行处理. 
// 防止子进程提前退出未处理信号回调函数.
    sigprocmask(SIG_BLOCK, &set, NULL);

    // 1,创建监听套接字
    int lfd = Tcp4bind(8001, NULL);
    // 2. 监听
    Listen(lfd, 128);
    // 3, 循环等待并接受客户端的连接.
    while (1) {
        struct sockaddr_in addressCli;
        int clientLen = sizeof(addressCli);
        int cfd = Accept(lfd, (struct sockaddr*)&addressCli, &clientLen);
        char stringIpBuf[16];
        printf("客户端的IP: %s, 端口: %d\n",
               inet_ntop(AF_INET, &addressCli.sin_addr.s_addr, stringIpBuf, sizeof(stringIpBuf)),
               ntohs(addressCli.sin_port));

        // 连接建立,进行通信
        // 创建子进程, 使用子进程同客户端进行通信
        pid_t pid = fork();
        if (pid == 0) { // 这里为子进程
            // 和客户端通信
            // 关闭监听文件描述符
            close(lfd);
            working_in_child(cfd);
        } else {
            // 关闭读写文件描述符
            close(cfd);
            // 回收
            // 注册信号回调
            struct sigaction act;
            act.sa_flags = 0;
            act.sa_handler = free_process;
            sigemptyset(&act.sa_mask);   // 将信号集初始化并清空
            sigaction(SIGCHLD, &act, NULL);
           // 进行解除信号阻塞操作,后面才处理信号回调,
            sigprocmask(SIG_UNBLOCK, &set, NULL); 
        }
    }
    return 0;
}

// 子进程与客户端通信的函数
int working_in_child(int cfd) {
    while(1)
    {
        // 收数据
        char buf[1024];
        bzero(buf, sizeof(buf));
        // 这个函数是阻塞的
        int len = Read(cfd, buf, sizeof(buf));
        printf("cfd: [%d], 接收的数据[%d]个, 客户端说: [%s]\n",cfd, len,buf);

        if(len == 0)    // 返回0,表示断开了连接
        {
            printf("客户端cfd: [%d]已经断开了连接...\n", cfd);
            break;
        }

        // 回复数据
        Write(cfd, buf, len);
    }

    // 6. 关闭文件描述符
    Close(cfd);
    exit(0);
    return 0;
}

// 父进程回收子进程的函数
void free_process(int sig){
    pid_t pid;
    while (1) {
        pid = waitpid(-1, NULL, WNOHANG);
        if( pid <= 0 ) {
            // 小于0 子进程全部退出了, 等于0 没有进程退出
            break;
        } else {
            printf("处理子进程child pid = [%d]\n", pid);
        }
    }
}

7, tcp服务端多线程版

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/unistd.h>
#include <cstring>
#include <cstdlib>
#include <sys/wait.h>
#include <pthread.h>
#include "lib/wrap.h"
/**
 * 滑动窗口
 * 1, mtu 1500 描述网卡最大发包字节长度
 * 2, mss 1024 建立连接时会有(前两次会有,最大报文数据长度)
 * 3, win 6144 描述本机窗口大小,即缓存区可接受的字节大小.
    父进程阻塞在accept() 函数上等待客户端的连接,
    突然客户端断开了连接, 服务器端的子进程随之退出,
    服务器端的子进程给父进程发信号 SIGCHLD , 在父进程中注册了信号捕捉, 当前信号产生之后,
    父进程开始处理, 因为信号的优先级很高,
    因此会中断进程原来的执行逻辑 ===> 父进程的accept() 阻塞被终断了
        1. 处理信号 --> 回收子进程
        2. 当前这一轮回收完毕, 退出回收函数
        3. 程序执行回到被信号中断的位置 ==> accept()
            1). 程序就不会继续阻塞了, 直接返回-1, 错误号 errno == EINTR
 */

typedef struct c_info
{
    int cfd;
    struct sockaddr_in clientAddress;
}CINFO;

void* working(void* arg);

int main(int argc, char *argv[]) {
    /*if(argc < 2){
        printf("argc < 2 ? , \n ./a.out 8001 \n");
        return 0;
    }
    short port = atoi(argv[1]);*/

    // 设置线程分离属性.
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 1,创建监听套接字
    int lfd = Tcp4bind(8001, NULL);
    // 2. 监听
    Listen(lfd, 128);
    CINFO *info;
    while (1) {
        struct sockaddr_in clientAddress;
        socklen_t len = sizeof(clientAddress);
        int cfd = Accept(lfd, (struct sockaddr*)&clientAddress, &len);

        // 连接建立, 通信
        // 子线程通信, 创建子线程
        // 数据初始化
        // 需要从数组中找一个空闲的元素, 初始化, 传给子线程
        pthread_t pthreadId;
        info = (CINFO *)malloc(sizeof(CINFO));
        info->cfd = cfd;
        info->clientAddress = clientAddress;

        printf("\n\n\n客户端ip信息clientAddress->ptr: %p\n", &clientAddress);
        printf("info->ptr: %p\n", info);
        // attr 设置线程分离属性.
        pthread_create(&pthreadId, &attr, working, info);

        // 设置线程分离
//        pthread_detach(pthreadId);

    }

    return 0;
}

// 子进程和客户端的通信函数
void* working(void* arg)
{
    CINFO *info = (CINFO *)arg;

    char stringIpBuf[16];
    printf("working: cfd:[%d], 客户端的IP: %s, 端口: %d\n",
           info->cfd,
           inet_ntop(AF_INET, &(info->clientAddress.sin_addr.s_addr), stringIpBuf, sizeof(stringIpBuf)),
           ntohs(info->clientAddress.sin_port));

    while(1)
    {
        // 收数据
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        // 这个函数是阻塞的
        int len = Read(info->cfd, buf, sizeof(buf));

        if(len < 0) {
            perror("数据读取有误");
            break;
        } else if(len == 0) {
            printf("客户端已经断开了连接 -> cfd: %d\n", info->cfd);
            break;
        } else {
            printf("接收的数据长度: [%d], 客户端说: %s\n", len, buf);
            // 回复数据
            write(info->cfd, buf, len);
        }
    }

    close(info->cfd);
    free(info);
    return 0;
}

相关文章

网友评论

      本文标题:cpp网络基础

      本文链接:https://www.haomeiwen.com/subject/exyncjtx.html