网络编程——文件传输
要求
- 客户端向服务端发起socket连接,建立数据传输通道
- 客户端向服务端发送要传输的文件名称,以'#'字符结尾,服务端逐字符接受,知道接收到‘#’
- 客户端向服务端发送文件长度,4字节
- 客户端向服务端发送文件内容
- 服务端接受完文件之后,发送“OK”
- 客户端接收到“OK”,关闭套接字
思路
- socket连接和之前的客户端和服务端的基本相同,不同的主要是数据接收传输的过程需要处理
- 客户端传输文件名,要在文件名后面加上一个‘#’,这就需要对出入参数进行处理,对命令行参数读入到数组中,在数组结尾加上一个‘#’,传输是以数组的形式。
//对文件名传输进行处理
char filename[MAX_PATH];
int i = 0;
for (i = 0; i < len3; i++)
{
filename[i] = argv[3][i];
}
filename[i++] = '#';
//传输文件名
iResult = send(ConnectSocket, filename, strlen(filename), 0);
if (iResult == SOCKET_ERROR)
{
cout << "Send Filename : " << Sendbuffer << " with error " << WSAGetLastError() << "\n" << endl;
break;
}
- 文件的类型不限于文本文件,所以在处理的时候应该以二进制文件的形式进行处理
C++的文件流中提供了read()和write()函数,可以比较方便的实现二进制数据一次按一定大小读取和写入。
//以二进制文件形式读取文件
ifstream sourcefile(argv[3], ios::in | ios::binary);
sourcefile.read(filebuf, MAX_LEN);
rec_file.write(Recvb, MAX_LEN);
- 在传输文件内容的长度为4字节,4字节足够绝大多数的文件传输要求
- 确定文件
//确定文件的大小
int len;
FILE* fp;
if(fp = fopen(argv[3], "r"))
{
fseek(fp, 0, SEEK_END);
printf("%ld\n", ftell(fp));
len = ftell(fp);
fclose(fp);
}
else
{
cout << "Error" << endl;
}
- 缓冲区大小是有限的,而且在传输大文件的时候,非常有可能出现缓冲区大小小于文件长度,所以在处理的时候要循环处理,发送要循环发送,接受也应该循环接受、写入。
C++的文件流中提供了read()和write()函数,不断调用这两个函数来进行超过缓冲区大小的传输数据。
sourcefile.read(filebuf, MAX_LEN);
rec_file.write(Recvb, MAX_LEN);
- 如果文件长度需要多次传输的时候,每次的接受都会打印信息
while (left > 0)
{
if (left > MAX_LEN)
{
iResult = recvn(s, Recvb, MAX_LEN);
if (iResult == -1)
{
cout << "Receive failed with error " << WSAGetLastError() << endl;
return -1;
}
else if (iResult == 0)
{
cout << "Receive data stopped. There "<<left<<" bytes unreceived" << endl;
break;
}
else
{
cout << "Receive partial data : " << iResult << " bytes" << endl;
rec_file.write(Recvb, MAX_LEN);
left -= MAX_LEN;
}
}
else
{
iResult = recvn(s, Recvb, left);
if (iResult == -1)
{
cout << "Receive failed with error " << WSAGetLastError() << endl;
return -1;
}
else if (iResult ==0 )
{
cout << "Receive data stopped. There " << left << " bytes unreceived" << endl;
break;
}
else
{
cout << "Receive data : " << iResult << " bytes" << endl;
rec_file.write(Recvb, left);
left = 0;
}
}
}
- 在传输文件内容的时候,主要是根据前面收到的文件长度之后,调用定长数据接受函数来进行数据接收。
- 对于传输文件的长度,要注意不能用数组进行传参
用四个字节的数组可以表示的长度和四个字节的整数可以表示的长度范围是不一样的,整数表示的范围远远大于数组。 - 传输文件的长度的时候要注意参数的设置
- 在传输结束之后,服务端打印文件传输结束的提示,并且给客户端进行信息返回。
//接收文件长度数据
iResult = recv(s, (char*)&len_tran, 4, 0);
if (iResult != 4)
{
if (iResult == -1)
{
cout << "Receive failed with error " << WSAGetLastError() << endl;
return -1;
}
else
{
cout << "Length data is not complete. Connection closed\n" << endl;
return -1;
}
}
recvlen =ntohl( len_tran);
cout << "The length of file to receive is " << recvlen << "\n" << endl;
- 客户端在接受OK信息之后,退出。
- 服务端在出现问题的时候,closesocket并且返回
注意,因为接受文件整个就是一个while循环,所以在出现其他问题,比如接受失败的时候,continue到第一个错误处理中(此时客户端因为操作失败已经关闭连接),统一的进行closesocket和return。
iResult = recv(ConnectSocket, RecvBuffer, sizeof(RecvBuffer), 0);
if (iResult == SOCKET_ERROR || iResult == 0)
{
cout << "Client(" << inet_ntoa(information.addrinfo.sin_addr) << " : " << information.addrinfo.sin_port << ") Exited" << endl;
//每次如果出现任何错误,continue会回到这里,因为这个时候客户端已经关闭,就可以统一的closesocket关闭连接同时返回
closesocket(ConnectSocket);
return -1;
}
结果
- 客户端参数设置
传入参数:目标IP地址,端口号以及要传输的文件名。 - 文件信息
传输的是一个PDF文件,大小为
- 客户端与服务端交互
运行结果:
客户端
从上图中可以看到:
客户端可以传输文件名、传输文件长度以及打印出文件成功发送之后,服务端返回的确认信息。得到确认之后退出。
服务端
从图中可以看出:
服务端可以得到客户端的地址和端口信息、可以打印出即将接受的文件名、文件的长度。同时在文件接收完毕的时候给出提示,打印出文件内容。最后给客户端发送确认信息。客户端在得到确认之后退出。
当传输文件过大的时候,服务端循环接受数据,并且在每次接受的时候,打印出相关信息。
并且
在客户端的文件目录下,可以看到传过来的数据,并且可以正常打开
由此,实验结果达到了要求
代码
客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <stdlib.h>
#include <stdint.h>
#include <fstream>
#include<io.h>
#include <stdint.h>
using namespace std;
#pragma comment (lib,"ws2_32.lib")
using namespace std;
//缓存区长度
#define MAX_LEN 100000
int __cdecl main(int argc, char* argv[])
{
WSADATA ws;
SOCKET ConnectSocket = INVALID_SOCKET;
struct sockaddr_in Server_addr;
int iResult = 0;
int Addrlen = 0;
HANDLE hThread = NULL;
char Sendbuffer[MAX_LEN];
char Recvbuffer[MAX_LEN];
//检查参数个数
if (argc != 4)
{
cout << "Need enter target IP, port and the file name to transmit !" << endl;
return -1;
}
int PORT = atoi(argv[2]);
//初始化 socket
iResult = WSAStartup(MAKEWORD(2, 2), &ws);
if (iResult != 0)
{
cout << "Initiate failed with error: " << GetLastError() << endl;
WSACleanup();
return -1;
}
//创建 socket
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET)
{
cout << "Create Client socket failed with error: " << WSAGetLastError() << endl;
WSACleanup();
return -1;
}
Server_addr.sin_family = AF_INET;
Server_addr.sin_addr.s_addr = inet_addr(argv[1]);
Server_addr.sin_port = htons(PORT);
memset(Server_addr.sin_zero, 0x00, 8);
//连接 socket
iResult = connect(ConnectSocket, (struct sockaddr*)&Server_addr, sizeof(Server_addr));
if (iResult != 0)
{
cout << "Connect with error: " << WSAGetLastError() << endl;
closesocket(ConnectSocket);
WSACleanup();
return -1;
}
else
{
cout << "Connect successfully!" << endl;
}
char filebuf[MAX_LEN + 1]; //文件内容存储数组
//传输文件,先传输文件名,再传输文件长度,最后传输文件内容
while (true)
{
cout << "Ready to send file" << endl;
int len3 = strlen(argv[3]);
//对文件名传输进行处理
char filename[MAX_PATH];
int i = 0;
for (i = 0; i < len3; i++)
{
filename[i] = argv[3][i];
}
filename[i++] = '#';
//传输文件名
iResult = send(ConnectSocket, filename, strlen(filename), 0);
if (iResult == SOCKET_ERROR)
{
cout << "Send Filename : " << Sendbuffer << " with error " << WSAGetLastError() << "\n" << endl;
break;
}
//确定文件的大小
int len;
FILE* fp;
if(fp = fopen(argv[3], "r"))
{
fseek(fp, 0, SEEK_END);
printf("%ld\n", ftell(fp));
len = ftell(fp);
fclose(fp);
}
else
{
cout << "Error" << endl;
}
//以二进制文件形式读取文件
ifstream sourcefile(argv[3], ios::in | ios::binary);
int filelen = len;
int filelen_net = htonl(filelen);
//传输内容长度
cout << "Sending the length of file: " << filelen << "\n" << endl;
iResult = send(ConnectSocket, (char*)&filelen_net, 4, 0);
if (iResult == SOCKET_ERROR)
{
cout << "Send file length failed with error " << WSAGetLastError() << endl;
break;
}
//传输文件内容
cout << "Ready to send file!" << endl;
int left = filelen;
while (left>0)
{
if (left > MAX_LEN)
{
sourcefile.read(filebuf, MAX_LEN);
iResult = send(ConnectSocket, filebuf, MAX_LEN, 0);
if (iResult == SOCKET_ERROR)
{
cout << "Send file content failed with error " << WSAGetLastError() << "\n" << endl;
break;
}
left -= MAX_LEN;
}
else
{
sourcefile.read(filebuf, left);
iResult = send(ConnectSocket, filebuf, left, 0);
left = 0;
if (iResult == SOCKET_ERROR)
{
cout << "Send file content failed with error " << WSAGetLastError() << "\n" << endl;
break;
}
}
}
sourcefile.close();
if (left != 0)
{
cout << "Send file content failed with error " << WSAGetLastError() << "\n" << endl;
cout << "Client Exit..." << endl;
break;
}
//判断来自客户端的返回信息
iResult = recv(ConnectSocket, Recvbuffer, sizeof(Recvbuffer), 0);
Recvbuffer[iResult] = '\0';
if (iResult == SOCKET_ERROR)
{
cout << "Failed to receive confirm from server!\n" << endl;
break;
}
else if (strcmp(Recvbuffer, "OK\0") == 0)
{
cout << "Received confirm from server: " << Recvbuffer << "\nSending file succeed!\n" << endl;
break;
}
}
closesocket(ConnectSocket);
WSACleanup();
system("pause");
return 0;
}
服务端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <iostream>
#include <stdlib.h>
#include <stdint.h>
#include <fstream>
using namespace std;
#pragma comment (lib,"ws2_32.lib")
#define PORT 4000
#define IP_ADDR "0.0.0.0"
#define MAX_LEN 100000
//定长数据传输
int recvn(SOCKET s, char *recvbuf, unsigned int fixedlen)
{
int iResult;
int cnt; //记录当前待接受的数量
cnt = fixedlen;
while (cnt > 0)
{
iResult = recv(s, recvbuf, cnt, 0);
if (iResult < 0)
{
cout << "Receive failed with error :" << WSAGetLastError() << endl;
return -1;
}
if (iResult == 0)
{
cout << "Data is not as long as fixed. Connection closed" << endl;
return fixedlen - cnt;
}
recvbuf += iResult;
cnt -= iResult;
}
return fixedlen;
}
//变长内容传输
int recvvl(SOCKET s, char*filename)
{
int iResult;
long recvlen;
int len_tran;
char Recvb[MAX_LEN];
//接收文件长度数据
iResult = recv(s, (char*)&len_tran, 4, 0);
if (iResult != 4)
{
if (iResult == -1)
{
cout << "Receive failed with error " << WSAGetLastError() << endl;
return -1;
}
else
{
cout << "Length data is not complete. Connection closed\n" << endl;
return -1;
}
}
recvlen =ntohl( len_tran);
cout << "The length of file to receive is " << recvlen << "\n" << endl;
ofstream rec_file;
rec_file.open(filename, ios::binary);
int left = recvlen;
while (left > 0)
{
if (left > MAX_LEN)
{
iResult = recvn(s, Recvb, MAX_LEN);
if (iResult == -1)
{
cout << "Receive failed with error " << WSAGetLastError() << endl;
return -1;
}
else if (iResult == 0)
{
cout << "Receive data stopped. There "<<left<<" bytes unreceived" << endl;
break;
}
else
{
cout << "Receive partial data : " << iResult << " bytes" << endl;
rec_file.write(Recvb, MAX_LEN);
left -= MAX_LEN;
}
}
else
{
iResult = recvn(s, Recvb, left);
if (iResult == -1)
{
cout << "Receive failed with error " << WSAGetLastError() << endl;
return -1;
}
else if (iResult ==0 )
{
cout << "Receive data stopped. There " << left << " bytes unreceived" << endl;
break;
}
else
{
cout << "Receive data : " << iResult << " bytes" << endl;
rec_file.write(Recvb, left);
left = 0;
}
}
}
if (left == 0)
{
cout << "Receive all the data\n" << endl;
}
return recvlen;
}
//自己定义的结构体,用于多线程传参
struct my_para
{
SOCKET connect_socket;
struct sockaddr_in addrinfo;
};
//多线程函数
DWORD WINAPI ClientThread(LPVOID lpparameter)
{
struct my_para information = *(struct my_para *)lpparameter;
int iResult = 0;
char RecvBuffer[MAX_LEN];
SOCKET ConnectSocket = information.connect_socket;
int filelength = 0;
char filename[MAX_PATH] = { 0 };
while (TRUE)
{
//接收名字
iResult = recv(ConnectSocket, RecvBuffer, sizeof(RecvBuffer), 0);
if (iResult == SOCKET_ERROR || iResult == 0)
{
cout << "Client(" << inet_ntoa(information.addrinfo.sin_addr) << " : " << information.addrinfo.sin_port << ") Exited" << endl;
//每次如果出现任何错误,continue会回到这里,因为这个时候客户端已经关闭,就可以统一的closesocket关闭连接同时返回
closesocket(ConnectSocket);
return -1;
}
//对接收的文件名进行处理,存储下来,在后面本地保存时使用
int i = 0;
for (i; RecvBuffer[i] != '#'; i++)
{
filename[i] = RecvBuffer[i];
}
filename[i] = '\0';
cout << "The file " << filename << " is to receive!\n" << endl;
//接收文件
cout << "Ready to receive file!\n" << endl;
iResult = recvvl(ConnectSocket, filename);
if (iResult == 0 || iResult == -1)
{
cout << "Send file failed !\n\n\n" << endl;
continue;
}
//文件传输成功时,返回OK信息
char returnmessage[] = "OK\0";
iResult = send(ConnectSocket, returnmessage, strlen(returnmessage), 0);
if (iResult == SOCKET_ERROR)
{
cout << "Return confirm message with error " << WSAGetLastError() << endl;
continue;
}
else
{
cout << "Return success!\n" << endl;
}
}
closesocket(ConnectSocket);
return 0;
}
int main(int argc, char * argv[])
{
WSADATA ws;
SOCKET ServerSocket = INVALID_SOCKET, ClientSocket = INVALID_SOCKET;
struct sockaddr_in LocalAddr, ClientAddr;
int iResult = 0;
int Addrlen = 0;
HANDLE hThread = NULL;
//Initiate
iResult = WSAStartup(MAKEWORD(2, 2), &ws);
if (iResult != 0)
{
cout << "Initiate failed with error: " << WSAGetLastError() << endl;
return -1;
}
//create socket
ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ServerSocket == INVALID_SOCKET)
{
cout << "create socket failed with error: " << WSAGetLastError() << endl;
WSACleanup();
return -1;
}
LocalAddr.sin_family = AF_INET;
LocalAddr.sin_addr.s_addr = inet_addr(IP_ADDR);
LocalAddr.sin_port = htons(PORT);
memset(LocalAddr.sin_zero, 0x00, 8);
//bind socket
iResult = bind(ServerSocket, (struct sockaddr *)&LocalAddr, sizeof(LocalAddr));
if (iResult != 0)
{
cout << "Bind socket failed: " << WSAGetLastError() << endl;
closesocket(ServerSocket);
WSACleanup();
return -1;
}
//listen
iResult = listen(ServerSocket, 10);
if (iResult != 0)
{
cout << "Listen socket failed with error: " << WSAGetLastError() << endl;
closesocket(ServerSocket);
WSACleanup();
return -1;
}
cout << "Server is already setup!\n" << endl;
SOCKET acc_sock;
while (TRUE)
{
Addrlen = sizeof(ClientAddr);
acc_sock = accept(ServerSocket, (struct sockaddr*)&ClientAddr, &Addrlen);
if (acc_sock < 0)
{
cout << "Accept failed with error: " << WSAGetLastError() << endl;
break;
}
char *addr_temp = inet_ntoa(ClientAddr.sin_addr);
cout << "Client has already connected! (" << addr_temp << " : " << ClientAddr.sin_port << ")\n" << endl;
struct my_para temp;
temp.addrinfo = ClientAddr;
temp.connect_socket = acc_sock;
hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)&temp, 0, NULL);
if (hThread == NULL)
{
cout << "Create Thread failed!" << endl;
break;
}
CloseHandle(hThread);
}
closesocket(ServerSocket);
closesocket(ClientSocket);
WSACleanup();
system("pause");
return 0;
}