写在前面的话:功能是基于C/S模型的网络传输实现,要求是服务器端可以在局域网中任何机子上运行,客户端启动后自动寻找服务器端进行连接,之后,服务器端向已经连接的客户端发送命令,客户端根据命令执行相应的操作(即发送某个约定文件夹下的所有文件),并且客户端不需要用户操作。

1、思路

首先,对于这个功能的实现思路如下,因为服务器不确定在哪个机子上,所以为了寻找到服务器端,客户端需要发送广播消息,并且为了维护客户端在线,广播消息需要实现成心跳包(即定时发送广播消息)。服务器监听心跳包,如果是新加入的客户端,则更新用户列表,否则不做处理。这是维护在线,离线的实现还在考虑中。

第二,当服务器端发送命令给客户端时,客户端根据命令来决定发送哪个文件夹中的文件给服务器端,由于文件夹下可能有多个文件,所以需要有个循环,循环发送每个文件。我的处理时在发送前先发送即将发送的文件个数,之后循环发送每个文件,在每个文件的发送过程中,首先发送文件名,之后发送文件长度,最后循环发送文件内容。当然服务器端接受的时候也需要按这个顺序来接收。

2、实现

客户端:

开启一个线程来发送心跳包,每发送一个心跳包,就睡眠3S,接着发送下一个,如此循环,心跳包采用UDP套接字来发送。

当接受命令时,客户端转换为C/S模型中的服务器端,因此客户端需要开启一个线程来监听固定端口,接受服务器端发送来的命令,之后根据命令执行相应的操作。

服务器端:

开启一个线程监听UDP心跳包。根据心跳包的IP地址判断是否做处理。

当点击发送命令按钮时,开启一个线程连接选中的客户端,发送命令并接收数据。

3、碰到的问题

(1)客户端的监听是使用WSAAsyncSelect()函数注册网络事件来实现的,注册了FD_ACCEPT、FD_READ、FD_CLOSE三个网络事件,代码如下:

// TCP套接字if(InitTcpSock()){if(BindTcpSock()){if(SOCKET_ERROR == WSAAsyncSelect(m_tcpSocket, m_hWnd, WM_SOCKET, FD_ACCEPT | FD_READ | FD_CLOSE)){DWORD dwError = 0;dwError = GetLastError();ShowError(_T("注册网络事件失败"), dwError);return FALSE;}listen(m_tcpSocket, 2);}}

