基于tcp通信在客户端上下载服务器的某个文件的小练习(附带粘包问题及解决方案)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

一个小练习题


提示:以下是本篇文章正文内容,下面案例可供参考

一、过程

服务器:创建套接字--填充网络信息结构体--绑定--监听--阻塞等待(accept)-- 查看服务器是否有要下载的文件若有则返回有并进行读取和发送,没有则返回无循环接收新的正确的文件名。

客户端:创建套接字-- 填充网络信息结构体--连接(connect)-- 发送要下载的文件名,接收服务器回复,如果没有文件则重新发送新的文件名。

注意事项:TCP的粘包问题 :

        TCP底层有一个 Nagle 算法,作用是将一定短的时间内发往同一个接收端的 ,多个小的数据包组装成一个整体,一起发给接收方 ,但是接受方没法区分数据的类型,就可能导致有冲突的现象,例如想在文件下载完成后给客户端发一个结束的标志,但该标志可能会被当作正文接收。

解决方法:

        1.加一个延时,加一点延时就能解决这个问题,但是在服务器上加延时不太好,所以不能采用该方法。

         2.定长发送:每次发送的数据都是固定长度的,这样可以避免粘包,但随之而来的问题是定长发送后,如果最后一次发送的数据没有规定长度这么多怎么办,解决这一问题的其中一种办法就是定义一个结构体,结构体中定义一个数来记录发送的际有效长度。

二、代码实现

1.服务器

代码如下(示例):

#include <head.h>

typedef struct MSG {
    char buff[128];
    int count;
} msg_t;

int main(int argc, const char* argv[]) {
    if (3 != argc) {
        puts("usage : ./xxx ip prot");
        exit(-1);
    }

    int sockfd, acceptfd, nbytes, fd;
    msg_t msg;

    if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0))) {
        PRINT_ERR("socket error");
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
        PRINT_ERR("bind error");
    }

    if (-1 == listen(sockfd, 5)) {
        PRINT_ERR("listen error");
    }

    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);

    while (1) {
        if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&clientaddr,
                                     &clientaddr_len))) {
            PRINT_ERR("accept error");
        }
        printf("客户端[%s:%d]连接成功\n", inet_ntoa(clientaddr.sin_addr),
               ntohs(clientaddr.sin_port));
        while (1) {
            memset(&msg, 0, sizeof(msg));
            if (-1 == (nbytes = recv(acceptfd, &msg, sizeof(msg), 0))) {
                PRINT_ERR("recv error");
            } else if (0 == nbytes) {
                printf("客户端[%s:%d]断开连接\n",
                       inet_ntoa(clientaddr.sin_addr),
                       ntohs(clientaddr.sin_port));
                break;
            }

            if (-1 == (fd = open(msg.buff, O_RDONLY))) {
                if (errno == ENOENT) {  
                    memset(msg.buff, 0, 128);
                    strcpy(msg.buff, "文件不存在");
                    if (-1 == send(acceptfd, &msg, sizeof(msg), 0)) {
                        PRINT_ERR("send error");
                    }
                } else {
                    PRINT_ERR("open error");
                }
            } else {
                memset(msg.buff, 0, 128);
                strcpy(msg.buff, "找到文件,准备下载");
                if (-1 == send(acceptfd, &msg, sizeof(msg), 0)) {
                    PRINT_ERR("send error");
                }
            }

            while (1) {
                memset(msg.buff, 0, 128);
                if (-1 == (msg.count = read(fd, msg.buff, 128))) {
                    PRINT_ERR("read error");
                }
                if (-1 == send(acceptfd, &msg, sizeof(msg), 0)) {
                    PRINT_ERR("send error");
                }
                if (0 == msg.count) {
                    close(fd);
                    break;
                }
            }
        }
        close(acceptfd);
    }
    close(sockfd);
    return 0;
}

2.客户端

代码如下(示例):

#include <head.h>

typedef struct MSG {
    char buff[128];
    int count;
} msg_t;

int main(int argc, const char* argv[]) {
    if (3 != argc) {
        puts("usage : ./xxx ip prot");
        exit(-1);
    }

    int sockfd, nbytes;
    char buff[128] = {0};
    msg_t msg;
    msg.count = 0;

    if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0))) {
        PRINT_ERR("socket error");
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {
        PRINT_ERR("connect error");
    }
    puts("已连接到服务器");
    while (1) {
        while (1) {
            printf("请输入要下载的文件\n");
            fgets(msg.buff, 128, stdin);
            msg.buff[strlen(msg.buff) - 1] = '\0';
            
            strcpy(buff, msg.buff);
            if (-1 == send(sockfd, &msg, sizeof(msg), 0)) {
                PRINT_ERR("send error");
            }

            memset(&msg, 0, sizeof(msg));
            if (-1 == recv(sockfd, &msg, sizeof(msg), 0)) {
                PRINT_ERR("recv error");
            }

            if (!strcmp(msg.buff, "找到文件,准备下载")) {
                printf("准备下载\n");
                break;
            } else if (!strcmp(msg.buff, "文件不存在")) {
                printf("文件不存在,请重新输入\n");
            }
        }
        int fd = open(buff, O_WRONLY | O_CREAT | O_TRUNC, 0666);
        if (-1 == fd) {
            PRINT_ERR("open error");
        }

        while (1) {
            memset(&msg, 0, sizeof(msg));
            if (-1 == recv(sockfd, &msg, sizeof(msg), 0)) {
                PRINT_ERR("recv error");
            }
            if (0 == msg.count) {
                break;
            }

            if (-1 == write(fd, msg.buff, msg.count)) {
                PRINT_ERR("write error");
            }
        }
        close(fd);
    }
    return 0;
}


总结

        TCP通信的流程不变,唯一需要处理的只有数据的解析,即如何判断服务器是否有要下载的文件,这时就需要对open这个函数的灵活使用,其返回值为 -1 时,错误码中ENOENT就可以判断文件是否存在,如下图:

另外一点就是在判断结束时发的信息可能粘包,所以定义一个结构体定长发送来解决这个问题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值