上回对这个程序做了个简单的介绍,这回我来讲讲具体实现细节.
首先最主要的是加载套接字库
在StdAfx.h中我们加入如下语句:
#include <afxsock.h>
然后初始化套接字库,在App类中的InitInstance()函数中加入如下语句:
if(!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
需要注意的是当程序连接的时候还要连接Ws2_32.lib这个库.接下来的工作我们都在Dialog的View类中完成,
对于对话框上控件的摆放和命名可自由搭配.其中每个按钮都有命令响应函数.两个进度条分别关联了两个成员变量.剩下的一个ip地址框,三个编辑框,用作用户和程序交流信息.
其中打开文件按钮的命令响应函数如下
void CWinSockDlg::OnOpen()
{
CFileDialog fileDlg(TRUE); //创建打开文件对话框
fileDlg.m_ofn.lpstrTitle="打开文件";
fileDlg.m_ofn.lpstrFilter="All Files(*.*)\0*.*\0\0"; //设定筛选
if(IDOK==fileDlg.DoModal())
{
m_sendfname=fileDlg.GetPathName();
]
//如果用户选择确定设置发送文件名并对对话框显示做响应调整
(GetDlgItem(IDC_EDIT1))->SetWindowText(m_sendfname);
(GetDlgItem(IDC_EDITSEND))->SetWindowText("准备好发送");
}
}
准备接收文件按钮和发送文件按钮响应函数如下
void CWinSockDlg::OnRecv()
{
sockrecv* sr=new sockrecv; //声明一个发送参数结构体
sr->hwnd=m_hWnd; //将主窗口句柄传送给它
HANDLE handl=CreateThread(NULL,0,SocketRecv,(LPVOID)sr,0,NULL);//创建线程用于接收
CloseHandle(handl);
}
void CWinSockDlg::OnSend()
{
socksend* ss=new socksend; //声明一个发送参数结构体
//取得IP地址控件上的地址值,设定发送文件名,传递主窗口句柄
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(ss->dwip);
ss->sendfname=m_sendfname;
ss->hwnd=m_hWnd;
HANDLE handl=CreateThread(NULL,0,SocketSend,(LPVOID)ss,0,NULL);
CloseHandle(handl);
}
下面是接收和发送参数结构体的定义:
struct sockrecv
{
HWND hwnd;
};
struct socksend
{
HWND hwnd;
DWORD dwip;
LPCSTR sendfname;
};
下面是最关键的接收线程函数,和发送线程函数:
DWORD WINAPI CWinSockDlg::SocketRecv(LPVOID lpParameter)
{
HWND hwnd=((sockrecv*)lpParameter)->hwnd;//将参数传递到本地变量
CString recvfname;
SOCKET socketrecv=socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(INVALID_SOCKET==socketrecv)
{
closesocket(socketrecv);
::MessageBox(hwnd,"套接字创建失败!","警告",MB_OK);
return FALSE;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(8848);
int ret1;
ret1=bind(socketrecv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //绑定套接字
if(SOCKET_ERROR==ret1)
{
closesocket(socketrecv);
::MessageBox(hwnd,"绑定失败!","警告",MB_OK);
return FALSE;
}
int ret2;
ret2=listen(socketrecv,5); //开始监听
if(SOCKET_ERROR==ret2)
{
closesocket(socketrecv);
::MessageBox(hwnd,"监听失败!","警告",MB_OK);
return FALSE;
}
SOCKADDR_IN addrconn;
int len=sizeof(SOCKADDR);
::PostMessage(hwnd,UM_PRGRRDY,0,0); //向主窗口发送消息
SOCKET sockConn=accept(socketrecv,(SOCKADDR*)&addrconn,&len); //开始接收
if(INVALID_SOCKET ==sockConn)
{
closesocket(socketrecv);
::MessageBox(hwnd,"接收失败!","警告",MB_OK);
return FALSE;
}
::PostMessage(hwnd,UM_PRGRBGN,0,0); //向主窗口发送消息
BOOL bRet = TRUE;
int dataLength, cbBytesRet, cbLeftToReceive;
BYTE* recdData = NULL;
CFile destFile;
CFileException fe;
BOOL bFileIsOpen = FALSE;
//当接收到向本地发送文件的请求时弹出文件保存对话框
CFileDialog fileDlg(FALSE);
fileDlg.m_ofn.lpstrTitle="保存将接收到的文件";
fileDlg.m_ofn.lpstrFilter="All Files(*.*)\0*.*\0\0";
if(IDOK==fileDlg.DoModal())
{
recvfname=fileDlg.GetPathName();
}
//用从文件保存对话框中得到的文件名在指定位置创建文件
if( !( bFileIsOpen = destFile.Open( recvfname, CFile::modeCreate |
CFile::modeWrite | CFile::typeBinary, &fe ) ) )
{
TCHAR strCause[256];
fe.GetErrorMessage( strCause, 255 );
TRACE( "GetFileFromRemoteSender encountered an error while opening the local file "
" File name = %s Cause = %s m_cause = %d m_IOsError = %d ",
fe.m_strFileName, strCause, fe.m_cause, fe.m_lOsError );
bRet = FALSE;
goto PreReturnCleanup;
}
//首先接收文件的长度信息
cbLeftToReceive = sizeof( dataLength );
do
{
BYTE* bp = (BYTE*)(&dataLength) + sizeof(dataLength) - cbLeftToReceive;
cbBytesRet = recv(sockConn, (char*)bp, cbLeftToReceive,0);
if ( cbBytesRet == SOCKET_ERROR || cbBytesRet == 0 )
{
int iErr = ::GetLastError();
TRACE( "GetFileFromRemoteSite returned a socket error while getting file length "
" Number of Bytes received (zero means connection was closed) = %d "
" GetLastError = %d ", cbBytesRet, iErr );
bRet = FALSE;
goto PreReturnCleanup;
}
cbLeftToReceive -= cbBytesRet;
}
while ( cbLeftToReceive > 0 );
dataLength = ntohl( dataLength ); //将文件的长度信息转化为本地字节序
//准备开始接收文件
recdData = new byte[RECV_BUFFER_SIZE];
cbLeftToReceive = dataLength;
do
{
//向主窗口发送消息主要用于控制进度条
::PostMessage(hwnd,UM_PRGRECV,0,(LPARAM)cbLeftToReceive);
int iiGet, iiRecd;
iiGet = (cbLeftToReceive<RECV_BUFFER_SIZE) ?
cbLeftToReceive : RECV_BUFFER_SIZE ;
iiRecd = recv(sockConn, (char*)recdData, iiGet,0 );
if ( iiRecd == SOCKET_ERROR || iiRecd == 0 )
{
int iErr = ::GetLastError();
TRACE( "GetFileFromRemoteSite returned a socket error while getting chunked file data "
" Number of Bytes received (zero means connection was closed) = %d "
" GetLastError = %d ", iiRecd, iErr );
bRet = FALSE;
goto PreReturnCleanup;
}
destFile.Write( recdData, iiRecd);
cbLeftToReceive -= iiRecd;
}
while ( cbLeftToReceive > 0 );
::PostMessage(hwnd,UM_PRGROVER,0,0); //向主窗口发送消息
PreReturnCleanup: //文件接收结束标签
delete[] recdData; //释放内存
if ( bFileIsOpen )
destFile.Close();
closesocket(sockConn);
closesocket(socketrecv);
return bRet;
}
下面是发送线程函数:
DWORD WINAPI CWinSockDlg::SocketSend(LPVOID lpParameter)
{
//将参数传递给本地变量
HWND hwnd=((socksend*)lpParameter)->hwnd;
CString sendfname=((socksend*)lpParameter)->sendfname;
DWORD dwip=((socksend*)lpParameter)->dwip;
SOCKET socketsend=socket(AF_INET,SOCK_STREAM,0);//创建Socket
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(dwip);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(8848);
int ret1;
ret1=connect(socketsend,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//连接指定地址
if(SOCKET_ERROR==ret1)
{
closesocket(socketsend);
::MessageBox(hwnd,"连接失败!","警告",MB_OK);
return FALSE;
}
::PostMessage(hwnd,UM_PRGSBGN,0,0); //向主窗口发送消息
//声明用于文件发送的本地变量
BOOL bRet = TRUE;
int fileLength, cbLeftToSend;
BYTE* sendData = NULL;
CFile sourceFile;
CFileException fe;
BOOL bFileIsOpen = FALSE;
//打开参数传递进来的文件用于发送
if( !( bFileIsOpen = sourceFile.Open( sendfname,
CFile::modeRead | CFile::typeBinary, &fe ) ) )
{
TCHAR strCause[256];
fe.GetErrorMessage( strCause, 255 );
TRACE( "SendFileToRemoteRecipient encountered an error while opening the local file "
" File name = %s Cause = %s m_cause = %d m_IOsError = %d ",
fe.m_strFileName, strCause, fe.m_cause, fe.m_lOsError );
bRet = FALSE;
goto PreReturnCleanup;
}
//首先传递将要被发送文件的长度给接收端
fileLength = sourceFile.GetLength();
fileLength = htonl( fileLength );
cbLeftToSend = sizeof( fileLength );
do
{
int cbBytesSent;
BYTE* bp = (BYTE*)(&fileLength) + sizeof(fileLength) - cbLeftToSend;
cbBytesSent = send(socketsend, (const char*)bp, cbLeftToSend,0 );
if ( cbBytesSent == SOCKET_ERROR )
{
int iErr = ::GetLastError();
TRACE( "SendFileToRemoteRecipient returned a socket error while sending file length "
" Number of Bytes sent = %d "
" GetLastError = %d ", cbBytesSent, iErr );
bRet = FALSE;
goto PreReturnCleanup;
}
cbLeftToSend -= cbBytesSent;
}
while ( cbLeftToSend>0 );
//开始发送晚间
sendData = new BYTE[SEND_BUFFER_SIZE];
cbLeftToSend = sourceFile.GetLength();
do
{
::PostMessage(hwnd,UM_PRGSEND,0,(LPARAM)cbLeftToSend);
int sendThisTime, doneSoFar, buffOffset;
sendThisTime = sourceFile.Read( sendData, SEND_BUFFER_SIZE );
buffOffset = 0;
do
{
doneSoFar = send(socketsend,(const char*) (sendData + buffOffset),
sendThisTime,0 );
if ( doneSoFar == SOCKET_ERROR )
{
int iErr = ::GetLastError();
TRACE( "SendFileToRemoteRecipient returned a socket error while sending chunked file data "
" Number of Bytes sent = %d "
" GetLastError = %d ", doneSoFar, iErr );
bRet = FALSE;
goto PreReturnCleanup;
}
buffOffset += doneSoFar;
sendThisTime -= doneSoFar;
cbLeftToSend -= doneSoFar;
}
while ( sendThisTime > 0 );
}
while ( cbLeftToSend > 0 );
::PostMessage(hwnd,UM_PRGSOVER,0,0);//向主窗口发送消息发送完毕
PreReturnCleanup: //结束标签后释放内存
delete[] sendData;
if ( bFileIsOpen )
sourceFile.Close();
closesocket(socketsend);
return bRet;
}
到这里主要的文件传送和接收功能已经实现了,剩下的就只有几个控制进度条,和传输接送状态的用户自定义消息了,在对话框的View类中定义如下消息:
#define UM_PRGRECV WM_USER+10
#define UM_PRGSEND WM_USER+11
#define UM_PRGROVER WM_USER+12
#define UM_PRGSOVER WM_USER+13
#define UM_PRGRRDY WM_USER+14
#define UM_PRGRBGN WM_USER+15
#define UM_PRGSBGN WM_USER+17
然后分别写这些消息的响应函数:
afx_msg void CWinSockDlg::onprgrecv(WPARAM wParam,LPARAM lParam)
{
//控制接收进度条的消息响应函数
int prgnow=(int)lParam; //将剩余接收字节作为参数传递给函数
if(m_beginprgrecv) //判断是否第一次接到此消息
{
m_prgrecv.SetRange32(0,prgnow); //设置进度条范围
m_prgrecvpre=prgnow; //给成员变量赋值
m_beginprgrecv=FALSE; //改变标志
}
int thistran=m_prgrecvpre-prgnow; //本次传输字节数
int now=m_prgrecv.GetPos(); //先前的总传输量
m_prgrecv.SetPos(thistran+now); //设置进度条位置
}
afx_msg void CWinSockDlg::onprgrover(WPARAM wParam,LPARAM lParam)
{
//恢复接收状态
m_prgrecv.SetPos(0);
m_prgrecvpre=0;
m_beginprgrecv=TRUE;
(GetDlgItem(IDC_EDITRECV))->SetWindowText("尚未准备好接收");
}
afx_msg void CWinSockDlg::onprgsend(WPARAM wParam,LPARAM lParam)
{
//请参考接收消息响应函数解释
int prgnow=(int)lParam;
if(m_beginprgsend)
{
m_prgsend.SetRange32(0,prgnow);
m_prgsendpre=prgnow;
m_beginprgsend=FALSE;
}
int thistran=m_prgsendpre-prgnow;
int now=m_prgsend.GetPos();
m_prgsend.SetPos(thistran+now);
}
afx_msg void CWinSockDlg::onprgsover(WPARAM wParam,LPARAM lParam)
{
//恢复发送状态
m_prgsend.SetPos(0);
m_prgsendpre=0;
m_beginprgsend=TRUE;
(GetDlgItem(IDC_EDITSEND))->SetWindowText("尚未准备好发送");
}
afx_msg void CWinSockDlg::onprgrrdy(WPARAM wParam,LPARAM lParam)
{
(GetDlgItem(IDC_EDITRECV))->SetWindowText("准备好接收");//UM_PRGRRDY消息响应
}
afx_msg void CWinSockDlg::onprgrbgn(WPARAM wParam,LPARAM lParam)
{
(GetDlgItem(IDC_EDITRECV))->SetWindowText("开始接收"); //UM_PRGRBGN消息响应
}
afx_msg void CWinSockDlg::onprgsbgn(WPARAM wParam,LPARAM lParam)
//UM_PRGSBGN消息响应
{
(GetDlgItem(IDC_EDITSEND))->SetWindowText("开始发送");
}
到这里所有功能都实现了,是不是很简单.本实例源代码:下载