首页 > 技术文章 > send和recv (大文件传输)

chaikefusibushiji 2014-08-26 19:38 原文

实验环境:

        Windows

实验目的:

        使用socket传输大文件(19.4M)


实验过程:

    第一阶段

        1.Server使用send(s,buffer,dataLen,0)将文件装入buffer一次发送出去

           结果:成功

        2.Client端使用char ch[20*1024*1024] = {0}开出20M栈空间以接数据

           结果:栈溢出

          

          应对方法:

                使用堆分配char *ch = new char[20*1024*1024]

        3.Client接收数据int ret = recv(s,ch,20*1024*1024,0)

           结果:recv只接了27K数据,Server中send显示发送数据为19.4M

        4.Client采用循环接收,不断的recv

           结果:文件传输成功


    第二阶段

        5.本机开Server,Client跑在其它电脑

           结果:Client接收失败

        6.在Client的循环接收中加入输出,打印差多少字节

           结果:循环输出42

               分析原因:

                    Server在send mfc.zip之前先send了8字节文件长度,Client在recv mfc.zip前先使用50字节长的缓冲区tmp[50] recv了长度,推断:差                                 的42字节被tmp收走

               验证:

                    添加输出,在recv长度处打印此次接收的长度

               结果:

                    输出50,Server send发送长度设置正确

        7.将tmp的后42字节加入buffer中并修改代码

           结果:成功


特殊现象概述:

        发送方:send两次,第一次send 8字节,第二次send 19.4M

        接收方:8字节在前、19.4M在后,两部分合二为一、一起到达


实验结论:

        1.系统发送缓冲区一定很大,即send(s,buffer,dataLen,0)19.4M一次发送没有问题。

        2.系统接收缓冲区有限制,即recv(s,buffer,bufferLen,0)不管buffer多大一次只能几十到上百K,只有循环接收才能解决。

        3.小数据发送会产生粘包。


附加内容:

        1.send函数

                int send(SOCKET s, const char *buffer, int len, int flag)

               数据从buffer拷贝到transport buffer,然后进行发送。len大小为多少便发送多少的数据。

        2.程序栈和堆的大小

                linux系统下默认栈大小是10M,windows系统下默认栈大小是1M.
                windows下用vs2010编译C++程序时,编译属性中可以重新设定栈大小.
                堆的话,理论上内存有多大,就可以建多大.
                但32位的程序在64位系统上运行的时候,一个进程的堆大小应该是不可以超过4G的.

                栈和线程相关, 默认1MB预留, 初次递交8KB, 自动增长, 具体使用要看线程调用栈了. 所以如果进程中有N个线程. 默认情况下, 有N*1MB的栈预留                 空间, 和小于这个数字的实际使用.
                堆和Heap管理有关, 默认存在系统堆和CRT堆. 具体大小取决于程序本身对内存的分配和使用, 可以调用HeapSize看实际使用大小. 
                另外还有虚拟内存, 独立于对堆外, 直接通过VirtualAlloc预留或分配. 也属于进程动态分配的内存.


实验代码:

Server

#include <WinSock2.h>
#include <Windows.h>
#include <iostream>
#include <string>
#include <fstream>

#pragma comment(lib, "Ws2_32.lib")

