栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux&C语言简单实现TCP客户端下载服务器文件-TCP 粘包问题-传输层

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux&C语言简单实现TCP客户端下载服务器文件-TCP 粘包问题-传输层

目录
  • 客户端下载服务器所在目录的文件
    • TCP 粘包问题- -面试
    • 解决方案
    • recv
    • send
    • 要求
  • 代码实现
    • 服务器-----server.c
    • 客户端----client.c
    • 执行结果
  • 注意
  • 6. 非原创

客户端下载服务器所在目录的文件
TCP 粘包问题- -面试
  • TCP的底层有一个 Nagel 算法,这个算法会将一定短的时间内的

  • 发往同一个接收端的多个小的数据包

  • 组装成一个整体 发送给对方

  • 但是对方没法确定每条消息的边界,

  • 所以就导致了 接收方收到消息后无法正确的解析

解决方案
- 只要保证每次发送的数据包一样大,接收方也按照这个大小去接收
- 就不会出现粘包问题
- 每次收发的数据不要以字符数组的方式发送,
- 把要发送的数据封装成一个结构体发送给对方,对方也要使用同样的结构体去接
- 循环发送文件内容
recv
功能:
	在套接字中接收一条消息
	如果对方的socket已经关闭了,recv会返回0
send
功能:
	向套接字中发送一条消息
 	如果对方的socket已经关闭了,第一次send会返回0
             				 第二次send会报错  SIGPIPE
要求
- 客户端和服务器不要运行在同一路径
- 客户端先给服务器发送要下载的文件名
- 服务器收到文件名后,判断当前路径下是否有该文件 (open(只读)--ENOENT)
- 如果文件不存在,则发送 不存在的提示  给客户端
- 如果文件存在,也要先告诉客户端文件存在,然后
- 循环读取文件内容,发送给客户端
- 客户端新建一个文件,接收文件内容,并保存到文件中。
代码实现 服务器-----server.c
#include 

#include 
#include 

#include 

#include 
#include 
#include 

#include 

#include 
#include 
#include 

#include 

#include 
#include 

#include 
#include 

#define ERRLOG(errmsg)                                       
    do                                                       
    {                                                        
        printf("%s--%s(%d):", __FILE__, __func__, __LINE__); 
        perror(errmsg);                                      
        exit(-1);                                            
    } while (0)
//用结构体传输--防止TCP 粘包问题
typedef struct __MSG
{
    int num;
    char buff[128];
} msg_t;

int main(int argc, const char *argv[])
{
    if (3 != argc)
    {
        printf("Usage : %s  n", argv[0]);
        exit(-1);
    }

    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    //端口号  填 8888 9999 6789 ...都可以
    server_addr.sin_port = htons(atoi(argv[2]));
    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    // 3.将套接字和网络信息结构体绑定
    if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
    {
        ERRLOG("bind error");
    }

    //将套接字设置成被动监听状态
    if (-1 == listen(sockfd, 10))
    {
        ERRLOG("listen error");
    }

    //用来保存客户端信息的结构体
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr)); //清空
    socklen_t client_addr_len = sizeof(client_addr);

    int accept_fd;

    char filename[128] = {0};
    char buff[128] = {0};
    int ret = 0;
    int fd;
    msg_t msg;                      //用结构体传输--防止TCP 粘包问题
    memset(&msg, 0, sizeof(msg_t)); //清空