在FD_READ网络事件发生时做处理,按接收到的命令执行相应的操作,出问题的地方就是当接受到发送文件的命令,执行相应的操作,发送指定文件夹下所有文件时,代码如下:

  1 // 发送指定文件夹所有文件
  2 UINT CClientDlg::SendFolderThread(LPVOID lpParameter)
  3 {
  4     SendParam *pSendParam = (SendParam*)lpParameter;
  5     BOOL bIsNull(TRUE);
  6     DWORD dwError(0);
  7
  8     SOCKET socket = pSendParam->socket;
  9
 10     CString strFolderPath = pSendParam->szFilePath;
 11
 12     int iFileSum = FileSum(strFolderPath, 0);
 13     if(iFileSum == 0)
 14     {
 15         return dwError;
 16     }
 17     else
 18     {
 19         // 发送文件总数
 20         char szFileSum[10] = {0};
 21         _itoa_s(iFileSum, szFileSum, 10);
 22         if(SOCKET_ERROR == send(socket, szFileSum, 10, NULL))
 23         {
 24             dwError = GetLastError();
 25             ShowError(_T("发送文件总数失败"), dwError);
 26             delete pSendParam;
 27             pSendParam = NULL;
 28             return dwError;
 29         }
 30
 31         strFolderPath.Append(_T("\\*"));
 32         CFileFind finder;
 33         BOOL isFind = finder.FindFile(strFolderPath);
 34         CString strFilePath;
 35         CString strFileName;
 36
 37         while(isFind)
 38         {
 39             isFind = finder.FindNextFile();
 40             if(!finder.IsDots())
 41             {
 42                 strFilePath = finder.GetFilePath();
 43                 strFileName = finder.GetFileName();
 44                 CFile file;
 45                 if (!file.Open(strFilePath, CFile::modeRead))
 46                 {
 47                     dwError = GetLastError();
 48                     ShowError(_T("打开文件失败"), dwError);
 49                     delete pSendParam;
 50                     pSendParam = NULL;
 51                     return dwError;
 52                 }
 53
 54                 char strBuf[512] = {0};
 55                 char strEnd[512] = {0};
 56
 57                 UINT len = 0;
 58                 // 发送文件名
 59                 WideCharToMultiByte(CP_ACP, 0,
 60                     strFileName.GetBuffer(strFileName.GetLength() - 1),
 61                     strFileName.GetLength(), strBuf, 511, 0, 0);
 62                 strFileName.ReleaseBuffer();
 63                 strBuf[strFileName.GetLength()] = 0;
 64                 if(SOCKET_ERROR == send(socket, strBuf, 512, NULL))
 65                 {
 66                     dwError = GetLastError();
 67                     ShowError(_T("发送文件名失败"), dwError);
 68                     return dwError;
 69                 }
 70                 // 发送文件内容
 71                 memset(strBuf, 0, 512);
 72                 while(len = file.Read(strBuf, 511))
 73                 {
 74                     if(SOCKET_ERROR == send(socket, strBuf, 512, NULL))
 75                     {
 76                         dwError = GetLastError();
 77                         ShowError(_T("发送文件失败"), dwError);
 78                         return dwError;
 79                     }
 80                     memset(strBuf, 0, 512);
 81                     if(len < 511)
 82                     {
 83                         send(socket, strEnd, 512, NULL);
 84                         break;
 85                     }
 86                 }
 87                 // 如果文件长度刚好是511的整数倍
 88                 if(0 == len)
 89                 {
 90                     send(socket, strEnd, 512, NULL);
 91                 }
 92                 file.Close();
 93             }
 94         }
 95         //AfxMessageBox(_T("文件传送完毕"));
 96     }
 97     delete pSendParam;
 98     pSendParam = NULL;
 99     return dwError;
100 }

