【Linux多线程服务端编程】| 【07】TCP网络编程实例文件传输

  • 一般使用中发送消息由网络库完成,不会直接调用write或send;

1 使用TcpConnection::send需要注意

  • send返回值为void,保证将数据发送给对方,用户不必关心调用send成功发送多少字节;
  • send是非阻塞;
  • send是线程安全,原子的;
  • send(const void* mes, size_t len)可以发送任意字节序列;
  • send(const StringPiece& meg)可发送string和const char*,StringPiece可传递字符串参数的class;
  • send(Buffer*)以指针为参数,避免内存拷贝;

2 muduo文件传输实现

  • 将文件发送给对方;

【版本一:一次将文件读入内存,在send】

  • 该版本,每次建立连接啧需要读取一遍文件,内存消耗与(并发连接数x文件大小)成正比;

【版本二:解决版本一,每次发送固定大小的数据块】

  • 每次发送固定大小的数据块;
  • 若由其他程序修改该文件时,每次连接都会打开该文件,导致每个连接占用一个socket fd和file fd;则我们将整个程序都共享一个文件描述符,记录每个连接时的偏移量;
  • 若客户端故意只发起连接,不接收数据,则我们使用需要去除空闲连接并设置最大连接数;

【版本三:使用智能指针】

  • 用shared_ptr的custom deleter来减轻资源管理负担,使用FILE*生命周期和TCPconnet一样长;

为什么TcpConnectiom::shutdown没有直接关闭TCP连接
在这里插入图片描述

  • muduo将主动关闭连接分成两步:
    • 先关闭本地写端;
    • 再关闭本地读端;
  • muduo只提供shutdown,为了收发数据的完整性;
    • 若对方发送了数据,则数据再路上,不会被漏收;
  • 由于TCP fd可读可写,shutdownWrite关闭了写方向,保留读方向,若直接close则关闭读写;
    在这里插入图片描述
  • 发送完数据,即shutdownWrite,发送TCP FIN分节,对方读到0字节,后对方会关闭连接,muduo会读到0字节,后muduo关闭连接;

【shutdwon后,为什么muduo回调connection callback的时间间隔大约在round-triptime】

  • 应该是避免数据还没用传输完成;

【muduo什何时真正close socket】

  • 在TcpConnection对象析构时;

3 简易实现文件传输

server.cc

#include "server_transfer_file.h"
#include "sylar/util.h"

namespace sylar {

namespace tcp {

static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");

TransferFile::TransferFile(std::string filename, sylar::IOManager* worker
                            ,sylar::IOManager* accept_worker)
               : TcpServer(worker, accept_worker){
        setName("TransferFile Server");
        FSUtil::Realpath(filename, m_file); 

    }

void TransferFile::handleClient(Socket::ptr client) {

    m_fp = fopen(m_file.c_str(), "r");
    if(m_fp) {
        SYLAR_LOG_INFO(g_logger) << "send to: [" << client->getRemoteAddress()->toString() << "] start...";
        char buf[128] = {0};
        
        // 获取文件长度
        struct stat statbuf;
        stat(m_file.c_str(), &statbuf);
        m_len = statbuf.st_size;
        
        // 发送头部信息
        std::string tmp = m_file+ " " + std::to_string(m_len);
        client->send(tmp.c_str(), tmp.size()); 
        SYLAR_LOG_INFO(g_logger) << getName() << " send head msg: " 
            << m_file.c_str() << " " << m_len; 
        
        // 若对方接收到文件且创建好文件
        do {
            if(!client->isConnected()) {
                SYLAR_LOG_INFO(g_logger) << getName() << "close";
                return;
            }
            int rt = client->recv(buf, sizeof(buf));
            if(rt > 0) { 
                SYLAR_LOG_INFO(g_logger) << "Client Create Sucess";
                break;
            }
        }while(true);
        
        // 发送消息
        do {
            if(!client->isConnected()) {
                SYLAR_LOG_INFO(g_logger) << getName() << "close";
                return;
            }
            int rt = fread(m_buf, 1, sizeof(m_buf), m_fp);
            if(rt <= 0) break;
            rt = client->send(m_buf, rt);
            SYLAR_LOG_INFO(g_logger) << "send to: [" << client->getRemoteAddress()->toString()
                << "] size: " << rt;   
        }while(true);
        
        SYLAR_LOG_INFO(g_logger) << "send finish";
       
        fclose(m_fp);
        client->close();
    }
} 
}
}

server.h

