网络编程——TCP文件传输
要求
- 客户端向服务端发起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;
}