C 语言中的 UDP 文件传输程序,而循环不会做任何事情



我正在尝试使用UDP,使用停止等待协议制作一个文件传输程序。客户端提示用户输入要传输的文本文件,如果存在,服务器会找到它并将其以数据包形式发送回(每次80个字符(。客户端发送文件名和服务器接收文件名的部分是有效的,但一旦我进入while(1(循环进行文件传输,就不会发生任何事情。不知道为什么会这样,任何帮助都会很棒!谢谢

Client.c

`#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <errno.h>
/*This source file lies the client side of the file transfer. Its job is to open a
socket and ask to connect with server
It should then ask user for input of the text file they wish to transfer.
Once server sends out the file in packets, the client will then place this data in an
output.txt file, viewable for the user*/
typedef struct packet{
char pktData[80]; //data in each packet
}Packet;
typedef struct frame{
int frame_kind; //ACK:0, SEQ:1 FIN:2
int countOfChar; //count of characters in a packet
int sq_no;
int ack;
Packet packet;
}Frame;
int offset = 4;
int main(){
int sockfd = 0; //socket descriptor
int countOfChar = 0; //count of characters in a packet
char buffer[80]; //data in each packet
char fileName[20];
char pktData[80];
//socklen_t addr_size;
int frame_id = 0;
int frame_id2 = 0;
Frame frame_send; //frame being sent to server (for the filename)
Frame frame_recv; //frame being received from the server (for the data)
Frame name_send; 
Frame name_recv;
int ack_recv = 1;

struct sockaddr_in serv_addr;
struct sockaddr_in new_addr;
memset(&serv_addr, '0', sizeof(serv_addr));


// Open a socket
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
printf("Error creating socket");
return 1;
}
// Server address structure
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(20000);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
socklen_t addr_size = sizeof(serv_addr);
//Sending File name
printf("nConnected to server successfully!nEnter the file name: ");
scanf("%[^n]%*c",fileName);
printf("n");
char buff[20];
strcpy(buff, fileName);
sendto(sockfd, &buff, sizeof(buff), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

int new_addr_size = sizeof(new_addr);
//int addr_size = sizeof(serv_addr);
//writing file into output.txt
FILE *fp = fopen("output.txt", "ab");
//loop until theres no more to read
while(1){
int f_recv_size = recvfrom(sockfd, &frame_recv, sizeof(Frame), 0, (struct sockaddr*)&serv_addr, &addr_size);
if (f_recv_size > 0 && frame_recv.frame_kind == 1 && frame_recv.sq_no == frame_id){
fwrite(frame_recv.packet.pktData, 1, sizeof(frame_recv.packet.pktData), fp);
//printf("[+]Frame %d received with %lu data bytesn",frame_recv.sq_no, sizeof(frame_recv.packet.pktData));
printf("Frame Received");

frame_send.sq_no = 0;
frame_send.frame_kind = 0;
frame_send.ack = frame_recv.sq_no + 1;
sendto(sockfd, &frame_send, sizeof(frame_send), 0, (struct sockaddr*)&serv_addr, addr_size);
printf("[+]Ack Sentn");
}else{
printf("[+]Frame Not Receivedn");
}
frame_id++; 
}
close(sockfd);
return 0;
}

服务器.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*This source file lies the server side of the file transfer. Its job is to listen
for a connection and connect with client when prompted.
It should then read the file with the txt file name and start sending out packets of 
80 bytes each with the data of the given file name.*/
typedef struct packet{
char pktData[80]; //data in each packet
}Packet;
typedef struct frame{
int frame_kind; //ACK:0, SEQ:1 FIN:2
int countOfChar; //count of characters in a packet
int sq_no;
int ack;
Packet packet;
}Frame;
int main(){
unsigned int size = 0;
int listenfd = 0;
int connfd = 0;
char file_buff[20];
char buffer[80];
char FLAG[] = "NF"; //No file flag
int frame_id = 0;
int frame_id2 = 0;
Frame name_recv;
Frame name_send;
Frame frame_recv;
Frame frame_send;
int ack_recv;
struct sockaddr_in server_addr, client_addr;
char sendBuff[1025];
size = sizeof(client_addr);
listenfd = socket(AF_INET, SOCK_DGRAM, 0);  //listening descriptor 
printf("Server initiated...n");
memset(&server_addr, '0', sizeof(server_addr));
memset(sendBuff, '0', sizeof(sendBuff));
//Local address structure
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(20000);
//bind to local address
bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

// Receiving File Name
recvfrom(listenfd, &file_buff, sizeof(file_buff), 0, (struct sockaddr*)&client_addr, &size);
printf("Filename requested by client = %sn",file_buff);
if (access(file_buff, F_OK) != -1 ) { 
printf("File exist!n");
FILE *fp = fopen(file_buff,"rb"); 
while(1){

if(ack_recv == 1){
frame_send.sq_no = frame_id;
frame_send.frame_kind = 1;
frame_send.ack = 0;     

fscanf(fp, "%s ", buffer);
strcpy(frame_send.packet.pktData, buffer);
sendto(listenfd, &frame_send, sizeof(Frame), 0, (struct sockaddr*)&client_addr, sizeof(client_addr));
//printf("[+]Frame %d Sent with %lu data bytesn",frame_send.sq_no, strlen(frame_send.packet.pktData));
printf("Frame Sent");
}
int addr_size = sizeof(server_addr);
int f_recv_size = recvfrom(listenfd, &frame_recv, sizeof(frame_recv), 0 ,(struct sockaddr*)&client_addr, &size);

if( f_recv_size > 0 && frame_recv.sq_no == 0 && frame_recv.ack == frame_id+1){
printf("[+]Ack Receivedn");
ack_recv = 1;
}   
else{
printf("[-]Ack Not Receivedn");
ack_recv = 0;
}   

frame_id++;     
}
}
else {
printf("File doesn't existn");
write(listenfd, FLAG, sizeof(FLAG));
close(listenfd);
return 0;
}
close(listenfd);
return 0;
}

很抱歉在高级中出现了所有混乱和不必要的变量,我已经尝试了很多选项,但都不起作用。

终端中服务器的响应

客户端在终端中的响应

第一个问题是,在尝试读取ack_recv之前,您永远不会在服务器程序中初始化它。读取一个未初始化的变量,如果该变量的地址未被占用,则会触发未定义的行为。

至少,读取了除1之外的值,因此服务器正在等待来自客户端的读取,而该读取从未到来,因为客户端正在服务器上等待。您需要将ack_recv初始化为1,以便服务器发送文件的第一部分。

然而,代码存在的问题比这更多。

主要问题是它假定没有数据包丢失。UDP不能保证传递。因此,如果一个包裹真的丢失了,其中一方将永远等待一个没有到达的包裹。读取时,每侧应使用select,以便在等待时允许超时。如果超时触发,它应该假设最后发送的数据包丢失并重新发送

对于客户端来说,这也意味着如果ACK丢失,它可以从服务器获得给定序列号的多个副本。因此,在接收到该序列号之前,它不应该递增frame_id

两边还有一个无限循环,因为有一个while (1)循环,里面没有breakreturnexit。服务器需要知道何时停止发送,并且需要某种方式让客户端知道何时停止。

此外,作为一般规则,在调试网络程序时,应该使用tcpdump或wireshark来跟踪双向传输的数据包。

最新更新