#ifndef __SERVER_TRANSFER_FILE_H__
#define __SERVER_TRANSFER_FILE_H__

#include "sylar/log.h"
#include "sylar/tcp_server.h"
#include <string>

// 先传输文件的名称、长度,让对方能够在接收文件前先创建好文件
// 若文件

namespace sylar {

namespace tcp {


class TransferFile : public TcpServer {
public:
    TransferFile(std::string filename = "", sylar::IOManager* worker = sylar::IOManager::GetThis()
               ,sylar::IOManager* accept_worker = sylar::IOManager::GetThis()); 



protected:
    virtual void handleClient(Socket::ptr client) override;

private:
    enum Status {
        OK,
        ERROR,
        NONE
    };
private:
    size_t m_len;
    std::string m_file;
    FILE* m_fp = nullptr;
    char m_buf[64 * 1024];
};

}
}

#endif

server.main

#include "sylar/tcpServer/server_transfer_file.h"
#include <iostream>


std::string filename = "";

void run() {
    auto addr = sylar::Address::LookupAny("0.0.0.0:8033");
    std::vector<sylar::Address::ptr> addrs;
    addrs.push_back(addr);

    sylar::tcp::TransferFile::ptr tcp_server(new sylar::tcp::TransferFile(filename));
    std::vector<sylar::Address::ptr> fails;
    while(!tcp_server->bind(addrs, fails)) {
        sleep(2);
    }
    tcp_server->start();
    
}
int main(int argc, char** argv) {
    if(argc > 1) {
        filename = argv[1];
        sylar::IOManager iom(2);
        iom.schedule(run);
    }else {
        std::cout << "error" << std::endl;
    }
    return 0;
}

client.h

#ifndef __CLIENT_TRANSFER_FILE_H__
#define __CLIENT_TRANSFER_FILE_H__

#include "sylar/socket.h"
#include "sylar/address.h"

#include <memory>



namespace sylar {
namespace tcp{

class ClientTransfer {
public:
    typedef std::shared_ptr<ClientTransfer> ptr;

    ClientTransfer(sylar::Address::ptr addr);
    void start();
    ~ClientTransfer();
private:
    enum Status {
        OK,
        ERROR,
        NONE
    };
private:
    Socket::ptr m_sock;
};

}
}

#endif

client.cc

#include "client_transfer_file.h"

#include "sylar/log.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

namespace sylar {

namespace tcp {

static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");

ClientTransfer::ClientTransfer(sylar::Address::ptr addr) {
    m_sock = sylar::Socket::CreateTCPSocket();
    m_sock->connect(addr, 10);
}

void ClientTransfer::start() {
    if(m_sock->isConnected()) {
        char buf[128];
        FILE* fd;
        // 接收服务端的头部信息,创建文件
        int rt = m_sock->recv(buf, sizeof(buf));
        if(rt) {
            char name[120];
            int len = 0;    
            sscanf(buf, "%s %d", name, &len);
            SYLAR_LOG_INFO(g_logger) << name << " " << len; 
            std::string newName = "ttttt.txt";  // 本地测试使用的文件名
            fd = fopen(newName.c_str(), "w+");  
            fseek(fp, len, SEEK_SET);           // 指定文件大小
            if(!fd) return;
            if(rt == -1){
                exit(1);
            }
            
            // 发送OK提示对方可以发送文件内容
            std::string tmp = std::to_string(OK);
            int rt = m_sock->send(tmp.c_str(), tmp.size());
            SYLAR_LOG_INFO(g_logger) << "send ok: " << rt; 
            
        } 
    
        do{
            char bbuf[64 * 1024];
            rt = m_sock->recv(bbuf, sizeof(bbuf));
            SYLAR_LOG_INFO(g_logger) << "recv: " << rt;  
            if(rt <= 0) break;
            rt = fwrite(bbuf, 1, sizeof(bbuf), fd);
            if(rt <= 0) break;
        }while(true);
    }
    m_sock->close();
}

ClientTransfer::~ClientTransfer() {
    m_sock->close();
}

}
}

client.main

#include "sylar/tcpServer/client_transfer_file.h"
#include "sylar/iomanager.h"
#include <iostream>


void run() {
    auto addr = sylar::Address::LookupAny("0.0.0.0:8033");
    sylar::tcp::ClientTransfer::ptr client(new sylar::tcp::ClientTransfer(addr));
    client->start();
}
int main(int argc, char** argv) {
    sylar::IOManager iom(2);
    iom.schedule(run);

    return 0;
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jxiepc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值