背景
近来接触到一块可搭载Linux系统的开发板,由于需要对该开发板进行性能评估,故需要将开发主机上交叉编译好的测试工具传输到开发板。此外,公司软件开发在云端完成(云端网络与开发板ping不同,但开发板可ping通电脑本地),且电脑主机不能识别U盘,故常用的网络传输方式和U盘拷贝方式都不能使用,只能本地自建scoket程序,或者通过串口传输。本章将自己编写Socket TCP程序,将本地主机上的文件传输给开发板。
常用传输方式
- 网络环境可用(云端可ping通开发板):
- tftp 传输
- nfs 传输
- scp uImage root@192.168.2.8 传输
- 云端可识别U盘:
- 串口传输:
# 符合 XMODEM, YMODEM, ZMODEM 协议的串口调试软件
# 常用软件用 SecureCRT 和 MobaXterm
# SecureCRT 对该协议的支持较好 ,但毕竟是串口传输,速率较慢,且不稳定,不适合较大文件的传输
rx uImage # 开发板上执行,再通过串口软件点击发送
# 常用命令有: rx rb rz
解决方案
编写socket软件,以开发板为服务端,本地电脑为客服端,通过网线直连,进行传输。开发板上的程序需通过交叉编译,编写语言为C++。 也可windows搭建nfs、tftp64服务器(特定软件)、MobaXtermt 使用ftp传输。
Linux下socket编程代码:
server.h
#ifndef _SERVER_H
#define _SERVER_H
#include <iostream>
#include <fstream>
#include <cstring>
#include <string.h>
#include <chrono>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUFSIZE 1024
class Server {
private:
int sockfd, s_socket; // 描述服务器的套接字
struct sockaddr_in server; // 描述服务器信息的结构体
struct sockaddr_in tcpAddr; // 描述客户端的结构体
char buf[BUFSIZE]; // 缓冲数组
int rval = 0; // 接受反馈
std::ofstream ofs; // 文件流指针,用于打开文件
public:
char file[BUFSIZE]; // 存放文件名
char clientIp[128]; // 存放客户端IP
int clientPort; // 客户端端口
public:
size_t fileSize; // 描述文件大小
size_t recvSize; // 描述已接收的字节
double time; // 接受文件所用时间
double Delay; // cilent与server之间的延时
public:
Server(const int port);
~Server();
// TCP连接建立以及相关信息传输
bool work();
double measureDelay();
private:
// 通过套接字接受文件
bool recvFile(const char filename[], double &time);
};
#endif
server.cpp
#include "server.h"
// 构造函数,初始化socket
Server::Server(const int port) {
// 建立描述服务器的套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cout << "Can not create socket!" << std::endl;
exit(0);
}
// 将服务器信息中的每个字节用0填充
memset(&server, 0, sizeof(server));
// 设置地址簇为Internet协议族——IPV4
server.sin_family = AF_INET;
// 设置端口
server.sin_port = htons(port);
// server.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
// 设置IP地址任何地址
server.sin_addr.s_addr = htonl(INADDR_ANY);
// 将本机IP和端口与sockfd绑定
rval = bind(sockfd, (struct sockaddr *) &server, sizeof(server));
if (rval == -1) {
// 异常处理
std::cout << "Can not create connect!" << std::endl;
exit(0);
}
// 监听sockfd套接字,限定最大等待队列数为5
listen(sockfd, 5);
}
// 析构函数,如果套接字存在,就关闭
Server::~Server() {
if (s_socket >= 0) {
close(s_socket);
}
close(sockfd);
}
bool Server::work() {
char filename[BUFSIZE] = {0};
socklen_t tcpAddrLenth = sizeof(tcpAddr);
// s_socket接受sockfd返回的socket描述符,tcpaddr存放客户端的信息
if ((s_socket = accept(sockfd, (struct sockaddr *) &tcpAddr, &tcpAddrLenth)) < 0) {
// 异常处理
std::cout << "Accept exception";
exit(1);
}
// 转换IP地址
strcpy(clientIp, inet_ntoa(tcpAddr.sin_addr));
clientPort = ntohs(tcpAddr.sin_port);
// 测量延时
//measureDelay();
memset(buf, 0, BUFSIZE);
// 接收文件名
read(s_socket, filename, BUFSIZE-1);
strcpy(file, filename);
// 发送确认信息
strcpy(buf, "recv");
write(s_socket, buf, sizeof(buf));
// 接受文件
if (!recvFile(filename, time)) {
return false;
}
// 接收成功后就关闭套接字
close(s_socket);
return true;
}
bool Server::recvFile(const char filename[], double &time) {
// 获取当前系统时间
auto start = std::chrono::system_clock::now();
// readLen标识从文件流中读取到buf缓冲数组中的字节数
int readLen = 0;
recvSize = 0;
// 创建接受的文件流
ofs.open(filename, std::ios::out | std::ios::binary);
if (!ofs.is_open()) {
std::cout << "Can not open " << filename << std::endl;
exit(0);
}
// 初始化buf缓冲数组为0
memset(buf, 0, sizeof(buf));
do {
readLen = read(s_socket, buf, BUFSIZE);
if (readLen == -1) {
// 异常处理
ofs.close();
return false;
} else if (readLen == 0) {
// readLen为0说明远端已经传输完成并且断开了TCP连接
break;
}
recvSize += readLen;
// 将buf缓冲数组的内容写入到文件流
ofs.write(buf, readLen);
// 初始化buf缓冲数组为0
memset(buf, 0, sizeof(buf));
} while (true);
// 接收成功后就关闭套接字
ofs.close();
fileSize = recvSize;
// 获取当前系统时间,与开始时间相减得到接收文件所用时间
std::chrono::duration<double> diff = std::chrono::system_clock::now() - start;
time = diff.count();
return true;
}
double Server::measureDelay() {
// 包数量
int packetNum = 0;
// 接收到的包数量
int PacketCount = 0;
// 接收client发送包的数量
read(s_socket, (char *) &packetNum, sizeof(packetNum));
write(s_socket, "recvDone", 9);
// 开始计时,获取当前系统时间
auto start = std::chrono::system_clock::now();
while (true) {
memset(buf, 0, BUFSIZE);
if (read(s_socket, buf, BUFSIZE) == -1) {
std::cout << "Recv packet wrong" << std::endl;
} else if (strcmp(buf, "sendDone") == 0) {
// 接收到sendDone信号,说明client发送完毕
// 获取当前系统时间,得到所用时间,并转换为毫秒
std::chrono::duration<double> diff = std::chrono::system_clock::now() - start;
Delay = diff.count();
break;
} else {
++PacketCount;
}
}
// 发送延时
write(s_socket, (char *) &Delay, sizeof(Delay));
return Delay;
}
ServerMain.cpp
#include <iostream>
#include <iomanip>
#include <chrono>
#include "server.h"
using namespace std;
/*
template <class T>
void measure(T&& func)
{
auto start = chrono::system_clock::now();
func();
chrono::duration<double> diff = chrono::system_clock::now() - start;
cout << diff << endl;
}
*/
// 帮助函数,传入argv第一个字符指针
void help(const char *name) {
// 裁切掉文件路径,保留文件名
std::string progname = name;
size_t lastPos = progname.find_last_of("/\\");
progname = progname.substr(lastPos + 1);
cout << ".\\" << progname << " [Options]" << " [Port]" << "[FilePath_1] [FilePath_2] ..." << endl;
cout << left << setw(10) << "Options :" << endl;
cout << right << setw(12) << "-r" << " " << "server receive file from client" << endl;
cout << right << setw(12) << "-s" << " " << "server send file to client" << endl << endl;
cout << left << setw(14) << "Port :" << endl;
cout << right << setw(12) << "0~1024" << " " << "Well Known Ports" << endl;
cout << right << setw(12) << "1025~49151" << " " << "Registered Ports" << endl;
cout << right << setw(12) << "49152~65535" << " " << "Dynamic and/or Private Ports" << endl;
}
int main(int argc, char **argv) {
if (argc == 1 || (argc == 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) ) {
help(argv[0]);
return 0;
}
if (strcmp(argv[1], "-r") != 0 && strcmp(argv[1], "-s") != 0) {
cout << "please look up by : --help" << endl;
return 0;
}
// 绑定的端口号
int port = atoi(argv[2]);
bool flag = false;
// 新建一个Server对象
Server server(port);
if (strcmp(argv[1], "-r") == 0 ) {
while (true) {
// 开始文件接收
flag = server.work();
// 如果文件接收成功,打印对应信息
if (flag) {
cout << left << setw(18) << server.clientIp;
cout << left << setw(10) << "Successd:" << left << setw(20) << server.file;
cout << " Size: " << left << setw(10) << std::to_string(server.fileSize / 1024 / 1024) + "MB";
cout << " [" + std::to_string(server.fileSize * 8 / 1024 / 1024 / server.time) + "]Mbps " << endl;
}
// 如果文件接收失败,打印对应信息
else {
cout << left << setw(18) << server.clientIp
<< left << setw(10) << "Failed:" << server.file << endl;
}
}
} else {
// while (true) {
// // 开始发送接收
// flag = server.work();
// // 如果文件接收成功,打印对应信息
// if (flag) {
// cout << left << setw(18) << server.clientIp;
// cout << left << setw(10) << "Successd:" << left << setw(20) << server.file;
// cout << " Size: " << left << setw(10) << std::to_string(server.fileSize / 1024 / 1024) + "MB";
// cout << " [" + std::to_string(server.fileSize * 8 / 1024 / 1024 / server.time) + "Mbps] " << endl;
// }
// // 如果文件接收失败,打印对应信息
// else {
// cout << left << setw(18) << server.clientIp
// << left << setw(10) << "Failed:" << server.file << endl;
// }
// }
}
return 0;
}
// g++ --static ServerMain.cpp server.cpp -l ws2_32 -o server
Makefile
CROSS_COMPILE ?= # 没有定义交叉编译器时,直接用gcc
CC := $(CROSS_COMPILE)g++ # 编译器
EXECUTABLE := server # 可执行文件名
INCLUDES := $(wildcard *.h) # 获取当前目录下的所有头文件
SRC := $(wildcard *.cpp) # 获取当前目录下的所有.cpp文件
OBJS := $(patsubst %.cpp, %.o, $(SRC)) # 将所有的.cpp文件换成.o文件 server.o ServerMain.o
CPPFLAGS := --std=c++11
RM-F := rm -rf
.PHONY : clean
$(EXECUTABLE) : $(OBJS)
@echo "输出变量OBJS的值,方便调试"
@echo $(OBJS)
$(CC) -o $(EXECUTABLE) $(OBJS) $(CPPFLAGS)
%.o : %.cpp $(INCLUDES)
$(CC) -c $< -o $@ $(CPPFLAGS)
clean:
$(RM-F) $(EXECUTABLE) $(OBJS)