一直想着给之前的CloudDisk项目加上一个C/S架构的文件传输模块,因为之前是nginx+fastcgi架构的B/S架构,自己又不会前段代码,没有办法继续增加新的功能块。最近终于抽出时间开始写项目了,已经选用TCP完成linux下的CS架构文件上传功能模块,这里展示TCP文件传输模块。
一、Socket
Socket类里仅仅封装了TCP连接所需的API和一些必要变量。
//Socket.h
#include<sys/socket.h>
#include<sys/types.h>
#include<cstring>
#include<netinet/in.h>
#include<memory.h>
#include<cstdlib>
#include<iostream>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
#include<arpa/inet.h>
#include"file.h"
#include"logger.h"
#define BUFFER_SIZE 1024
#define PORT 8888
#define MAX_CONNECT 20
using namespace std;
class Socket {
public:
Socket();
~Socket() {}
bool SockInitServer();
bool SockInitClient();
bool SockBind();
bool SockListen();
int SockConnect();
int SockAccpet();
int SockRecv(int recvfd, char buf[], int len);
int SockSend(int sendfd, const char buf[], int len);
bool SockClose();
int m_sockfd;
int recvLen;
int sendLen;
struct sockaddr_in m_sockaddr;
};
//Socket.cpp
#include"Socket.h"
Socket::Socket() {
if((m_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
LOG("socket build failed.");
} else {
LOG("socket build success.");
}
}
bool Socket::SockInitServer() {
memset(&m_sockaddr, 0, sizeof(m_sockaddr));
m_sockaddr.sin_family = AF_INET;
m_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
m_sockaddr.sin_port = htons(PORT);
LOG("init success.");
return true;
}
bool Socket::SockInitClient() {
memset(&m_sockaddr, 0, sizeof(m_sockaddr));
m_sockaddr.sin_family = AF_INET;
m_sockaddr.sin_port = htons(PORT);
m_sockaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
return true;
}
bool Socket::SockBind() {
if(bind(m_sockfd, (struct sockaddr*)&m_sockaddr, sizeof(m_sockaddr)) == -1) {
LOG("bind error.");
return false;
} else {
LOG("bind success.");
}
return true;
}
bool Socket::SockListen() {
if(listen(m_sockfd, MAX_CONNECT) == -1) {
LOG("listen error.");
return false;
} else {
LOG("listen success.");
}
return true;
}
int Socket::SockConnect() {
int sendfd;
if((sendfd = connect(m_sockfd, (struct sockaddr*)&m_sockaddr, sizeof(m_sockaddr))) == -1) {
LOG("connect failed.");
} else {
LOG("connect success.");
}
return sendfd;
}
int Socket::SockAccpet() {
int recvfd;
unsigned int addrLen = sizeof(m_sockaddr);
if((recvfd = accept(m_sockfd, (struct sockaddr*)&m_sockaddr, &addrLen)) == -1) {
LOG("accept fialed.");
} else {
LOG("accept success.");
}
return recvfd;
}
int Socket::SockRecv(int recvfd, char buf[], int len) {
recvLen = recv(recvfd, buf, len, 0);
//cout << buf << endl;
if(recvLen < 0) {
LOG("recv error.");
return -1;
} else {
LOG("recv success.");
}
return recvLen;
}
int Socket::SockSend(int sendfd, const char buf[], int len) {
sendLen = send(sendfd, buf, len, 0);
//cout << buf << endl;
if(sendLen < 0) {
LOG("send failed.");
return -1;
} else {
LOG("send success.");
}
return sendLen;
}
bool Socket::SockClose() {
close(m_sockfd);
LOG("close success.");
return true;
}
二、Server
Server类里面完成了对文件数据的接收解析,拿到文件的名字并创建文件,然后将其传给fdfs模块。这个类后续还会做很多优化,以及加上文件操作的其他功能模块。
//Server.h
#include"Socket.h"
#include"uploadfile.h"
class Server {
public:
Socket socket;
File file;
Upload upload;
Server() {}
~Server() {}
bool ServerInit();
bool ServerAccept();
bool GetFileName();
bool WriteFile(int recvfd, int fd);
};
//Server.cpp
#include"Server.h"
bool Server::ServerInit() {
socket.SockInitServer();
socket.SockBind();
socket.SockListen();
file.fileData = (char*)malloc(BUFFER_SIZE);
memset(file.fileData, '\0', sizeof(file.fileData));
return true;
}
bool Server::ServerAccept() {
int recvfd = socket.SockAccpet();
cout << socket.SockRecv(recvfd, file.filePath, 100) << endl;
cout << "filepath is: " << file.filePath << endl;
if(recvfd < 0) {
cout << "recv error." << endl;
return false;
} else {
GetFileName();
}
cout << "filename: " << file.fileName << endl;
int fd = open(file.fileName, O_CREAT | O_RDWR, 0664);
if(fd < 0) {
cout << "open file failed." << endl;
return false;
} else {
WriteFile(recvfd, fd);
}
return true;
}
bool Server::GetFileName() {
int i = 0, k = 0;
for(i = strlen(file.filePath); i >= 0; --i) {
if(file.filePath[i] != '/') {
++k;
} else {
break;
}
}
strcpy(file.fileName, file.filePath + (strlen(file.filePath) - k) + 1);
return true;
}
bool Server::WriteFile(int recvfd, int fd) {
int times = 1;
while(socket.recvLen = socket.SockRecv(recvfd, file.fileData, BUFFER_SIZE)) {
cout << "times: " << times << endl;
cout << socket.recvLen << endl;
++times;
if(socket.recvLen < 0) {
cout << "recv2 error." << endl;
break;
}
write(fd, file.fileData, socket.recvLen);
if(socket.recvLen < BUFFER_SIZE) {
cout << "write finished." << endl;
break;
} else {
cout << "write success." << endl;
}
cout << "continue..." << endl;
memset(file.fileData, 0, sizeof(file.fileData));
}
cout << "recv finished." << endl;
close(fd);
return true;
}
int main() {
Server server;
if (!server.upload.mysql.MysqlInit()) {
cout << "Init failed!" << endl;
return -1;
}
if(server.ServerInit()) {
cout << "server init success." << endl;
} else {
cout << "server init failed." << endl;
return -1;
}
while(true) {
if(server.ServerAccept()) {
server.upload.UploadFile(server.file.fileName);
server.upload.SaveToMysql();
continue;
} else {
break;
}
}
server.socket.SockClose();
return 0;
}
三、Client
Client主要用来传输文件,后面将作为界面程序的接口,会增加其他的功能模块。
//Client.h
#include"Socket.h"
class Client {
public:
File file;
Socket socket;
int sendLen;
int fd;
Client() {}
~Client() {}
bool ClientInit();
bool ClientSend();
bool SendFilePath();
bool SendFileData();
};
//Client.cpp
#include"Client.h"
bool Client::ClientInit() {
socket.SockInitClient();
socket.SockConnect();
file.fileData = (char*)malloc(BUFFER_SIZE);
memset(file.fileData, '\0', sizeof(file.fileData));
return true;
}
bool Client::ClientSend() {
if(!SendFilePath()) {
cout << "sendPath failed." << endl;
return false;
}
SendFileData();
return true;
}
bool Client::SendFilePath() {
cout << "please input file path: " << endl;
cin >> file.filePath;
fd = open(file.filePath, O_RDWR, 0664);
if(fd < 0) {
cout << "filepath not found!" << endl;
return false;
} else {
cout << "filepath : " << file.filePath << endl;
}
sendLen = socket.SockSend(socket.m_sockfd, file.filePath, 100);
if(sendLen < 0) {
cout << "filepath send error!" << endl;
return false;
} else {
cout << "filepath send success!" << endl;
}
return true;
}
bool Client::SendFileData() {
cout << "begin send data..." << endl;
int times = 1;
while((sendLen = read(fd, file.fileData, BUFFER_SIZE))) {
if(sendLen < 0) {
cout << "read error." << endl;
break;
} else if(sendLen == BUFFER_SIZE) {
cout << "times = " << times << endl;
++times;
if(socket.SockSend(socket.m_sockfd, file.fileData, BUFFER_SIZE) < 0) {
cout << "send failed!" << endl;
break;
} else {
LOG("data len is: %d", sendLen);
cout << "send successful!" << endl;
}
} else if(sendLen < BUFFER_SIZE) {
cout << "times = " << times << endl;
++times;
if(socket.SockSend(socket.m_sockfd, file.fileData, sendLen) < 0) {
cout << "send failed!" << endl;
break;
} else {
LOG("data len is: %d", sendLen);
cout << "send successful!" << endl;
}
break;
}
memset(file.fileData, 0, BUFFER_SIZE);
}
close(fd);
return true;
}
int main() {
Client client;
if(client.ClientInit()) {
cout << "client init success." << endl;
} else {
cout << "client init failed." << endl;
}
while(true) {
if(client.ClientSend()) {
continue;
} else {
break;
}
}
client.socket.SockClose();
return 0;
}
四、总结
目前该CS架构只是作为前期的功能实现,后续将会做进一步的优化,例如对文件发送接收的组包加密之类的,对网络传输模型的优化,对文件接收端做异步处理、消峰处理、容灾处理。合入CS框架之后,发现原来的代码耦合性还是有点高,后面会针对文件上传下载等功能作进一步的解耦设计。
学习如逆水行舟,不进则退。加油~