最近入职培训中需要写一个Linux下的C/S网络文件传输工具,在实现的过程中,遇到了一些坑,在这里 做个总结:
如何判断文件接收结束
在设计初期,我的思路是客户端先将要下载的文件路径发给服务器,服务器判断文件是否存在后再执行相应的下发。
文件服务器先read本地文件,然后send文件,如此循环发送。等到read的返回值为0,则表示文件发送结束,跳出循环。文件客户端通过while(recv(sockfd, recvbuf, sizeof(recvbuf), 0) > 0)循环来接收服务器发送过来的文件。但是问题来了,当服务器发送完毕跳出循环,但是客户端并不知道,tcp会一直连接等待数据,udp可能丢失部分数据也不知道。至此,服务器怎样告知客户端文件已经发送结束?
解决思路:在发送文件内容之前,定义一个结构体,将文件名、文件数据,以及文件数据的大小,还有一些你自己想要的一些信息(比如文件md5校验码等)放在结构体中,一并发送给客户端。
如何udp多线程
由于udp服务器不需要accept(),因此也没有link_id(连接的客户端id),在服务器文件接收或下发文件时需要知道当下与之通信的客户端的标识符,由于udp是无连接的,并且sockfd = socket()也不能起到标识客户端的作用。
而recvfrom和sendto函数中需要标识符来表示通信中的客户端,我们发现,client_addr起到了这个效果:
struct sockaddr_in client_addr; //客户端地址结构
因此我们在udp连接方式下创建线程时,不能传参数 sockfd,而是将client_addr这个参数传出,到相应的线程函数里,在相应的线程函数里处理接收或发送流程。(在tcp中我们是将link_id=accept()这个标识符传出的)
线程创建:pthread_create(&tcp_send_tid, NULL,udp_process_client_send, (void *)&client_addr);
线程函数:
/** @fn void *udp_process_client_send(void * arg)
* @brief 服务器处理客户端UDP下载文件线程
* @param arg 参数同步,线程创建的时候.
* @return 无.
*/
void *udp_process_client_recv(void * arg)
{
pthread_detach(pthread_self()); //设置分离属性
int udp_client_sock = *((int *)(arg)); //参数传进来,同步client_sock
int send_len;
int fd = open(filepath, O_RDONLY,0666); //只读打开文件
if (fd < 0)
{
perror("file_send_thread");
return NULL;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; //Address Family
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_LOOPBACK;0.0.0.0
server_addr.sin_port = htons(PORT); //Port number
socklen_t addrlen = sizeof(server_addr);
/**********发送文件************/
char buf[BUFF_SIZE];
int buf_len;
while ((buf_len = read(fd, buf, sizeof(buf))) > 0)
{
if((send_len=sendto(udp_client_sock, buf, buf_len, 0,(struct sockaddr*)&server_addr, addrlen))<0)
{
printf("send failed ! \n");
break;
}
printf("send_len: %d \n",send_len);
bzero(buf,BUFF_SIZE);
}
printf("udpsend finished!\n");
close(fd); //关闭文件
}