C++ 使用C/C+;在Linux的socket编程中发送和接收文件+;(GCC/G+;+;)
我想使用套接字和能够发送和接收文件的C/C++语言在Linux上实现一个客户机-服务器体系结构。有没有什么图书馆可以让这项任务变得简单?任何人都可以提供一个例子吗?做一个C++ 使用C/C+;在Linux的socket编程中发送和接收文件+;(GCC/G+;+;),c++,c,linux,sockets,network-programming,C++,C,Linux,Sockets,Network Programming,我想使用套接字和能够发送和接收文件的C/C++语言在Linux上实现一个客户机-服务器体系结构。有没有什么图书馆可以让这项任务变得简单?任何人都可以提供一个例子吗?做一个人2发送文件。您只需打开客户端上的源文件和服务器上的目标文件,然后调用sendfile,内核就会剪切并移动数据。最可移植的解决方案就是将文件分块读取,然后以循环方式将数据写入套接字(同样,在接收文件时也是如此)。您可以将一个缓冲区分配到该缓冲区,然后从该缓冲区分配到套接字(您还可以使用send和recv,这是套接字特定的数据写入
人2发送文件。您只需打开客户端上的源文件和服务器上的目标文件,然后调用sendfile,内核就会剪切并移动数据。最可移植的解决方案就是将文件分块读取,然后以循环方式将数据写入套接字(同样,在接收文件时也是如此)。您可以将一个缓冲区分配到该缓冲区,然后从该缓冲区分配到套接字(您还可以使用send
和recv
,这是套接字特定的数据写入和读取方式)。大纲将如下所示:
while (1) {
// Read data into buffer. We may not have enough to fill up buffer, so we
// store how many bytes were actually read in bytes_read.
int bytes_read = read(input_file, buffer, sizeof(buffer));
if (bytes_read == 0) // We're done reading from the file
break;
if (bytes_read < 0) {
// handle errors
}
// You need a loop for the write, because not all of the data may be written
// in one call; write will return how many bytes were written. p keeps
// track of where in the buffer we are, while we decrement bytes_read
// to keep track of how many bytes are left to write.
void *p = buffer;
while (bytes_read > 0) {
int bytes_written = write(output_socket, p, bytes_read);
if (bytes_written <= 0) {
// handle errors
}
bytes_read -= bytes_written;
p += bytes_written;
}
}
while(1){
//将数据读入缓冲区。我们可能没有足够的数据填充缓冲区,所以
//存储实际读取的字节数(以字节为单位)。
int bytes_read=read(输入_文件、缓冲区、sizeof(缓冲区));
if(bytes_read==0)//我们已经完成了对文件的读取
打破
如果(字节数_读取<0){
//处理错误
}
//您需要一个用于写入的循环,因为并非所有数据都可以写入
//在一次调用中;write将返回写入的字节数。p
//跟踪我们在缓冲区中的位置,同时减少读取的字节数
//跟踪要写入的剩余字节数。
void*p=缓冲区;
而(字节读取>0){
int bytes_write=write(输出_套接字,p,字节_读取);
如果(字节)写入此文件将作为一个好的发送文件示例:最小可运行POSIX读取+写入示例
用法:
在同一台计算机上安装两台计算机
例如,在大多数情况下,如果两台计算机都连接到您的家庭路由器,这将起作用,这就是我测试它的方式
在服务器计算机上:
printf 'ab\ncd\n' > input.tmp
./client input.tmp 192.168.0.10 12345
使用ifconfig
查找服务器本地IP,例如192.168.0.10
运行:
./server output.tmp 12345
在客户端计算机上:
printf 'ab\ncd\n' > input.tmp
./client input.tmp 192.168.0.10 12345
结果:在服务器计算机上创建一个文件output.tmp
,其中包含'ab\ncd\n'
服务器.c
/*
Receive a file over a socket.
Saves it to output.tmp by default.
Interface:
./executable [<output_file> [<port>]]
Defaults:
- output_file: output.tmp
- port: 12345
*/
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *file_path = "output.tmp";
char buffer[BUFSIZ];
char protoname[] = "tcp";
int client_sockfd;
int enable = 1;
int filefd;
int i;
int server_sockfd;
socklen_t client_len;
ssize_t read_return;
struct protoent *protoent;
struct sockaddr_in client_address, server_address;
unsigned short server_port = 12345u;
if (argc > 1) {
file_path = argv[1];
if (argc > 2) {
server_port = strtol(argv[2], NULL, 10);
}
}
/* Create a socket and listen to it.. */
protoent = getprotobyname(protoname);
if (protoent == NULL) {
perror("getprotobyname");
exit(EXIT_FAILURE);
}
server_sockfd = socket(
AF_INET,
SOCK_STREAM,
protoent->p_proto
);
if (server_sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed");
exit(EXIT_FAILURE);
}
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(server_port);
if (bind(
server_sockfd,
(struct sockaddr*)&server_address,
sizeof(server_address)
) == -1
) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_sockfd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
fprintf(stderr, "listening on port %d\n", server_port);
while (1) {
client_len = sizeof(client_address);
puts("waiting for client");
client_sockfd = accept(
server_sockfd,
(struct sockaddr*)&client_address,
&client_len
);
filefd = open(file_path,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR);
if (filefd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
do {
read_return = read(client_sockfd, buffer, BUFSIZ);
if (read_return == -1) {
perror("read");
exit(EXIT_FAILURE);
}
if (write(filefd, buffer, read_return) == -1) {
perror("write");
exit(EXIT_FAILURE);
}
} while (read_return > 0);
close(filefd);
close(client_sockfd);
}
return EXIT_SUCCESS;
}
/*
通过套接字接收文件。
默认情况下,将其保存到output.tmp。
接口:
/可执行文件[]]
默认值:
-输出文件:output.tmp
-港口:12345
*/
#定义_XOPEN_源700
#包括
#包括
#包括
进一步评论
可能的改进:
- 当前
output.tmp
在每次发送完成时都会被覆盖
这就要求创建一个简单的协议,允许传递一个文件名,以便可以上载多个文件,例如:文件名到第一个换行符,最大文件名256个字符,其余的直到套接字关闭都是内容。当然,这需要卫生设施来避免冲突
或者,我们可以创建一个服务器,对文件进行散列以查找文件名,并在磁盘(数据库)上保留从原始路径到散列的映射
- 一次只能连接一个客户端
如果存在连接持续很长时间的慢速客户端,这尤其有害:慢速连接会使所有人停止连接
解决这个问题的一种方法是为每个
接受
分支一个进程/线程,立即重新开始侦听,并对文件使用文件锁同步
- 添加超时,如果需要的时间太长,请关闭客户端。否则很容易执行DoS
poll
或select
是一些选项:
一个简单的HTTPwget
实现如下所示:
在Ubuntu 15.10上测试。Brian,我猜你和florin不会得到复选标记,因为这几乎肯定是家庭作业:(您的原始答案怎么了?您首先建议在*nix框上使用“sendfile”命令,现在您已经给出了一个编程解决方案,同时删除了原始答案的所有痕迹。我没有删除原始答案的所有痕迹。我只是在其上添加了一个标准的框架,read
/write
循环。我认为我应该首先解释一下,因为即使使用sendfile
,您也需要使用这样一个循环来接收文件,而且因为我在后面提到了splice
,它使用相同的模式。啊,不知怎的错过了“sendfile”的保留。对不起。@Damon您有任何参考资料来获取更多信息吗?我很好奇到底得到了什么是的;它的效率比标准的用户空间读/写循环要低,还是效率不高?我自己实际上对它们没有太多经验,所以我想了解更多关于gotchas的信息。在同一个程序中接收文件如何?关于:如果(写(sockfd,buffer,read\u return)=-1){
在套接字上,可能会发生部分写入,因此应检查是否已写入所有字节,如果未写入,则计划再次调用write()
和仍需要写入的部分数据written@user3629249谢谢,我添加了一条评论。转换到文件*
然后使用fwrite
行吗?fwrite()
函数用于文本数据并允许格式化。write()
函数也可以处理二进制数据,但没有任何格式化功能。当write()
失败且程序即将退出时,应调用close()
在socketfd
上,要让服务器知道它,“服务器”应该检查accept()
的返回值以确保操作成功。调用read()
后的“服务器”应该立即检查返回值0