int main()
{
	WSADATA ws;

	if(WSAStartup(MAKEWORD(2, 2), &ws) != 0)
		return false;

	SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
	if(INVALID_SOCKET == s)
		return false;

	SOCKADDR_IN	addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(4444);

	int ret = bind(s, (sockaddr*)&addr, sizeof(addr));
	if(SOCKET_ERROR == ret)
	{
		std::cout<<"bind failed : "<<GetLastError()<<std::endl;
		return SOCKET_ERROR;
	}

	ret = listen(s, 10);
	if(SOCKET_ERROR == s)
	{
		std::cout<<"listen fail : "<<GetLastError()<<std::endl;
		return SOCKET_ERROR;
	}

	while(true)
	{
		SOCKADDR_IN clientAddr;
		int addrLen = sizeof(clientAddr);

		SOCKET cs = accept(s, (sockaddr*)&clientAddr, &addrLen);
		if(INVALID_SOCKET == cs)
		{
			std::cout<<"accept failed : "<<GetLastError()<<std::endl;
			continue;
		}

		std::cout<<"one connection enter into..."<<std::endl;

		{
			char buf[1024] = {0};
			ret = recv(cs, buf, 1024, 0);
			if(SOCKET_ERROR == ret)
			{
				std::cout<<"recv error : "<<GetLastError()<<std::endl;
				continue;
			}
			std::cout<<buf<<std::endl;
		}
		{
			char buf[] = "hello baby\r\n";
			ret = send(cs, buf, strlen(buf), 0);
			if(SOCKET_ERROR == ret)
			{
				std::cout<<"send error : "<<GetLastError()<<std::endl;
				return -1;
			}
		}
		{
			std::ifstream is("C:\\Documents and Settings\\Administrator\\桌面\\mfc.zip", std::ios::binary);
			is.seekg(0, is.end);
			int length = is.tellg();
			is.seekg(0, is.beg);

			char *ch = new char[length+10];

			std::string str = std::to_string((long long)length);
			memset(ch, 0, 10);
			memcpy(ch, str.c_str(), str.length());
			is.read(ch+10, length);

			ret = send(cs, ch, length+10, 0);
			if(SOCKET_ERROR == ret)
			{
				std::cout<<"send large data error : "<<GetLastError()<<std::endl;
				return -1;
			}

			delete []ch;
			is.close();
		}

		Sleep(10000);

		closesocket(cs);
	}

	return 0;
}

Client

#include <WinSock2.h>
#include <Windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <process.h>

#pragma comment(lib, "Ws2_32.lib")


int main()
{
	WSADATA ws;

	if(WSAStartup(MAKEWORD(2, 2), &ws) != 0)
		return false;

	SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
	if(INVALID_SOCKET == s)
		return false;

	sockaddr_in addr = {0};
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.4.18");
	addr.sin_family = AF_INET;
	addr.sin_port = htons(4444);

	int ret = connect(s, (sockaddr*)&addr, sizeof(addr));
	if(SOCKET_ERROR == ret)
	{
		std::cout<<"Connect Server Failed : "<<GetLastError()<<std::endl;
		return -1;
	}

	std::string str = "xiaogushihaoren";
	ret = send(s, str.c_str(), str.length(), 0);
	if(SOCKET_ERROR  == ret)
	{
		std::cout<<"Send Data Failed : "<<GetLastError()<<std::endl;
		return -1;
	}

	char ch[100] = {0};
	ret = recv(s, ch, 100, 0);
	if(0 == ret)
	{
		std::cout<<"Peer Close The Connection"<<std::endl;
		return -1;
	}
	else if(SOCKET_ERROR == ret)
	{
		std::cout<<"Recv Data Failed : "<<GetLastError()<<std::endl;
		return -1;
	}

	std::cout<<ch<<std::endl;

	{
		char tmp[10] = {0};
		ret = recv(s, tmp, 10, 0);
		int len = std::stoi(tmp);

		char *chmax = new char[len];

		int cursor = 0;
		while(cursor != len)
		{
			char ch[100*1024] = {0};
			ret = recv(s, ch, 100*1024, 0);
			if(SOCKET_ERROR == ret)
			{
				std::cout<<"Recv Data Failed : "<<GetLastError()<<std::endl;
				return -1;
			}

			if(0 == ret)
				break;

			memcpy(chmax+cursor, ch, ret);
			cursor += ret;
		}

		std::ofstream os("C:\\mfc1.zip", std::ios::binary);
		os.write(chmax, len);

		os.close();
	}

	std::cin.get();
	return 0;
}


推荐阅读