此报告是作者自学总结的,有错误还请提出
一,代码实现功能简述
此代码实现了简单的文件上传和下载,现仅支持txt文档,后续会不断优化,选择的是tcp 而不是udp,因为需要可靠传输。
二,实现用到的技术
1,c++库中的socket技术、thread线程库
2,WS2_32动态链接库
三,实现过程
1,先创建套接字进行客户端与服务器的基础连接通信
客户端如下:
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)!=0) {
cout << "wsastartup faild:" << WSAGetLastError() << endl;
return false;
}
//1,创建通信套接字
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==INVALID_SOCKET) {
perror("socket");
return -1;
}
//2,连接服务器ip和port
struct sockaddr_in saddr;//定义套接字地址结构
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.101.35", &saddr.sin_addr.S_un.S_addr);//转换为网络大端,存放进s_addr ip
int ret = connect(fd, (sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
perror("connect");
return -1;
}
服务器如下 :
WSAData wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
cout << "wsastartup faild:" << WSAGetLastError() << endl;
return false;
}
//1,创建监听的套接字
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);//ipv4,流式协议,tcp
if (fd == INVALID_SOCKET) {
perror("socket");//打印错误信息
return -1;
}
//2,套接字与本地ip和端口绑定
//端口与ip为结构体
struct sockaddr_in saddr;//库里的结构体,定义结构体变量
saddr.sin_family = AF_INET;//指定地址家族为tcp/ip
saddr.sin_port = htons(9999);//端口转化为大端(低字节存储高位)端口5000以上,本机未使用的
saddr.sin_addr.S_un.S_addr = INADDR_ANY;//自动读取本地ip,服务器能接受任何计算机发来的请求(定义IP地址)
int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));//sizeof()计算占用内存大小
if (ret == -1) {
perror("bind");
return -1;
}
//3,设置监听
ret = listen(fd, 128);
if (ret == -1) {
perror("listen");
return -1;
}
//4,阻塞并等待客户端连接
sockaddr_in caddr;
int addrlen = sizeof(struct sockaddr_in);
while (1) {//初始化
SOCKET cfd = accept(fd, (struct sockaddr*)&caddr , &addrlen);//cfd用于通信的套接字
if (cfd == INVALID_SOCKET) {
continue;
}
文件的上传功能
客户端选择文件,发送文件名和文件大小给服务器,服务器会在自己的目录下生成一个txt文件来存储信息。
服务器:
ofstream file("文本.txt", ios::binary | ios::out);//二进制方式打开
if (!file.is_open()) {
perror("Error opening file for writing");
break;
}
// Receive file content from client and write to file
filesize = recv(cfd, rbuf, sizeof(rbuf), 0);//上传接受文件字节大小
if (filesize <= 0) break;
cout << "正在接受文件:" << rbuf <<"文件长度为:" << filesize << endl;
for (int i = 0; i < strlen(rbuf); i++) {
file << rbuf[i];
}
cout << "上传成功" << endl;
file.close();
必须以二进制来读取文件,否则文件内会出现中文乱码
文件的下载功能
用户需发给服务器要下载的文件名,服务器根据文件名找到该文件,先发送文件的大小,让客户端做好存储文件空间的准备,当客户端准备好后,服务器发送文件给客户端。
服务器:
cout << "下载文件名:"<<filename << endl;
ifstream ifs(filename,ios::binary| ios::in);
if (!ifs.is_open()) {
cout << "文件打不开" << endl;
continue;
}char c; int i = 0;
while ((c=ifs.get())!=EOF) {
sbuf[i++] = c;
}
cout <<"文件内容:" << sbuf <<"长度:" << endl;
// 开始发送文件
send(cfd, sbuf, strlen(sbuf), 0);
cout << "发送成功" << endl;
ifs.close();
由此实现下载功能
问题1:多个客户端请求时会出现“停滞”问题
原因:服务器只能线性处理客户端请求,多个客户端需排队等待
解决方法:在服务器端创建线程来解决等待问题,
while (1) {//初始化
SOCKET cfd = accept(fd, (struct sockaddr*)&caddr , &addrlen);//cfd用于通信的套接字
if (cfd == INVALID_SOCKET) {
continue;
}
//创建子线程
thread work(working,cfd,caddr);
work.detach();
}
每一个线程对应一个客户端的连接,把子线程与主线程分离,防止线程资源被主线程收回或杀死
客户端完整代码:
#include<WS2tcpip.h>
#include<WinSock2.h>
#include<iostream>
#include<thread>
#include<chrono>
#include<fstream>
#include<string>
#define FILE "D:\\2.PC端\\文本.txt"
#pragma comment(lib,"ws2_32.lib")
using namespace std;
char rbuf[1024];//接受文件
char buff[1024];//对话
char sbuf[1024];//上传文件
char filename[1024];//文件名
int filesize = 0;//文件字节大小
int main() {
//服务器有监听和通信两个套接字句柄,客户端只有通信套接字句柄
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)!=0) {
cout << "wsastartup faild:" << WSAGetLastError() << endl;
return false;
}
//1,创建通信套接字
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==INVALID_SOCKET) {
perror("socket");
return -1;
}
//2,连接服务器ip和port
struct sockaddr_in saddr;//定义套接字地址结构
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.101.35", &saddr.sin_addr.S_un.S_addr);//转换为网络大端,存放进s_addr ip
int ret = connect(fd, (sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
perror("connect");
return -1;
}
//3,通信
while (1) {
memset(filename, 0, sizeof(filename));
cout << "1,上传文件,2,下载文件" << endl;
//长传大文件要依据长度分块
int select;
cin >> select;
if (select == 1) {
strcpy_s(buff, "1");
send(fd, buff, strlen(buff), 0);
cout << "输入文件名:" << endl;
cin >> filename;
//strcpy_s(filename, FILE);
send(fd, filename, strlen(filename), 0);
ifstream file(filename,ios::binary|ios::in);//二进制方式打开
if (!file.is_open()) {
perror("Error opening file for reading");
break;
}
char c; int i = 0;
while ((c=file.get())!=EOF) {
sbuf[i++] = c;
}
cout << sbuf << endl;
send(fd, sbuf, strlen(sbuf), 0);
file.close();
cout << "已发送,长度为: "<<strlen(sbuf) << endl;
}
else {
strcpy_s(buff, "2");
send(fd, buff, strlen(buff), 0);
cout << "输入下载文件名" << endl;
cin >> filename;
send(fd, filename, strlen(filename), 0);
string s;
cout << "1,准备好了,0,未准备好接收" << endl;
cin >> s;
if (stoi(s) == 0) {
cout << "退出" << endl;
break;
}
else {
//接受文件
recv(fd, rbuf, sizeof(rbuf), 0);
cout <<"文件内容:"<< rbuf <<"文件长度:"<<strlen(rbuf) << endl;
ofstream ofs("五五.txt",ios::binary|ios::out);
for (int i = 0; i < strlen(rbuf); i++) {
ofs << rbuf[i];
}
cout << "接收成功" << endl;
ofs.close();
}
}
}
//4,关闭文件描述符
closesocket(fd);
WSACleanup();
return 0;
}
服务器完整代码:
#include<winsock2.h>
//#include<arpa/inet.h>就是上面的winsock2.h
#include<WS2tcpip.h>
#include <iostream>
#include<thread>
#include<string>
#include<fstream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
char rbuf[1024];//接受文件
char buff[1024] ;//对话
char sbuf[1024];//上传文件
char filename[1024];//文件名
int filesize = 0;//文件字节大小
void working(SOCKET cfd, sockaddr_in caddr);
int main()
{
WSAData wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
cout << "wsastartup faild:" << WSAGetLastError() << endl;
return false;
}
//1,创建监听的套接字
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);//ipv4,流式协议,tcp
if (fd == INVALID_SOCKET) {
perror("socket");//打印错误信息
return -1;
}
//2,套接字与本地ip和端口绑定
//端口与ip为结构体
struct sockaddr_in saddr;//库里的结构体,定义结构体变量
saddr.sin_family = AF_INET;//指定地址家族为tcp/ip
saddr.sin_port = htons(9999);//端口转化为大端(低字节存储高位)端口5000以上,本机未使用的
saddr.sin_addr.S_un.S_addr = INADDR_ANY;//自动读取本地ip,服务器能接受任何计算机发来的请求(定义IP地址)
int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));//sizeof()计算占用内存大小
if (ret == -1) {
perror("bind");
return -1;
}
//3,设置监听
ret = listen(fd, 128);
if (ret == -1) {
perror("listen");
return -1;
}
//4,阻塞并等待客户端连接
sockaddr_in caddr;
int addrlen = sizeof(struct sockaddr_in);
while (1) {//初始化
SOCKET cfd = accept(fd, (struct sockaddr*)&caddr , &addrlen);//cfd用于通信的套接字
if (cfd == INVALID_SOCKET) {
continue;
}
//创建子线程
thread work(working,cfd,caddr);
//work.detach();
}
closesocket(fd);
WSACleanup();
return 0;
}
void working(SOCKET cfd,sockaddr_in caddr){
//连接成功,打印客户端ip和端口信息
//将客户端网络大端转化为主机小端(低字节存储低位)
char ip[32];//存放ip
cout << "客户端ip:" << inet_ntop(AF_INET,&caddr.sin_addr.S_un.S_addr,ip,sizeof(ip)) <<//tcp/ip协议,需要转换的大端IP地址,
"\t端口:" << ntohs(caddr.sin_port) << endl;
//5,通信
while (1) {
memset(filename, 0, 1024);
recv(cfd, buff, sizeof(buff), 0);
if (buff[0] == '1') {
int len = recv(cfd, buff, sizeof(buff), 0);//用于通信的文件描述符,存储地址,空间,标志为0
if (len > 0) {
cout <<"上传文件名:" << buff << endl;
strcpy_s(filename, buff);
ofstream file("文本.txt", ios::binary | ios::out);//二进制方式打开
if (!file.is_open()) {
perror("Error opening file for writing");
break;
}
// Receive file content from client and write to file
filesize = recv(cfd, rbuf, sizeof(rbuf), 0);//上传接受文件字节大小
if (filesize <= 0) break;
cout << "正在接受文件:" << rbuf <<"文件长度为:" << filesize << endl;
for (int i = 0; i < strlen(rbuf); i++) {
file << rbuf[i];
}
cout << "上传成功" << endl;
file.close();
}
}
else {
int len = recv(cfd, filename, sizeof(filename), 0);
if (len > 0) {
cout << "下载文件名:"<<filename << endl;
ifstream ifs(filename,ios::binary| ios::in);
if (!ifs.is_open()) {
cout << "文件打不开" << endl;
continue;
}char c; int i = 0;
while ((c=ifs.get())!=EOF) {
sbuf[i++] = c;
}
cout <<"文件内容:" << sbuf <<"长度:" << endl;
// 开始发送文件
send(cfd, sbuf, strlen(sbuf), 0);
cout << "发送成功" << endl;
ifs.close();
}
}
}
//关闭文件描述符
}