[英]Send and receive file with socket
客户端在接收文件时使用以下部分代码:
void do_retr_cmd(int f_sockd){
int fd;
ssize_t nread = 0;
uint32_t fsize, fsize_tmp, total_bytes_read, size_to_receive;
char *filename = NULL, *conferma = NULL, *filebuffer = NULL;
char buf[256], dirp[256], t_buf[256];
memset(dirp, 0, sizeof(dirp));
memset(buf, 0, sizeof(buf));
memset(t_buf, 0, sizeof(t_buf));
printf("Write the name of file to download: ");
fgets(dirp, BUFFGETS, stdin)
filename = NULL;
filename = strtok(dirp, "\n");
sprintf(buf, "RETR %s", dirp);
if(send(f_sockd, buf, strlen(buf), 0) < 0){
perror("Errore durante l'invio del nome del file");
onexit(f_sockd, 0, 0, 1);
}
fsize = 0;
recv(f_sockd, t_buf, sizeof(t_buf), 0)
fsize = atoi(t_buf);
fd = open(filename, O_CREAT | O_WRONLY, 0644);
fsize_tmp = fsize;
filebuffer = (char *)malloc(fsize);
total_bytes_read = 0;
nread = 0;
for(size_to_receive = fsize; size_to_receive > 0;){
nread = read(f_sockd, filebuffer, size_to_receive);
if(nread < 0){
perror("read error on retr");
onexit(f_sockd, 0, 0, 1);
}
if(write(fd, filebuffer, nread) != nread){
perror("write error on retr");
onexit(f_sockd, 0, 0, 1);
}
size_to_receive -= nread;
}
close(fd);
fflush(stdout);
fflush(stdin);
memset(buf, 0, sizeof(buf));
recv(f_sockd, buf, 21, 0)
printf("%s", buf);
memset(buf, 0, sizeof(buf));
memset(t_buf, 0, sizeof(t_buf));
memset(dirp, 0, sizeof(dirp));
free(filebuffer);
}
服务器发送文件时使用这部分代码:
void do_server_retr_cmd(f_sockd, m_sockd){
int fd, rc;
uint32_t fsize, size_to_send;
char *filename = NULL, *other = NULL;
char buf[512], t_buf[256];
off_t offset;
struct stat fileStat;
memset(buf, 0, sizeof(buf));
memset(t_buf, 0, sizeof(t_buf));
recv(f_sockd, buf, sizeof(buf), 0)
other = NULL;
filename = NULL;
other = strtok(buf, " ");
filename = strtok(NULL, "\n");
if(strcmp(other, "RETR") == 0){
printf("Ricevuta richiesta RETR\n");
} else /* do something */
fd = open(filename, O_RDONLY);
memset(&fileStat, 0, sizeof(fileStat));
fileStat.st_size = 0;
fstat(fd, &fileStat)
fsize = fileStat.st_size;
snprintf(t_buf, 255, "%" PRIu32, fsize);
send(f_sockd, t_buf, sizeof(t_buf), 0)
offset = 0;
for (size_to_send = fsize; size_to_send > 0; ){
rc = sendfile(f_sockd, fd, &offset, size_to_send);
if (rc <= 0){
perror("sendfile");
onexit(f_sockd, m_sockd, fd, 3);
}
size_to_send -= rc;
}
close(fd);
fflush(stdout);
fflush(stdin);
memset(buf, 0, sizeof(buf));
strcpy(buf, "226 File transfered\n");
send(f_sockd, buf, strlen(buf), 0)
memset(buf, 0, sizeof(buf));
memset(t_buf, 0, sizeof(t_buf));
}
->错误检查已被省略<-
我对这两个代码有很大的疑问。 当我启动主程序时,我必须写:
1. retr
,然后按Enter
2. Write the filename to download:
我写入文件名,然后按Enter
问题在于,有时文件已正确下载,有时未下载,但文件的一部分显示在stdout(在终端上)上。
我不明白为什么我会有这种奇怪的行为。
PS:我知道我的代码很丑,但是我是C新手!
我正在Ubuntu 64位上开发并使用GCC-4.6.3(C语言)。
TCP连接为您提供了可靠的双向字节流 ,但是未保留“应用程序消息”的边界,这意味着可以在另一侧的多个recv()
接收一个send()
,而在另一侧,可以将多个send()
折叠为一个recv()
(并且您可以接收所发送的最后一个块的一部分)。 好消息是,您确实按发送顺序接收了发送的字节。
行recv(f_sockd, buf, sizeof(buf), 0);
服务器代码中的假定您在此处读取文件名,而实际上您最多可以获取256
字节的客户端发送的内容。
您需要在裸TCP之上强加某种应用程序级协议 。 一种非常简单的方法是以以下形式在文件内容之前发送文本标题:
file-size file-name\n
因此,您的服务器可以查找第一个换行符,在第一个空格处分割行,并具有期望的字节数,以及将这些字节保存到的文件名。 在换行符之后,不要忽略其余的接收缓冲区,将其保存到文件中。 这也使您可以重新使用该连接进行多次文件传输。
希望这可以帮助。
recv(f_sockd, buf, 21, 0)
printf("%s", buf);
由于没有实现控制接收和打印内容的实际协议,因此该printf
打印大量随机垃圾。 例如, printf
如何知道要打印多少字节?
我的答案的先前版本不太正确,但是这就是为什么您看到奇怪行为的原因。 您将文件大小发送为
snprintf(t_buf, 255, "%" PRIu32, fsize);
然后您会收到
recv(f_sockd, t_buf, sizeof(t_buf), 0)
但这不能保证实际读取sizeof(t_buf)
字节。 然后atoi
有时会返回不正确的大小,文件的其余部分将被视为状态消息,并在末尾显示(直到第一个空字符)。
由于recv
可能不会一次返回您要求的所有数据,因此您必须检查其返回值并可能重复调用recv
:
size_t to_recv = sizeof(t_buf);
size_t rcvd = 0;
while (to_recv > 0) {
ssize_t r = recv(f_sockd, t_buf + rcvd, sizeof(t_buf) - rcvd, 0);
if (r < 0) {
//error
}
else {
to_recv -= r;
rcvd += r;
}
}
显然,您要么必须知道要期待多少数据,要么要按照其他答案中的建议提出更好的协议(例如,寻找终止符来确定何时读取大小)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.