老师的要求是:要用Winsock库,实现FTP,要求在自己的电脑上完成客户端和服务端,要有图形化界面。
由于Winsock我也是现学的,所以不一定用的很好,而且c/c++也不太会,所以可能会有很多不完美的地方,仅供大家写课设时参考,最后我会放上我自学的网站。
话不多说,先上源码
服务端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
using namespace std;
#define BUF_SIZE 1024
boolean exit_flag = true;
int main() {
printf("服务端已启用!\n");
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
listen(servSock, 20);
SOCKADDR clntAddr;
int nSize = sizeof(SOCKADDR);
char ch_flag[20] = {0};//功能选择标记
while (exit_flag) {
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
memset(ch_flag, 0, 20);
recv(clntSock, ch_flag, 20, 0);
if (strcmp(ch_flag, "1") == 0) {
printf("用户选择上传功能!");
//文件创建
FILE* fp = fopen("upload.txt", "wb"); //以二进制方式打开(创建)文件
if (fp == NULL) {
printf("文件无法创建,请按任意键退出!\n");
system("pause");
exit(0);
}
//循环接收数据,直到文件传输完毕
char buffer[BUF_SIZE] = { 0 }; //文件缓冲区
int nCount;
while ((nCount = recv(clntSock, buffer, BUF_SIZE, 0)) > 0) {
fwrite(buffer, nCount, 1, fp);
}
fclose(fp);
if (buffer[0] != NULL) {
puts("用户上传成功!");
/*char* flag = "1";
send(clntSock,flag,sizeof(char)+strlen(flag),0);*/
}
else {
puts("用户上传失败!");
/*char* flag = "0";
send(clntSock, flag, sizeof(char) + strlen(flag), 0);*/
}
}
else if (strcmp(ch_flag, "2") == 0) {
printf("用户选择下载功能!");
//先检查文件是否存在
FILE* fp = fopen("a.txt", "rb"); //以二进制方式打开文件
if (fp == NULL) {
printf("未找到文件,按任意键退出!\n");
char* reflBuf = NULL;
send(clntSock, reflBuf, sizeof(reflBuf), 0);
system("pause");
exit(0);
}
//循环发送数据,直到文件结尾
char buffer[BUF_SIZE] = { 0 }; //缓冲区
int nCount;
while ((nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0) {
send(clntSock, buffer, nCount, 0);
}
shutdown(clntSock, SD_SEND); //文件读取完毕,断开输出流,向客户端发送FIN包
recv(clntSock, buffer, BUF_SIZE, 0); //阻塞,等待客户端接收完毕
fclose(fp);
printf("文件已被下载!\n");
}
else if(strcmp(ch_flag, "0") == 0)
{
printf("用户选择退出功能!");
exit_flag = false;
break;
}
closesocket(clntSock);
}
//closesocket(clntSock);
closesocket(servSock);
WSACleanup();
printf("客户端已退出,");
system("pause");
return 0;
}
客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#include<graphics.h>
using namespace std;
#define BUF_SIZE 1024
void download(SOCKET sock);
void upload(SOCKET sock);
void initDLL();
//static boolean d_flag = false;//下载标识初始化
//static boolean u_flag = false;//上传标识初始化
TCHAR* a;
int main() {
initgraph(600, 400);//窗口初始化
setbkmode(TRANSPARENT);//背景透明
IMAGE bk;
loadimage(&bk, _T('bk1.jpg'), 887, 1920);
putimage(0, 0, &bk);
settextcolor(0xFF0000);//字体深蓝色
outtextxy(220,80,_T("请 选 择 你 的 操 作 ~"));
setlinestyle(PS_SOLID, 3);//设置画线样式
setlinecolor(0xFFFF00);//绿不绿蓝不蓝的按钮颜色
rectangle(100, 180, 160, 220);
rectangle(270, 180, 330, 220);
rectangle(440, 180, 500, 220);
//自绘制按钮图标
RECT r1 = { 100,180,160,220 };
RECT r2 = { 270,180,330,220 };
RECT r3 = { 440,180,500,220 };
drawtext(_T("上传"), &r1, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
drawtext(_T("下载"), &r2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
drawtext(_T("退出"), &r3, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//鼠标实现点击
MOUSEMSG m;//鼠标指针
initDLL();//dll初始化
while (true) {
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
//connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
m = GetMouseMsg();//获取一条鼠标消息
if (m.x <= 160 && m.x >= 100 && m.y >= 180 && m.y <= 220) {//上传
setlinecolor(BLACK);//线条颜色
rectangle(100, 180, 160, 220);
if (m.uMsg == WM_LBUTTONDOWN) {
char* ch = "1";
send(sock, ch, sizeof(char) + strlen(ch), 0);
upload(sock);
FlushMouseMsgBuffer();//清除鼠标缓冲区信息
}
}
else if (m.x <= 330 && m.x >= 270 && m.y >= 180 && m.y <= 220) {//下载
setlinecolor(BLACK);//线条颜色
rectangle(270, 180, 330, 220);
if (m.uMsg == WM_LBUTTONDOWN) {
char* ch = "2";
send(sock, ch, sizeof(char) + strlen(ch), 0);
//下载文件
download(sock);
FlushMouseMsgBuffer();
}
}
else if (m.x <= 500 && m.x >= 440 && m.y >= 180 && m.y <= 220) {//退出
setlinecolor(BLACK);//线条颜色
rectangle(440, 180, 500, 220);
if (m.uMsg == WM_LBUTTONDOWN) {
char* ch = "0";
send(sock, ch, sizeof(char) + strlen(ch), 0);
FlushMouseMsgBuffer();
break;
}
}
else {
setlinecolor(0xFFFF00);//绿不绿蓝不蓝的按钮颜色
rectangle(100, 180, 160, 220);
rectangle(270, 180, 330, 220);
rectangle(440, 180, 500, 220);
}
closesocket(sock);
}
WSACleanup();
//system("pause");
closegraph();
//文件接收完毕后直接关闭套接字,无需调用shutdown()
//system("pause");
return 0;
}
void initDLL() {
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
}
void download(SOCKET sock) {
//文件创建
FILE* fp = fopen("download.txt", "wb"); //以二进制方式打开(创建)文件
if (fp == NULL) {
//printf("文件无法创建,请按任意键退出!\n");
return;
//system("pause");
//exit(0);
}
//循环接收数据,直到文件传输完毕
char buffer[BUF_SIZE] = { 0 }; //文件缓冲区
int nCount;
while ((nCount = recv(sock, buffer, BUF_SIZE, 0)) > 0) {
fwrite(buffer, nCount, 1, fp);
}
fclose(fp);
//system("pause");
}
void upload(SOCKET sock) {
//char* filepath = "b.txt"; //文件名
FILE* fp = fopen("b.txt", "rb"); //以二进制方式打开文件
if (fp == NULL) {
//printf("未找到文件,按任意键退出!\n");
char* sendBuf = NULL;
send(sock, sendBuf, sizeof(sendBuf), 0);
return;
//system("pause");
//exit(0);
}
//循环发送数据,直到文件结尾
char buffer[BUF_SIZE] = { 0 }; //缓冲区
int nCount;
while ((nCount = fread(buffer, 1, BUF_SIZE, fp)) > 0) {
send(sock, buffer, nCount, 0);
}
shutdown(sock, SD_SEND); //文件读取完毕,断开输出流,向客户端发送FIN包
recv(sock, buffer, BUF_SIZE, 0); //阻塞,等待客户端接收完毕
//u_flag = true;
fclose(fp);
}
然后,我们来看一下效果图。 可以看到,客户端是有图形化界面的,所以得先安装easyx库,安装方法可以直接搜索一下。
因为我用的是VS2019,所以会有部分安全问题。
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
这两条都是处理安全问题的宏。
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
这两条分别是导入Winsock2库和需要的资源。 (winsock也有这个库,但是winsock2更全且向下兼容)
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
声明出一个资源文件的对象,然后将DLL初始化,启用2.2版本的库
先说服务端
SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
创建了两个套接字对象,都是服务端本身。 同样是套接字,类型不同是因为带 _in 的可以将ip和端口号分开操作。 memset()是c++的内置函数,用来初始化内存的 地址族变量只在Linux里有区别,所以看到有人用AF_INET和我这里的PF_INET是一样的,端口号的话可以随便设置,只要在可用端口号范围(1024~65535)就行了。
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
listen(servSock, 20);
bind()就是用来绑定上面两个类型不同的套接字,相当于服务端套接字同时有了名字和详细信息。 listen()是用来监听客户端的,会让服务端进入阻塞状态。
然后,在while循环里定义客户端套接字并连接。把客户端套接字放进循环里是为了出循环后就可以主动关闭套接字,以防它的接收缓冲区影响后续操作。(好奇的同学可以自己放到外面试一下,就会发现fin包接收的极其缓慢,再次进入监听也极其缓慢)
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
accept()是当服务端在监听状态下,有客户端接入后建立连接。
char ch_flag[20] = {0};//功能选择标记
这是一个用来监听客户端是按了哪个按钮的变量,获取数据的方式也是通过套接字之间的传输。
//循环接收数据,直到文件传输完毕
char buffer[BUF_SIZE] = { 0 }; //文件缓冲区
int nCount;
while ((nCount = recv(clntSock, buffer, BUF_SIZE, 0)) > 0) {
fwrite(buffer, nCount, 1, fp);
}
首先定义了一个缓冲区,recv()接收流数据,写入缓冲区,recv()会返回接收的字节数,所以只要没接收完,nCount就大于0,再用IO流写入到本地文件。
send(clntSock, reflBuf, sizeof(reflBuf), 0);
send是用来发送数据的函数。
closesocket(clntSock);
closesocket(servSock);
WSACleanup();
分别关闭客户端套接字,服务端套接字,DLL。
再说客户端
initgraph(600, 400);//窗口初始化
setbkmode(TRANSPARENT);//背景透明
IMAGE bk;
loadimage(&bk, _T('bk1.jpg'), 887, 1920);
putimage(0, 0, &bk);
settextcolor(0xFF0000);//字体深蓝色
outtextxy(220,80,_T("请 选 择 你 的 操 作 ~"));
setlinestyle(PS_SOLID, 3);//设置画线样式
setlinecolor(0xFFFF00);//绿不绿蓝不蓝的按钮颜色
rectangle(100, 180, 160, 220);
rectangle(270, 180, 330, 220);
rectangle(440, 180, 500, 220);
//自绘制按钮图标
RECT r1 = { 100,180,160,220 };
RECT r2 = { 270,180,330,220 };
RECT r3 = { 440,180,500,220 };
drawtext(_T("上传"), &r1, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
drawtext(_T("下载"), &r2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
drawtext(_T("退出"), &r3, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
用easyx库建立了一个600x400的紫的界面,设置了各种参数,包括按钮的坐标。 下面用while循环实现了各个功能,按钮的点击特效是通过检测鼠标位置不断重绘按钮最外圈线条实现的。
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
上面配置了套接字,connect()用于与服务端连接。(相应的,服务端用accept()连接) 其他的就和服务端一样了。
下面附上我自学的网址: http://c.biancheng.net/cpp/html/3029.html 希望这篇博客可以帮到你们~
|