这是大一写的课程设计。
点对点通信及文件传输软件
一、软件分析
软件主要功能为通信及文件传输,通信不局限于局域网,文件传输功能适合小文件发送,传输大文件速度较慢。
软件可以方便的用于校园内(比如宿舍间,宿舍内)的文件传送,这样就解决了xp系统和win7系统的共享难题,也避免了频繁使用U盘对电脑USB接口的损伤。
软件为点对点式,即每次通信只有两个用户,其中一个为客户端,另一个为服务端,因此也就对应了两个应用程序,分别将其命名为“TcpServer”和“TcpClient”。
二、软件实现方案
1.网络传输协议的选择
软件要实现通信及文件传输功能,就必然要通过网络传输协议,协议是架于硬件以及操作系统之上的。在此使用了最为广泛应用的TCP/IP协议。
在TCP/IP协议栈中,有两个高级协议是我们网络应用程序编写者应该了解的,它们是“传输控制协议”(Transmission Control Protocol,简称TCP)和“用户数据报协议”(User Datagrm Protocol,简称UDP)。
TCP是面向连接的通信协议,TCP提供两台计算机之间的可靠无错的数据传输。应用程序利用TCP进行通信时,源和目标之间会建立一个虚拟连接。这个连接一但建立,两台计算机之间就可以把数据当作一个双向字节流进行交换。
UDP是无连接通信协议,UDP不保证可靠数据的传输,但能够向若干个目标发送数据,接收发自若干个源的数据。简单地说,如果一个主机向另外一台主机发送数据,这一数据就会立即发出,而不管另外一台主机是否已准备接收数据。如果另外一台主机收到了数据,它不会确认收到与否。
由于此软件为点对点通信,并且为了使两台计算机之间传输的文件数据不会丢失或发生错误,应该采用TCP协议。
2.如何用VC++实现
在VC++中,针对软件主要功能即网络之间的通信的实现方法有以下两种:(1)使用WinSock API函数(2)使用MFC提供的类CAsyncSocket或CSocket。
对于第一种方法,具体实现为服务器端首先要调用socket()函数建立一个流式套接字,再用bind()函数与本机的一个端口建立关联,继续调用listen()函数将套接字置于被动的侦听方式以监听连接,然后调用accept()函数进入等待状态之后才可以接收来自客户端的请求,一旦接收到客户端通过connect()发出的连接请求,accept()将返回一个新的套接字描述符。通过此套接字描述符调用send()或recv()函数即可与客户端进行数据收发。待数据传送完成,服务器客户端调用closesocket()关闭套接字。
该方法在编程过程工作量大,编程效率低,但却可以加深对网络协议的认识。
第二种方法,MFC提供了两个类CSocket和CAsyncSocket,他们封装了有关Socket的Windows API。其中CSocket是CAsyncSocket的子类。CAsyncSocket提供了对API的初步封装,CSocket提供了对API的高度封装。使用这两个类都相对直接使用WinSock API简单了许多。
默认CSocket是同步的而CAsyncSocket是异步的,因此当调用CSocket的对象的accept等函数时会阻塞主线程,从而使用户界面停止响应,所以需要新建子线程来执行这些操作,编程时发现在不同线程对同一个CSocket对象进行操作时总会发生错误。经过查阅MSDN发现:CSocket并不是线程安全的,不同线程是不能传递MFC的Object的,只能传递Object的handle,因此对同一个CSocket的对象操作只能在同一线程中进行,这样在新线程中就无法对主线程的CSocket对象进行操作。若直接使用WinSock API来进行,同时设置SOCKET为异步模式,就不必为SOCKET新建线程。
2.软件中采用的工具
通过上面的讨论及对软件编写情况分析决定:(1)通信时在服务端使用WinSock API并将SOCKET的I/O模式设为非阻塞模式,在客户端使用CSocket,这样双方都不会导致主线程阻塞也就不用新建线程。(2)进行文件传输时,双方新建子线程进行,双方各有两个线程函数RecvFile和SendFile,在子线程中使用WinSock API建立SOCKET来传输文件,这样在文件传输的过程中仍然不会影响到双方通信。
编程环境为Win7+VS2008,客户端和服务端各有两个类。UML图如下:
TcpClient方
CMySocket |
+ OnClose(nErrorCode : int) + OnConnect(nErrorCode : int) + OnReceive(nErrorCode : int) |
CTcpClientDlg |
# m_hIcon : HICON + m_ipAddress : CString + m_sendPath : CString + m_recvPath : CString + m_bAgree : bool + m_pThread : CwinThread* + m_bar : CstatusBarCtrl + m_server : SOCKET + m_client : SOCKET |
+ CtcpClientDlg(pParent : CWnd* = NULL) + OnBnClickedExit() : afx_msg + OnBnClickedConnect() : afx_msg + OnBnClickedClear() : afx_msg + OnBnClickedSend() : afx_msg + OnBnClickedTransmit() : afx_msg + OnBnClickedBrowser() : afx_msg + OnOK() + CreateAndListen(nPort : int) : BOOL # OnInitDialog() : BOOL # OnPaint() : afx_msg # OnQueryDragIcon() : afx_msg HCURSOR |
TcpServer方
CTcpClientDlg |
# m_hIcon : HICON + m_ipAddress : CString + m_sendPath : CString + m_recvPath : CString + m_bAgree : bool + m_pThread : CwinThread* + m_bar : CStatusBarCtrl + m_client : CMySocket |
+ CtcpClientDlg(pParent : CWnd* = NULL) + OnBnClickedExit() : afx_msg + OnBnClickedStart() : afx_msg + OnBnClickedClear() : afx_msg + OnBnClickedSend() : afx_msg + OnBnClickedTransmit() : afx_msg + OnBnClickedBrowser() : afx_msg + OnOK() + CreateAndConnect(nPort : int,lpAddress : LPCSTR) : BOOL # OnSocket(wParam : WPARAM,lParam : LPARAM) : afx_msg long # OnInitDialog() : BOOL # OnPaint() : afx_msg # OnQueryDragIcon() : afx_msg HCURSOR |
三、具体代码分析
1.基础知识
在整个代码编写过程中遇到很多问题,经过查阅MSDN以及网上搜寻资料找到解决方案,先列出三个方面,其余写在代码分析中:
(1)CString.Format的用法说明:在MFC中使用CString来处理字符串是一个很好的选择,CString的Format方法为字符串的转换带来了很大的方便。Format用于转换的格式字符:%c单个字符,%d十进制整数(int),%ld十进制整数(long),%f十进制浮点数(float),%lf十进制浮点数(double),%o八进制数,%s字符串,%u无符号十进制数,%x十六进制数。
(2)自定义类访问对话框控件:要在自定义的类中访问主对话框空间,须获得主对话的指针。主对话框的对象在app类的InitInstance()方法中声明。查看app类的cpp文件,在InitInstance()方法中有如下语句:
CAppDlg dlg;
m_pMainWnd = &dlg;
主对话框指针被传给m_pMainWnd 成员,按f12查看其定义,发现m_pMainWnd 是在afxwin.h中声明的成员:
CWnd* m_pMainWnd;
由于MFC中的自定义类会自动包含stdafx.h头文件,且stdafx.h包含afxwin.h,因此根据注释,在自定义类中可以直接使用AfxGetApp()->m_pMainWnd来获取主对话框的指针。之后可通过
AfxGetApp()->m_pMainWnd()->GetDlgItem(***);
来获得要访问的主对话框控件的CWnd指针,在对该指针进行转化即可。
(3)解决回车键默认关闭窗口的问题:在一般情况下编写的对话框程序,用户在运行的时候,如果不注意按下了Enter或者ESC键,程序就会立刻退出,之所以会这样,是因为按下Enter键时,Windows就会自动去找输入焦点落在了哪一个按钮上,当获得焦点的按钮的四周将被点线矩形包围。如果所有按钮都没有获得输入焦点,Windows 就会自动去寻找程序或资源所指定的默认按钮(默认按钮边框较粗)。如果对话框没有默认按钮,那么即使对话框中没有OK按钮,OnOK函数也会自动被调用,对于一个普通的对话框程序来说,OnOK函数的调用,以为着程序会立刻退出。为了使Enter键无效,最简单的办法就是将CXXXDlg的OnOK函数写成空函数,然后针对OK按钮写一个新的函数来响应。