问题就出在这段代码的74行,程序运行的时候总是提示10035错误代码,根据错误代码大全,这个错误是无法立即完成一个非阻挡性套接字操作,原因就是说缓冲区已满,无法立即完成发送。在网上搜了一下,感觉这个帖子解释的挺好的,摘抄几个个人认为解释的不错的回复:

  1. 10035错误的原因是无法立即完成一个非阻挡性套接字操作。
    原因就说缓冲区满了 所以无法立即
  2. TCP情况下:
    负责监听和接收的socket:可以采用非阻塞;
    负责发送的发送的socket要采用阻塞模式;

    因为TCP协议在网络上传输的两端是阻塞传输的,你在代码中设置非阻塞只是要socket把数据放到本地缓冲区上就不要管了,其实数据还在本地缓冲区,在上次发出去的数据没有得到回应以前,数据将一直不会发出去,但这时你的socket又把下一批数据放入缓冲区,

    在本机上,把发送端的包大小设置为10240,接收的包设置为5120,会立即出错。

    解决办法:
    1.TCP情况下,把负责发送的socket采用阻塞模式;
    2.采用UDP

  3. 10035出错的原因是这样的
    因为你采用了WSAEventSelect,这个函数会自动把socket设成了非阻塞模式。
    非阻塞模式的socket在send前一定要检查socket的当前状态是否为可send状态,因为你没有合理的检查机制,导至在socket不可send的状态你send了。
    阻塞模式的socket在send的时候不用检查状态的,可以直接send.

    阻塞模式 写法简单,适合单连接。
    用阻塞模式写多连接也可以,不过有几个连接,就得最少开几个线程,为方便控制,书上推荐开连接数*2个线程。

    非阻塞模式,相当难控制,对于现在的我来说,是个很大的工程,正在学习中...
    如果有多个连接,书上建议用非阻塞模式,因为不管有多个连接,只要1-2个线程就可以搞定。

    非阻塞模式有5种控制方法如下(抄书):
    1.select模型
    2.WSAAsyncSelect
    3.WSAEventSelect
    4.重叠模型
    5.完成端口模型

  4. 楼上说的其实都没说到关键
    楼主这样发送大文件的方法根本不是正确的方法,因为这样根本没有考虑发送缓存是否已经满了或网络的异常状况,
    这样一直发一直发,完全不考虑结果,只要有一个send没发送成功,那么本次文件发送就会失败,在实际应用中根本不可取;
    楼主的做发是一种理想的做法;

    如果非要这样循环读\循环send的话,可以在每次循环的时候Sleep(10)一下,这样基本就没有问题,但不推荐这样做,这样会
    使程序效率极其低效;

    要做好大文件的发送,要考虑以下几个方面:
    1, 发送缓存的选取
    2, 文件的读取,可以考虑使用内存文件,如果能够一次性读取的话,就不要读那么多次,总之就是要减少读取的次数
    *  由于Tcp/ip协议能够自己进行流量控制,所以即使你在发送的时候一次将整个文件一次发送,也不会有问题
    *  send的时候没必要每次只发送一点点数据,太影响效率了,一个文件要发送很久才能传完

  5. 楼上的,
    可以在每次循环的时候Sleep(10)一下,这样基本就没有问题
    //Sleep(1000)该出问题还是会出问题,但通常加上Sleep(1),这样为防止CPU 100%,而不是防出错

    我搞不明白,把文件拆开(只要不拆成1bit),有什么不妥 1040-10240之间都可以运行的很好
    发整个文件,一个文件4M的话,就是4M内存,机器只运行你一个程序?

  6. 楼上的,你没明白我的意思
    姑且不论Sleep(1)是为了防止CPU 100%,还是防出错,这无关紧要,
    一个合理高效的服务器是不会这样发送数据的;

    大文件100%是要拆的,毫无疑问
    一般的小文件,又能占多少内存呢!为什么不能一次读完?
    我想说的是,每次"尽量"多从文件读些数据进来,避免频繁读写磁盘,象这样的代码是没有任何问题的:
    int ret = fread( bug, sizeof(char) , 65536, pFile);
    int nsend = send(mSock,(char *)bug, ret, 0);

  7. 完成端口不存在这个问题是因为操作系统内部对待发送的数据进行了缓存,也就是说如果TCP的发送缓冲区已满则,操作系统会将你要发送的数据加入待发送队列当检测到发送缓存区中有空闲的时候在进行发送。这也就是为什么在进行完成端口的发送时数据往往需要从堆上分配而不是栈上分配的具体原因。所以单纯从吞吐量的角度来看完成端口不一定比 select + 非阻塞套接字的方式更高。

我认为第三个说的不错,非阻塞模式的socket在send前一定要检查socket的当前状态是否为可send状态,因为自己是初学网络编程,对于select模型的使用还不太熟练,所以不会使用他说的用select来检测套接字缓冲区是否已满。最后不得以换成了开线程的方法来实现。其次第五个说使用sleep这种方法我试了,不行。还是会出现同样的错误代码。

