Linux实现远程通信——文件传输(服务端)

该文描述了一个文件传输的过程,客户端和服务端通过Json对象进行通信,判断用户操作并执行相应函数。在传输文件时,检查接收方是否在线,若在线则创建新线程进行文件传输,涉及套接字创建、缓冲区设置、地址重用等技术。传输过程中,通过设置接收和发送缓冲区大小,以及使用accept接收连接请求,确保数据安全传输。若连接超时,则返回超时信息。最后,文件传输完成后关闭相关套接字。
摘要由CSDN通过智能技术生成

文件传输

 在创建Json对象的数据后,客户端和服务端就可以依据这个类型的数据进行通信了。我们通过解析Json对象val中的cmd指令,也就是命令,来判断客户端是想要进行什么操作(用户注册?登录?聊天?),从而执行对应的函数操作。

if(cmd == "send_file"){
        server_send_file(bev,val);   //执行传输文件对应的函数
}

 传输文件时,首先判断发送对象是否在线(此时已经是朋友了),直接获取好友的bev,如果发送对象不在线,那么创建Json对象,告知客户端,**好友不在线。**返回的无格式Json数据如下:

{"cmd":"send_file_reply","result":"offline"}

 如果好友在线,那么就新创建一个线程,并线程分离。创建线程时,执行对应的回调函数。下面是回调函数的功能:首先创建socket套接字s1,用来发送和接收数据。然后允许地址重用,即SO_REUSEADDR,这样去掉wait_time。

int sockfd = socket(AF_INET, SOCK_STREAM, 0);           //IPVE,流式套接字
if(sockfd == -1){
        return;
}
int opt = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));    //允许地址重复用

 设置当前线程socket的接收缓冲区大小和发送缓冲区大小(setsockopt)为1024*1024(1M),即每次发送和接收1M的数据。接着将创建的socket套接字绑定bind到新端口上去(这里用的是8080端口),然后listen监听该套接字,这样在传输数据时,就不用服务端来处理,而是新线程来处理。利用accept来接收登录者客户端和好友客户端的连接请求,并为他们创建各自的通信套接字s2、s3。
​ 在recv接收完登录者客户端发送的数据后,将其放入自定义的缓冲区buf。然后将buf中的数据发送send给好友客户端,每次发送完数据后清空buf缓冲区。最后传输完毕就关闭登录者客户端和好友客户端的通信套接字s2、s3,最后关闭文件服务器用来监听数据的套接字s1。

//接收缓冲区
int nRecvBuf=MAXSIZE;
setsockopt(sockfd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=MAXSIZE;
setsockopt(sockfd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));

struct sockaddr_in server_addr,client_addr;
memset(&server_addr,0,sizeof(server_addr));             //对server_addr清零
server_addr.sin_family = AF_INET;                       //IPV4传输
server_addr.sin_port = htons(port);                     //因为在公网(网络)上传输,所以要转换为网络字节序
server_addr.sin_addr.s_addr = inet_addr(IP);            //服务端地址
bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));       //绑定地址
listen(sockfd,10);                                      //将主动套接字改为被动套接字,接受传入连接请求,挂起连接队列的最大长度为10

int len = sizeof(client_addr);
//接受发送客户端的连接请求
*from_fd = accept(sockfd,(struct sockaddr *)&client_addr,(socklen_t *)&len);    //accpet监听sockfd,当有连接请求,创建新的套接字并返回新的文件描述符
int f_fd = *from_fd;                                                    //因为from_fd是局部变量,用f_fd记录其值,避免from_fd在程序运行完后被释放
//接受接收客户端的连接请求                                              //原文件描述符不受影响
*to_fd = accept(sockfd,(struct sockaddr *)&client_addr,(socklen_t *)&len);
int t_fd = *to_fd;

 由服务端(不是文件服务端)创建Json对象,创建Json对象,对其赋值,然后利用Json::FastWriter封装成string格式的文本信息即可实现Json数据的传输。返回的无格式Json数据如下:

{"cmd":"send_file_port_reply","port":"8080","length":"10000","filename":"xxx.txt"}

 然后等待登录者客户端连接文件服务器(此时线程执行函数中监听登录者客户端的连接请求),如果超过10s没有连接,就取消线程。

/*等待发送客户端 连接 文件服务器*/
int count = 0;
while(from_fd <= 0){                    //通过判断from_fd是否为0,来判断是否发送客户端是否连接,如果连接成功,fd > 0
        count++;
        usleep(100000);                 //0.1ms 
        if(count == 100){               //如果10s,都没有连接,就取消线程并返回连接超时
                pthread_cancel(send_file_thread.native_handle());
                v.clear();
                v["cmd"] = "send_file_reply";
                v["result"] = "timeout1";
                reply = Json::FastWriter().write(v);                                       
                if(bufferevent_write(bev,reply.c_str(),strlen(reply.c_str()))){     
                        cout << "bufferevent_write error" << endl;
                }
                return;
        }
}

并且通过Json::Value创建Json对象,对其赋值,然后利用Json::FastWriter封装成string格式的文本信息,告知客户端登录者客户端连接超时。

{"cmd":"send_file_reply","result":"timeout1"}

好友客户端也同理,这里我直接放代码:

{"cmd":"send_file_port_reply","port":"8080","length":"10000","filename":"xxx.txt"}
/*等待接收客户端 连接 文件服务器*/
count = 0;
while(to_fd <= 0){                    //通过判断to_fd是否为0,来判断接收客户端是否连接,如果连接成功,fd > 0
        count++;
        usleep(100000);
        if(count == 100){               //如果10s,都没有连接,就取消线程并返回连接超时
                pthread_cancel(send_file_thread.native_handle());
                v.clear();
                v["cmd"] = "send_file_reply";
                v["result"] = "timeout2";
                reply = Json::FastWriter().write(v);                                  
                if(bufferevent_write(bev,reply.c_str(),strlen(reply.c_str()))){     
                        cout << "bufferevent_write error" << endl;
                }
                return;
        }
}

超时返回:

{"cmd":"send_file_reply","result":"timeout2"}

 如果都连接成功,即按照线程执行函数中处理方式:接收登录者客户端的数据储存到buf,然后将buf中的数据发送给好友客户端。

char buf[MAXSIZE]={0};                                  //每次接收1M的数据
size_t size,sum=0;
while(1){
        size = recv(f_fd,buf,MAXSIZE,0);                //将发送客户端的数据放入buf中
        if(size < 0 ){                                  //客户端异常退出或者异常发生,则退出
                perror("recv");
                break;
        }
        else if(size == 0){                             //客户端退出
                cout << "客户端已经断开" << endl;
                break;
        }
        sum += size;
        send(t_fd,buf,size,0);                          //将得到的size大小的数据发送给接收客户端
        if(sum >= length){                              //如果传输的总大小 >= 文件大小,表明文件传输完毕,停止传输
                cout << "文件发送完成" << endl;
                break;
        }
        memset(buf,0,MAXSIZE);                          //接受完一次数据就清空缓存
}
//传输完毕,关闭,防止下次调用时出现 地址被占用的问题
close(f_fd);
close(t_fd);
close(sockfd);

到这里,服务端的所有功能已经全部实现!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值