昨天花了点时间,写了个小程序,做了个局域网内的文件传输工具。当然了,关键在于学习,局域网内的文件用共享就好了,还有什么好传的。但里面用到的知识是可以用在INTERNET上的,只不过如果用在公网上的话一般还需要一个服务器的中转,多一步NAT穿透技术的支持。就是在做这个小程序的时候也出现过一些问题。先把关键部分代码贴出,再细细解释。
struct FILE_INFO
{
int FileLength;
char FileName[50];
char* buf;
};
这是我用于传输文件信息的结构体,本来我将buf定义为CString类型,在传送阶段没有出现错误,很正常,但是在接收端却出现了异常,接收端接收到的buf显示的是乱码,至今不知道为什么。
void CXYFTDlg::OnBtnSend()
{
// TODO: Add your control notification handler code here
DWORD dwIP;
((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);
SOCKADDR_IN addrTo;
addrTo.sin_addr.S_un.S_addr = htonl(dwIP);
addrTo.sin_family = AF_INET;
addrTo.sin_port = htons(8000);
CFileDialog fdlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,
"ALL FILE(*.*)|*.*||",NULL);
fdlg.DoModal();
FILE_INFO finfo;
SEND_STRUCT* pSendStruct = new SEND_STRUCT;
strcpy(finfo.FileName,fdlg.GetFileName());
finfo.buf = new char[20];
memset(finfo.buf,0,20);
strcpy(finfo.buf,"IWANTTOSENDFILETOYOU!");
finfo.FileLength = 0;
strcpy(pSendStruct->FileName,fdlg.GetFileName());
strcpy(pSendStruct->FilePath,fdlg.GetPathName());
pSendStruct->hwnd = m_hWnd;
pSendStruct->sock = m_socket;
pSendStruct->addrTo = addrTo;
if(SOCKET_ERROR == sendto(m_socket,(const char*)&finfo,
sizeof(finfo),0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR)))
{
MessageBox("发送失败!");
return;
}
HANDLE hThread = CreateThread(NULL,0,SendProc,(LPVOID)pSendStruct,0,NULL);
CloseHandle(hThread);
return;
}
这是发送端代码,因为要询问对方是否同意接收,所以要多了一个线程用于等待对方消息。如果没有多增加一个线程的话,等待将是痛苦的,而如果是在本机上进行测试的话,这样的等待更将是没有结果的,因为你根本没有表达的机会,就在那“死循环”。
DWORD WINAPI CXYFTDlg::SendProc(LPVOID lpParameter)
{
FILE_INFO finfo;
strcpy(finfo.FileName,((SEND_STRUCT*)lpParameter)->FileName);
SOCKET sock = ((SEND_STRUCT*)lpParameter)->sock;
HWND hWnd = ((SEND_STRUCT*)lpParameter)->hwnd;
char FilePath[100];
strcpy(FilePath,((SEND_STRUCT*)lpParameter)->FilePath);
SOCKADDR_IN addrTo = ((SEND_STRUCT*)lpParameter)->addrTo;
while(TRUE)
{
if(m_SendAck == 0)
{
AfxMessageBox("对方已拒绝接收");
m_SendAck = 2;
return 0 ;
}else
if(m_SendAck == 1)
{
AfxMessageBox("对方已经同意接收");
CFile file(FilePath,CFile::modeRead|CFile::typeBinary);
char* pBuf = new char[file.GetLength()];
file.ReadHuge(pBuf,file.GetLength());
finfo.buf = new char[file.GetLength()];
memcpy(finfo.buf,pBuf,file.GetLength());
finfo.FileLength = file.GetLength();
AfxMessageBox(finfo.FileName);
if(SOCKET_ERROR == sendto(sock,(const char*)&finfo,
sizeof(finfo),0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR)))
{
int fail = WSAGetLastError();
CString szfail;
szfail.Format("%d",fail);
AfxMessageBox(szfail);
return 0;
}
file.Close();
m_SendAck = 2;
return 1;
}
}
return 0;
}
这就是那个专门用于等待对方应答和发送文件正文的线程。做这个的过程中不得不提的就是通过int WSAGetLastError (void);函数得到的发送错误报警10038,在CSDN当中,查到的10038提示的错误是:
WSAENOTSOCK
(10038)
Socket operation on nonsocket.
An operation was attempted on something that is not a socket. Either the socket handle parameter did not reference a valid socket, or for select, a member of an
fd_set was not valid.
说的大概意思就是在一个无效的SOCKET上进行操作。我出现这个错误的原因并不是因为SOCKET关闭或者SOCKET的什么问题,而是因为当中的一个变量——用于表示文件路径名的FilePath定义得过小,这可能就导致了该说明所提到的第二个错误 for select, a member of an fd_set was not valid。
void CXYFTDlg::OnRecv(WPARAM wParam,LPARAM lParam)
{
switch(LOWORD(lParam))
{
case FD_READ:
SOCKADDR_IN addrFrom;
char recvBuf[300];
int len = sizeof(SOCKADDR);
if(SOCKET_ERROR == recvfrom(m_socket,recvBuf,300,0,
(SOCKADDR*)&addrFrom,&len))
{
int fail = WSAGetLastError();
CString sfail;
sfail.Format("%d",fail);
MessageBox(sfail);
return;
}
if(strcmp(((FILE_INFO*)recvBuf)->buf,"IWANTTOSENDFILETOYOU!") == 0)
{
FILE_INFO finfo;
finfo.buf = new char[20];
memset(finfo.buf,0,20);
memset(finfo.FileName,0,20);
CString strTemp = inet_ntoa(addrFrom.sin_addr);
strTemp += "想传送文件";
strTemp += ((FILE_INFO*)recvBuf)->FileName;
strTemp += "给你,是否接收?";
SetDlgItemText(IDC_EDIT_SHOW,strTemp);
RECV_STRUCT* pRecvStruct = new RECV_STRUCT;
pRecvStruct->hwnd = m_hWnd;
pRecvStruct->sock = m_socket;
pRecvStruct->addrFrom = addrFrom;
HANDLE hThread = CreateThread(NULL,0,RecvProc,pRecvStruct,
0,NULL);
CloseHandle(hThread);
}else
if(strcmp(((FILE_INFO*)recvBuf)->FileName,"ACKINFO") == 0)
{
if(strcmp(((FILE_INFO*)recvBuf)->buf,"RECEIVE")==0)
{
m_SendAck = 1;
return;
}else
{
m_SendAck = 0;
return;
}
}else
{
CString szAddress;
MessageBox(((FILE_INFO*)recvBuf)->FileName);
szAddress += "D://新建文件夹//XYFT//Debug//";
szAddress += ((FILE_INFO*)recvBuf)->FileName;
MessageBox(szAddress);
CFile file(szAddress,
CFile::modeCreate|CFile::modeWrite|CFile::typeBinary);
file.WriteHuge(((FILE_INFO*)recvBuf)->buf,
((FILE_INFO*)recvBuf)->FileLength);
}
}
}
这是接收端的代码之一,我使用的是异步套接字进行接收,感觉挺方便的。当然,接收端跟发送端一样,也需要在建立一个线程,用于回答发送端的询问。如果没有多一个线程的话,等待也将是痛苦的。
DWORD WINAPI CXYFTDlg::RecvProc(LPVOID lpParameter)
{
HWND hwnd = ((RECV_STRUCT*)lpParameter)->hwnd;
SOCKET sock = ((RECV_STRUCT*)lpParameter)->sock;
SOCKADDR_IN addrFrom = ((RECV_STRUCT*)lpParameter)->addrFrom;
FILE_INFO finfo;
finfo.buf = new char[20];
memset(finfo.buf,0,20);
while(TRUE)
{
if(m_ACKReceive == 1)
{
strcpy(finfo.FileName,"ACKINFO");
strcpy(finfo.buf,"RECEIVE");
finfo.FileLength = 0;
if(SOCKET_ERROR == sendto(sock,(const char*)&finfo,
sizeof(finfo),0,(SOCKADDR*)&addrFrom,sizeof(SOCKADDR)))
{
AfxMessageBox("发送失败!");
return 0;
}
CString strShow = "您已经同意接收来自";
strShow += inet_ntoa(addrFrom.sin_addr);
strShow += "的文件";
::PostMessage(hwnd,UM_SHOW,0,
(LPARAM)strShow.GetBuffer(strShow.GetLength()));
m_ACKReceive = 2;
return 1;
}
if(m_ACKReceive == 0)
{
strcpy(finfo.FileName,"ACKINFO");
strcpy(finfo.buf,"NORECEIVE");
finfo.FileLength = 0;
if(SOCKET_ERROR == sendto(sock,(const char*)&finfo,
sizeof(finfo),0,(SOCKADDR*)&addrFrom,sizeof(SOCKADDR)))
{
AfxMessageBox("发送失败!");
return 0;
}
CString strShow = "您已经拒绝接收来自";
strShow += inet_ntoa(addrFrom.sin_addr);
strShow += "的文件";
::PostMessage(hwnd,UM_SHOW,0,
(LPARAM)strShow.GetBuffer(strShow.GetLength()));
m_ACKReceive = 2;
return 1;
}
}
}
这就是接收端新增加的线程。
当中还定义了几个自定义消息和静态成员变量。之所以定义静态成员变量是因为CreateThread函数的响应函数是一个静态成员函数,要在外部对工作在它内部的一个变量进行修改,方法之一就是定义一个静态成员变量。但是需要记住的一点就是静态成员变量的初始化必须在类的外部。
int CXYFTDlg::m_SendAck = 2;
int CXYFTDlg::m_ACKReceive = 2;
大概的程序就是如此,不足还有很多,希望路过者多提宝贵意见,共同学习,共同进步。