记一个网络传输功能的实现过程相关推荐

  1. 计算机网络-IP数据报计算(IP数据报分片)一个数据报部分长度为3400字节(使用固定首部)。现在经过一个网络传输,该网络的MTU为800字节:

    IP数据报计算(IP数据报分片) 题目: 一个数据报部分长度为3400字节(使用固定首部).现在经过一个网络传输,该网络的MTU为800字节: (1)应分为几个数据报片? (2)各数据报片的数据字段长 ...

  2. 一个网络传输框架——zeroMQ 调研笔记

    一.它是什么 zeroMQ,一个处理消息传输的库,重点在传输上,看起来它像是在socket上面封装了一层,让我们可以很容易的利用它来做N对M的数据传输,在分布式系统中很方便,在接收端它有round-r ...

  3. 记一个简单的Android计时器制作过程。

    刚学Handler,做一个计时器,就是秒表啦. 开始试图用Handler延迟发送消息来读秒,发现根本不现实,根本不会有准确的时间. 改成了比较当前时间和开始时间后成功计时. 没什么难度,就直接放源码啦 ...

  4. usb设备复合g_webcam摄像头码流传输功能以及g_serial串口功能

    0.为什么要复合?因为usb驱动里的UDC(USB设备控制器)数量不够,会报错 udc资源已占用.所以使用insmod.modprobe等加载模块的方式一次只能有一个功能(内核定值好的三功能模块g_m ...

  5. ASP网络多功能办公系统设计与实现

    互联网的出现与迅速发展,信息技术步伐的加快,使得企业面临着众多的挑战与竞争,在竞争过程中,对于一个集团企业而言,对信息的掌握程度.信息获取是否及时.信息能否得到充分的利用.对信息的反应是否敏感准确,也 ...

  6. win10使用网络共享功能的方法

    win10系统有一个网络共享功能,可以实现局域网之间文件和打印机的共享操作,从而有效提高资源共享,实现更高效的办公需求.好多新手用户不知道网络共享功能怎么使用?为此,本文教程来和大家详细说明一些使用方 ...

  7. java上传图片特征码到服务器,记一个Base64编码后经网络传输产生的问题

    问题:机器特征码经过网络传输之后,'+'都变成了' ' 详情:机器特征码提取了机器cpuId和mac地址信息,最后经过Base64编码后生成的字符串,一开始我使用的是 String strs = ne ...

  8. 【C++进阶】详解C++开源网络传输库libcurl的编译过程

    目录 1.引言 2.直接编译libcurl工程,提示找不到ssh.h 3.下载并编译libssh2开源代码,部署到libcurl目录中 4.获取openssl开源库的头文件和库文件,部署到libcur ...

  9. 计算机网络中 中继器的作用是,一个动画看懂网络原理之中继器工作过程

    一个动画看懂网络原理之中继器工作过程 我们在学习计算机网络技术时,涉及到网络设备时,书本上往往在介绍网卡之后,就会给大家介绍中继器,那么中继器是什么,它的作用是什么,它的工作过程又是怎样的,下面就这些 ...

最新文章

  1. Loadrunner检查点使用总结
  2. python3在线-荐python3在线编程输入输出总结
  3. Common Attention Points
  4. Java ServletContextListener用法
  5. ios开发第一步--虚拟机安装MAC OS X
  6. 【Java线程池】Java线程池汇总,看这一篇文章就够了
  7. winsock使用java编写_利用Socket进行Java网络编程(一)
  8. C++ 常量类型 const 详解
  9. Ajax+PHP快速上手及简单应用
  10. 通过Java技术手段,某程序员发现自己被绿了!
  11. https://blog.csdn.net/zxp_cpinfo/article/details/53692922
  12. 【评测】各种细胞治疗处理技术设备
  13. 小型的 JavaScript 虚拟键盘
  14. “感动中国”2012年度人物颁奖词
  15. 华为实验17-ospf多区域配置
  16. 1.19(Cake Baking)
  17. MFC win32 API串口同步模式代码示范
  18. 访问学者申请美国J1签证英语要求有规定吗?
  19. found duplicated code in this file
  20. firefox 显示网页加载时间的插件

热门文章

  1. java mvc 导入_Java SpringMVC文件导入和导出
  2. 强迫用户升Win10?旧版Windows放弃对新CPU更新支持
  3. Web后端语言模拟http请求(带用户名和密码)实例代码大全
  4. mysql 查看表的类型
  5. 【物联网智能网关-03】GPRS模块中文短信收发
  6. 【转载】Hook钩子C#实例
  7. flink的kafka各种依赖区别
  8. ubuntu20.04安装讯飞输入法(失败经历)
  9. HBase-site.xml 常见重要配置参数(转载)
  10. ERROR: Command errored out with exit status 1一例