完整项目地址:https://download.csdn.net/download/lijunhcn/88453272
基于WinSocket实现C-S文件传输系统——“闪翼文件传输系统”
完整项目有需要可以私信博主
1、打开服务器,输入端口号并启动服务器
2、打开一个客户端,在连接服务器成功之前,并不能进行登录操作
3、客户端成功连接服务器,正确输入用户名和密码后客户端登陆成功
4、显示服务器的文件路径(服务器项目的构建文件路径)
5、显示服务器的默认文件列表(默认文件列表为服务器项目所在的文件列表)
6、上传文件到服务器(在客户端创建一个文本文件CliUpload.txt用于上传)
7、从服务器下载文件到客户端(在服务器创建一个文本文件SerDownload.txt用于下载)
8、关闭客户端,需要重连服务器且重新登陆
server端:
核心源码:
#include "msgthread.h"
MsgThread::MsgThread(SOCKET mClient, SOCKADDR_IN mCli_Addr, QWidget *parent) : QThread(parent)
{
this->mClient = mClient;
this->mCli_Addr = mCli_Addr;
}
MsgThread::~MsgThread()
{
int ret = closesocket(mClient);
if( ret == 0 )
{
qDebug()<< "关闭成功";
}
else
{
qDebug()<< "关闭失败";
}
}
void MsgThread::run()
{
//给客户端发送报文
ResponseGram responseCli;
responseCli.response = SUCCESS;
strcpy(responseCli.content, "SUCCESS");
if(SendtoCli(mClient, &responseCli));
else
{
emit isMsg("为客户端创建线程成功,但与客户端发送信息失败");
}
//开始获取报文命令
CmdGram commandCli;
while(true)
{
if(ReceiveCli(mClient, (char*)&commandCli));
else
{
emit isMsg("获取客户端命令失败");
break;
}
if(SerToCli(mClient, &commandCli, &mCli_Addr));
else
{
emit isMsg("获取客户端命令成功,但服务器未响应");
break;
}
}
closesocket(mClient);
}
bool MsgThread::SerToCli(SOCKET Ser_Cmd_Socket, CmdGram *Command, SOCKADDR_IN *Cli_Data_Port)
{
SOCKET Ser_Data_Socket;
FILE* fp = NULL;
ResponseGram* response;
//回复报文,从ser发往cli
ResponseGram Response;
Response.response = SUCCESS;
strcpy(Response.content, "SUCCESS");
response = &Response;
//cli从ser下载文件
if(Command->cmd == DOWNLOAD)
{
emit isMsg("收到下载文件命令");
fp = fopen(Command->content, "rb");//直接打开命令所在的的文件目录
if(fp == NULL)
{
response->response = ERR;
strcpy(response->content, "打开文件错误\n\n");
//发送错误报文
if(SendtoCli(Ser_Cmd_Socket, response))
{
emit isMsg("与客户端报告错误失败");
return false;
}
return false;
}
else
{
//发送成功报文
response->response = SUCCESS;
strcpy(response->content, "打开成功,正在传送......");
if(SendtoCli(Ser_Cmd_Socket, response))//发送报文过后开始传输文件
{
//建立连接
if(DataLink(&Ser_Data_Socket, Cli_Data_Port))
{
if(SendFiletoCli(Ser_Data_Socket, Command->content))//发送文件
{
fclose(fp);
return true;
}
else
return false;
}
else
return false;
}
else
return false;
}
}
else if(Command->cmd == UPLOAD)//上传文件
{
emit isMsg("收到上传文件命令");
//先检查服务器中是否存在同名文件
char fileName[128];
strcpy(fileName, Command->content);
FILE* fp = fopen(fileName, "r+");
if(fp != NULL)//文件能够打开,说明服务器中存在同名文件,提出警告
{
response->response = SUCCESS;
strcpy(response->content, "警告:服务器中已存在该文件,将会覆盖文件\n");
remove(fileName);
}
else
{
response->response = SUCCESS;
strcpy(response->content, "SUCCESS");
}
//给cli发送报文
if(SendtoCli(Ser_Cmd_Socket, response));
else
{
emit isMsg("文件符合上传条件,但与客户端通信失败");
return false;
}
//建立数据传输通道
if(DataLink(&Ser_Data_Socket, Cli_Data_Port));
else
{
emit isMsg("建立数据传输通道失败");
return false;
}
//接收cli的文件
if(ReceiveFile(Ser_Data_Socket, fileName));
else
{
emit isMsg("接收客户端文件失败");
return false;
}
return true;
}
else if(Command->cmd == QUIT)//退出
{
emit isMsg("接收到客户端退出命令");
response->response = SUCCESS;
strcpy(response->content, "服务器已接收到断开请求");
if(SendtoCli(Ser_Cmd_Socket, response));
else
{
emit isMsg("服务器已成功接收客户端断开信息,但与客户端通信失败");
return false;
}
closesocket(Ser_Data_Socket);
return true;
}
else if(Command->cmd == LIST)//列出文件目录
{
emit isMsg("收到列出文件目录命令");
//建立连接
if(DataLink(&Ser_Data_Socket, Cli_Data_Port));
else
{
emit isMsg("建立数据传输连接失败");
return false;
}
//发送文件目录
if(SendFileList(Ser_Data_Socket))
{
emit isMsg("发送文件目录成功");
return true;
}
else
{
emit isMsg("发送文件目录失败");
return false;
}
}
else if(Command->cmd == PWD)//显示文件路径
{
emit isMsg("收到显示当前文件路径命令");
response->response = SUCCESS;
if(GetCurrentDirectoryA(RESPONSE_CONTENT_SIZE, response->content));
else
{
strcpy(response->content, "获取当前目录失败");
return false;
}
if(SendtoCli(Ser_Cmd_Socket, response))
return true;
else
{
emit isMsg("获取当前目录成功,但与客户端发送信息失败");
return false;
}
}
else
{
return true;
}
}
//向cli发送报文
bool MsgThread::SendtoCli(SOCKET Ser_Cmd_Socket, ResponseGram* Response)
{
if(send(Ser_Cmd_Socket, (char*)Response, sizeof (ResponseGram), 0) == SOCKET_ERROR)
{
int error = WSAGetLastError();
emit isMsg("向客户端发送报文失败");
qDebug()<<error;
return false;
}
emit isMsg("向客户端发送报文成功");
return true;
}
//接收来自客户端的命令
bool MsgThread::ReceiveCli(SOCKET Ser_Cmd_Socket, char* Command)
{
int flag;
int not_recv = sizeof(CmdGram);
//开始读取数据
for(; not_recv > 0;)
{
flag = recv(Ser_Cmd_Socket, Command, not_recv, 0);
if(flag == SOCKET_ERROR)
{
emit isMsg("接收命令错误或客户端退出");
return false;
}
else
{
emit isMsg("接收命令成功");
}
//字符串指针加int的意义:偏移
not_recv -= flag;
Command += flag;
}
return true;
}
//建立数据通道
bool MsgThread::DataLink(SOCKET *Ser_Data_Socket, SOCKADDR_IN *Cli_Data_Port)
{
//创建ser端数据的socket
SOCKET ser_data_socket;
ser_data_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(ser_data_socket == INVALID_SOCKET)
{
emit isMsg("创建数据传输套接字失败");
return false;
}
//获取客户端数据传输端口号
SOCKADDR_IN cli_data_port;
std::memcpy(&cli_data_port, Cli_Data_Port, sizeof(SOCKADDR_IN));
cli_data_port.sin_port = htons(DATA_PORT_PV);
//连接
if(::connect(ser_data_socket, (SOCKADDR*)&cli_data_port, sizeof(SOCKADDR)))
{
emit isMsg("与客户端的连接出现问题");
closesocket(ser_data_socket);
return false;
}
*Ser_Data_Socket = ser_data_socket;
emit isMsg("与客户端数据传输连接成功");
return true;
}
//给cli发送文件
bool MsgThread::SendFiletoCli(SOCKET Ser_Data_Socket, char *FileName)
{
//开始读文件
char buf[2048];
FILE *fp = fopen(FileName, "rb");
if(fp == NULL)
{
emit isMsg("文件不存在");
fclose(fp);
closesocket(Ser_Data_Socket);
return false;
}
while(1)
{
int size = fread(buf, 1, 2048, fp);
if(send(Ser_Data_Socket, buf, size, 0) == SOCKET_ERROR)
{
emit isMsg("发送文件过程中发生错误");
closesocket(Ser_Data_Socket);
return false;
}
if(size < 2048)
break;
}
fclose(fp);
closesocket(Ser_Data_Socket);
emit isMsg("文件发送完成");
return true;
}
//从cli接收文件
bool MsgThread::ReceiveFile(SOCKET Ser_Data_Socket, char *FileName)
{
//先创建文件,再读取
char buf[2048];
int recv_size;
FILE* fp = fopen(FileName, "wb");
if(fp == NULL)
{
emit isMsg("创建文件过程中发生错误");
fclose(fp);
closesocket(Ser_Data_Socket);
return false;
}
while(1)
{
recv_size = recv(Ser_Data_Socket, buf, 2048, 0);
if(recv_size == SOCKET_ERROR)
{
emit isMsg("上传文件时发生错误");
fclose(fp);
closesocket(Ser_Data_Socket);
return false;
}
if(recv_size == 0)
break;
fwrite(buf, 1, recv_size, fp);
}
fclose(fp);
closesocket(Ser_Data_Socket);
emit isMsg("完成上传");
return true;
}
//向cli发送文件列表
bool MsgThread::SendFileList(SOCKET Ser_Data_Socket)
{
long handle;
struct _finddata_t File_Data;
char fileInfo[500];
const char column[100] = " Name | Updata Time | Size(Bytes) \n";
//使用通配符找到当前目录,并将其信息存入指针
handle = _findfirst32("*", &File_Data);
if(handle == -1)
{
emit isMsg("服务器读取文件列表失败");
if(send(Ser_Data_Socket, "获取文件列表失败", sizeof("获取文件列表失败"), 0));
else
{
emit isMsg("服务器发送信息失败");
return false;
}
}
else
{
send(Ser_Data_Socket, column, sizeof(column), 0);
while(true)
{
//查找下一个文件
int FindNext = _findnext32(handle, &File_Data);
if(FindNext == -1)
{
emit isMsg("服务器已查找完所有文件");
break;
}
else
{
ReturnFileInfo(&File_Data, fileInfo);
if(send(Ser_Data_Socket, fileInfo, sizeof(fileInfo), 0));
else
{
emit isMsg("服务器发送信息失败");
return false;
}
}
}
}
closesocket(Ser_Data_Socket);
_findclose(handle);
return true;
}
//返回文件具体信息
void MsgThread::ReturnFileInfo(struct _finddata32_t *File_Data, char *fileInfo)
{
char fileinfo[500];
int size;
time_t fileUpdata_time = File_Data->time_write;
tm* time_wanted;
time_wanted = NULL;
time_wanted = localtime(&fileUpdata_time);
size = File_Data->size;
sprintf(fileinfo, "| %24s| %d/%d/%d %d:%d:%d | %d \n",
File_Data->name, 1900 + time_wanted->tm_year, 1 + time_wanted->tm_mon, time_wanted->tm_mday,
time_wanted->tm_hour, time_wanted->tm_min, time_wanted->tm_sec, size);
strcpy(fileInfo, fileinfo);
}
客户端:
核心源码:
#include "login.h"
Login::Login(QWidget *parent) : QDialog(parent)
{
Construct();
chances = 3;
}
//连接到serip:serport
SOCKET My_Socket;
void Login::ConnectSer()
{
//从输入框中获取服务器地址和端口
serip = my_severloc->text();
serport = my_port->text();
IP_NUM = serip;
Port_NUM = serport;
SOCKADDR_IN addr;
if(WSAStartup(MAKEWORD(2, 2), &wasData) != 0)
{
qDebug()<<"WinSock初始化失败";
}
//创建套接口
My_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(My_Socket == INVALID_SOCKET)
{
qDebug()<<"创建套接口失败";
}
addr.sin_family = AF_INET;
addr.sin_port = htons(Port_NUM.toInt());
char* temp;
QByteArray Tran = IP_NUM.toLatin1();
temp = Tran.data();
addr.sin_addr.S_un.S_addr = inet_addr(temp);
memset(&(addr.sin_zero), 0, sizeof(addr.sin_zero));
if (::connect(My_Socket, (SOCKADDR*)&addr, sizeof(SOCKADDR)) == SOCKET_ERROR) //避免与QTconnect冲突
{
qDebug()<<"未能连接上服务器";
closesocket(My_Socket);
WSACleanup();
return;
}
qDebug()<<"连接成功,可进行登录";
cli_login->setEnabled(true);
return;
}
void Login::Login_clicked()
{
user = my_username->text();
psd = my_password->text();
if((user == Name) && (psd == PassWord))
{
this->close();
emit mainshow();
}
else
{
//拥有三次机会输入
chances--;
if(chances >= 1)
{
switch (chances)
{
case 1:
FailedLog.setText("用户名或密码错误,您还有1次输入机会!");
break;
case 2:
FailedLog.setText("用户名或密码错误,您还有2次输入机会!");
break;
default:
break;
}
FailedLog.show();
}
else
{
ExitLog.show();
}
}
}
void Login::show_loginwin()
{
this->show();
//清空文本框,重新连接服务器
my_severloc->clear();
my_port->clear();
my_username->clear();
my_password->clear();
//设置光标
my_severloc->setFocus();
cli_login->setEnabled(false);
}
void Login::Construct()
{
this->setWindowTitle("闪翼文件传输系统-客户端登录");
this->resize(1200, 800);
this->setWindowIcon(QIcon(":/logo/logo.png"));
setFixedSize(this->width(), this->height());
QImage image;
image.load(":/logo/login_background.png");
QPalette palet;
palet.setBrush(this->backgroundRole(),QBrush(image));
this->setPalette(palet);
//服务器地址输入布局
my_severloc = new QLineEdit(this);
my_severloc->setStyleSheet("QLineEdit{border-width:0;border-style:outset;background-color:rgba(242,242,242,0)}");
my_severloc->resize(300, 45);
my_severloc->move(250, 340);
my_severloc->setFont(QFont("Consolas", 15, QFont::Bold));
my_port = new QLineEdit(this);
my_port->setStyleSheet("QLineEdit{border-width:0;border-style:outset;background-color:rgba(242,242,242,0)}");
my_port->resize(300, 45);
my_port->move(250, 435);
my_port->setFont(QFont("Consolas", 15, QFont::Bold));
//登录界面布局
my_username = new QLineEdit(this);
my_username->setStyleSheet("QLineEdit{border-width:0;border-style:outset;background-color:rgba(242,242,242,0)}");
my_username->resize(300, 55);
my_username->move(780, 316);
my_username->setFont(QFont("Consolas", 15, QFont::Bold));
my_password = new QLineEdit(this);
my_password->setStyleSheet("QLineEdit{border-width:0;border-style:outset;background-color:rgba(242,242,242,0)}");
my_password->resize(300, 55);
my_password->move(780, 424);
my_password->setFont(QFont("Consolas", 15, QFont::Bold));
my_password->setEchoMode(QLineEdit::Password);
//按钮设置
connectser = new QPushButton(this); connectser->resize(220,60); connectser->move(295,515);
QString styleSheetString1("QPushButton{border-image:url(\":/logo/connect1.png\");}");
styleSheetString1+="QPushButton:hover{border-image:url(\":/logo/connect2.png\");}";
styleSheetString1+="QPushButton:pressed{border-image:url(\":/logo/connect3.png\");}";
connectser->setStyleSheet(styleSheetString1);
connect(connectser, &QPushButton::clicked, this, &Login::ConnectSer);
cli_login = new QPushButton(this); cli_login->resize(220,60); cli_login->move(800,515);
QString styleSheetString2("QPushButton{border-image:url(\":/logo/login1.png\");}");
styleSheetString2+="QPushButton:hover{border-image:url(\":/logo/login2.png\");}";
styleSheetString2+="QPushButton:pressed{border-image:url(\":/logo/login3.png\");}";
cli_login->setStyleSheet(styleSheetString2);
connect(cli_login, &QPushButton::clicked, this, &Login::Login_clicked);
cli_login->setEnabled(false);
//设置重试对话框
FailedLog.setWindowTitle("用户登录");
FailedLog.setWindowIcon(QIcon(":/logo/logo.png"));
FailedLog.addButton(Retry, QMessageBox::AcceptRole);
connect(Retry, &QPushButton::released,
[=]()
{
my_username->clear();
my_password->clear();
});
//设置失败对话框
ExitLog.setWindowTitle("用户登录");
ExitLog.setWindowIcon(QIcon(":/logo/logo.png"));
ExitLog.setText("您的机会已用完,请您退出登录。");
ExitLog.addButton(ExitL, QMessageBox::RejectRole);
connect(ExitL, &QPushButton::released,
[=]()
{
closesocket(My_Socket);
WSACleanup();
close();
});
}