ACC: //重新等待客户端连接
    //阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
    printf("等待客户端n");
    accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
    if (-1 == accept_fd)
        ERRLOG("accept error");

    printf("客户端 (%s:%d) 连接成功n",
           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

RECV_filename: //重新接收
    //接收客户端发来的数据
    if (0 > recv(accept_fd, &msg, sizeof(msg_t), 0))
        ERRLOG("recv error");
    strcpy(filename, msg.buff);
    printf("要下载的文件是:[%s]n", filename);
    //只读
    if (-1 == (fd = open(filename, O_RDONLY))) //-1错误
    {
        if (errno == ENOENT)
        { //文件不存在
            //发送文件不存在的消息给客户端
            memset(&msg, 0, sizeof(msg_t)); //清空
            strcpy(msg.buff, "文件不存在");
            if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
                ERRLOG("send error");

            goto RECV_filename; //重新接收
        }
        ERRLOG("open error"); //其他错误
    }

    //文件存在也要发送 文件存在的信息给客户端
    memset(&msg, 0, sizeof(msg_t));
    strcpy(msg.buff, "文件存在");
    if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
        ERRLOG("send error");

    //循环发送文件内容
    while ((ret = read(fd, buff, 128)) > 0)
    {
        memset(&msg, 0, sizeof(msg_t));
        printf("ret= %dn", ret);
        msg.num = ret;
        strcpy(msg.buff, buff);
        if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
            ERRLOG("send error");
    }
    //发送文件结束的标志
    memset(&msg, 0, sizeof(msg_t));
    strcpy(msg.buff, "结束");
    msg.num = 0; //结束的标志
    if (0 > send(accept_fd, &msg, sizeof(msg_t), 0))
        ERRLOG("send error");

    close(fd);
    close(accept_fd);
    goto ACC; //重新等待客户端连接
    close(sockfd);

    return 0;
}
客户端----client.c
#include 

#include 
#include 

#include 

#include 
#include 
#include 

#include 

#include 
#include 
#include 

#include 

#include 
#include 

#include 
#include 

//用结构体传输--防止TCP 粘包问题
typedef struct __MSG
{
    int num;
    char buff[128];
} msg_t;

#define ERRLOG(errmsg)                                       
    do                                                       
    {                                                        
        printf("%s--%s(%d):", __FILE__, __func__, __LINE__); 
        perror(errmsg);                                      
        exit(-1);                                            
    } while (0)

int main(int argc, const char *argv[])
{
    if (3 != argc)
    {
        printf("Usage : %s  n", argv[0]);
        exit(-1);
    }

    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    //端口号  填 8888 9999 6789 ...都可以
    server_addr.sin_port = htons(atoi(argv[2]));
    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    //与服务器建立连接
    if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
    {
        ERRLOG("connect error");
    }

    char filename[128] = {0};
    char buff[128] = {0};
    msg_t msg;                      //用结构体传输--防止TCP 粘包问题
    memset(&msg, 0, sizeof(msg_t)); //清空

INPUT_filename:
    printf("下载文件名: ");
    scanf("%s", filename);
    strcpy(msg.buff, filename);
    //发--将文件名发给服务器
    if (0 > send(sockfd, &msg, sizeof(msg_t), 0))
        ERRLOG("send error");

    //收--接收文件是否存在的信息
    if (0 > recv(sockfd, &msg, sizeof(msg_t), 0))
        ERRLOG("recv error");

    //如果文件不存在 重新输入文件名
    if (0 == strcmp(msg.buff, "文件不存在"))
    {
        printf("文件不存在n");
        goto INPUT_filename; //重新发送
    }

    //文件存在 则循环接收文件内容 并写到文件里
    if (strcmp(msg.buff, "文件存在") == 0)
    {
        int dfd;
        if ((dfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664)) == -1)
            ERRLOG("open src error");

        while (0 != recv(sockfd, &msg, sizeof(msg_t), 0))
        {
            printf("msg.num = %dn", msg.num);
            if (msg.num == 0)
                break;

            if (-1 == write(dfd, msg.buff, msg.num))
                ERRLOG("write error");
        }
        close(dfd);
    }
    printf("文件[%s]下载完成n", filename);
    close(sockfd);
    return 0;
}
执行结果

注意
- 使用scanf输入的字符串 结尾没有 'n
scanf("%s", filename); 

- 如果使用 fgets() 需要手动清理换行符
fgets(filename, N, stdin);
filename[strlen(filename)-1] = '';//将结尾的 n 换成 
6. 非原创
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/881564.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号