文件传输
在创建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);
到这里,服务端的所有功能已经全部实现!