起因是学校课要求写一个双机网络通信,并且可以发送文件的程序。
使用的是传统的socket (recv(),send() …..)等。而且是阻塞模式,使用图形界面MFC编写 (VC 6.0)。但为了能让程序不出现假死现象( recv,accept 这样的函数都会出现这样的事情),所以采用了多线程技术,其实也就是用了AfxBeginThread ,TerminateThread等等。这样对于阻塞函数都让他们在新建立的线程里运行就好了。
另外解决的一个大问题就是,创建的新线程无法对窗口进行操作,比如要自在编辑框显示一句话等等。如果直接取得窗口类的句柄操作,会出现wincore的错误,也就是跨线程错误。主要原因也就是——-MFC的线程被自己外部创建的线程调用就会有这个错误。解决的方法就是在线程里用SendMessage给窗口发送一个自定义的消息,比如我这里用的就是。
要实现这样的方法
在BEGIN_MESSAGE_MAP下面要添加
ON_MESSAGE(WM_UpdateDATA, OnMyMessage) //绑定我自己的消息,这样外部的线程就可以通过SendMessage来调用窗体的函数OnMyMessage了,用来显示信息WM_UpdateDATA在stdafx.h中我自己定义 #define WM_UpdateDATA WM_USER+100 //定义一个自己的消息这样只要外部给窗口SendMessage WM_UpdateDATA 就会让窗口的onmymessage函数执行了,具体要显示的内容放在一个全局变量就好了。
这里写出一些关键的代码,具体程序可以到点击这里下载
我注释的相当详细了。
编译的话,debug版可以正常工作,但release版就无法正常工作了
serverdlg.cpp
//定义全局变量,方便各个工作线程和窗口线程的通信
SOCKET sockuse,sock;
int flag; //主要是用来标志是否连接,用来控制一些循环和功能
HWND mydlg; //记录窗体的句柄
HWND stopbutton; //记录关闭连接按钮的句柄
char buff[200]; //主要的缓冲区,显示数据使用,也供线程间通信使用
CWinThread* mainthread; //记录线程的句柄
UINT FileTrans(LPVOID pParam) //发送文件的线程函数
{
OPENFILENAMEA ofn;
char szFile[260];
char path[260];
char filebuff[100];
int num;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "所有文件*.*";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = 0;
//以上的定义是为了建立一个文件打开对话框。步骤
//上来说都是固定的,这里只是借用一下
if (GetOpenFileNameA(&ofn)==FALSE) //判断文件路径取得是否正确
{ //若失败则恢复建立MainControl线程,进行数据接收处理。
mainthread=AfxBeginThread(MainControl,NULL);
flag=1; //设定flag保证MainControl可以正常进行循环
return 0;
}
memset(filebuff,0,100); //清空filebuff
strcpy(path,ofn.lpstrFile); //将路径拷贝到path中
strcpy(filebuff,"@@sendstart");
send(sockuse,filebuff,strlen(filebuff),0); //发送请求信息
recv(sockuse,filebuff,100,0); //等待接收反馈信息
if(strcmp(filebuff,"@@sendagree")) //若接收到的信息不是@@sendagree
{
strcpy(buff,"对方接受错误");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
mainthread=AfxBeginThread(MainControl,NULL); //恢复建立MainControl线程
flag=1; //设定flag保证MainControl循环正常
return 0;
}
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
while(!feof(fp))
{
num=fread(filebuff,1,100,fp); //文件结束前每次读取100字节
send(sockuse,filebuff,num,0); //发送,最后一次不足100字节
//作为标志,可以让接受方知道文件结束
}
strcpy(buff,"发送完毕");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
memset(buff,0,100);
fclose(fp); //关闭文件
flag=1;
return 0;
}
UINT FileRecv() //接收文件函数
{
char sendbuff[100]; //发送缓冲区
char recvbuff[100]; //接受缓冲区
int num; //记录每次接受的字节数
CString szGetName; //记录保存的文件路径
CFileDialog * lpszOpenFile; //定义一个CfileDialog对象
lpszOpenFile=new CFileDialog(false,"","",OFN_FILEMUSTEXIST|OFN_HIDEREADONLY,"文件类型(*.*)|*.*||");//生成一个对话框
if(lpszOpenFile->DoModal()==IDOK)//假如点击对话框确定按钮
{
szGetName = lpszOpenFile->GetPathName(); //得到打开文件的路径
//SetWindowText(szGetName); //在窗口标题上显示路径
}
delete lpszOpenFile; //释放分配的对话框
memset(sendbuff,0,100);
strcpy(sendbuff,"@@sendagree");
send(sockuse,sendbuff,strlen(sendbuff),0); //发送@@sendagree告知对方开始发送吧
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显//示信息
num=recv(sockuse,recvbuff,100,0); //接收数据
FILE *fp=fopen(szGetName,"wb"); //打开文件,路径为szGetName
fwrite(recvbuff,num,1,fp); //写入之前的数据
{
num=recv(sockuse,recvbuff,100,0);
fwrite(recvbuff,num,1,fp);
}
fclose(fp); //结束后关闭文件。
strcpy(buff,"接收完毕");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,////显示信息
return 0;
}
int Addrlen=sizeof(sockaddr_in); //accept要用到的数值
sockaddr_in ClientAddr;
sockuse=accept(sock,(struct sockaddr FAR *)&ClientAddr,&Addrlen);
//上一句的accept函数调用后会进行阻塞,造成未返回时程序假死,使用了单独的线程
//就是为了防止这样的现象发生
flag=1; //返回成功后,设定flag保证MainControl循环正常
strcpy(buff,"已经连接!");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消//息,显示信息
mainthread=AfxBeginThread(MainControl,NULL); //建立MainControl线程
return 0;
}
{
char recvbuff[100]; //接受缓冲区
memset(buff,0,100);
while(flag)
{
memset(recvbuff,0,100); //每次接收前清空缓冲区
recv(sockuse,recvbuff,100,0); //进行阻塞接收数据,如果不是用单独的线程
//会造成程序假死,这也就是为什么我的程序使用单独的线程来处理
if(!strcmp(recvbuff,"@@end"))
{
SendMessage(stopbutton,BM_CLICK,NULL,NULL); //消息判断为@@end则调用
//窗口的OnButtonEnd函数来结束连接的清理工作。
return 0;
}
else if(!strcmp(recvbuff,"@@sendstart"))
{
//接受的消息判断为"@@sendstart"则调用FileRecv()
FileRecv();
}
else
{
strcpy(buff,"client:");
strcat(buff,recvbuff);
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给//窗口消息,显示信息
}
}
return 0;
}
{
m_ctrlstop.EnableWindow(true);
m_ctrlfile.EnableWindow(true);
m_ctrlstart.EnableWindow(false);
m_ctrlsend.EnableWindow(true); //以上使各个按钮进行使能
sockaddr_in ServerAddr; //开始建立socket
WSADATA WSAData;
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0)
{
showmess("SOCKET 初始化错误rn");
return;
}
sock=socket(AF_INET,SOCK_STREAM,0); //采用流式套接字,ipv4
if(sock==SOCKET_ERROR)
{
showmess("SOCKET 创建错误!rn");
WSACleanup();
return;
}
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY); //任意ip作为本机ip
ServerAddr.sin_port=htons(2006); //使用2006端口
if(bind(sock,(struct sockaddr FAR*)&ServerAddr,sizeof(ServerAddr))==SOCKET_ERROR)
{
//绑定socket和本地地址
showmess("绑定错误!n");
return;
}
//显示正在侦听
showmess("listening.....");
listen(sock,1);
flag=0; //未连接之前,flag=0
mydlg=this->GetSafeHwnd(); //取得窗口句柄供线程函数使用
stopbutton=::GetDlgItem(mydlg,IDC_BUTTON_STOP); //取得关闭连接按//钮的句柄供线程函数使用
AfxBeginThread(WaitForAccept,NULL); //启动WaitForAccept线程等待连接
return;
}
void CServerDlg::showmess(char *mess) //用来在信息窗口显示信息
{
m_strmess+=mess;
m_strmess+="rn"; //在每条信息后添加回车
UpdateData(false); //更新信息显示
}
{
flag=0; //将连接标志清零
strcpy(buff,"@@end");
send(sockuse,buff,strlen(buff),0); //给对方发送信息告知结束
TerminateThread(mainthread->m_hThread,0x01); //结束MainControl线程
closesocket(sock); //关闭套接字
closesocket(sockuse);
WSACleanup(); //清理网络
m_ctrlstop.EnableWindow(false); //使能一些按钮
m_ctrlfile.EnableWindow(false);
m_ctrlstart.EnableWindow(true);
}
void CServerDlg::OnMyMessage()
{
showmess(buff); //仅仅是为了线程函数调用内部的信息显示函数
}
void CServerDlg::OnButtonFile()
{
flag=0; //设定 flag
TerminateThread(mainthread->m_hThread,0x01); //中止MainControl
AfxBeginThread(FileTrans,NULL); //启用FileTrans线程
}
void CServerDlg::OnButtonSend()
{
char talkbuff[100];
memset(talkbuff,0,100);
UpdateData(true);
strcpy(talkbuff,m_strtalk); //取得对话框数据
send(sockuse,talkbuff,100,0); //发送给对方信息
}
clientDlg.cpp
#include "winsock.h"
#include "stdio.h"
#include"string.h"
#pragma comment(lib,"wsock32.lib") //这四句要加在本文件的开头部分。保证
//网络功能正常
在BEGIN_MESSAGE_MAP下面要添加
ON_MESSAGE(WM_UpdateDATA, OnMyMessage)
/*绑定我自己的消息,这样外部的线程就可以通过SendMessage来调用窗体的函数OnMyMessage了,用来显示信息
WM_UpdateDATA在stdafx.h中我自己定义为
#define WM_UpdateDATA WM_USER+100 //定义一个自己的消息*/
//定义全局变量,方便各个工作线程和窗口线程的通信
SOCKET sock;
char buff[100]; //主要的缓冲区,显示数据使用,也供线程间通信使用
HWND mydlg; //记录窗体的句柄
sockaddr_in ServerAddr;
int flag=0; //主要是用来标志是否连接,用来控制一些循环和功能
CWinThread* mainthread;
HWND stopbutton; //记录关闭连接按钮的句柄
UINT WaitForConnect(LPVOID pParam) //等待connect 的线程
{
if(connect(sock,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))==SOCKET_ERROR)
{
strcpy(buff,"connect failn");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消//息,显示信息
closesocket(sock); //失败则关闭sock
return 0;
}
strcpy(buff,"已经连接!");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL);//发送给窗口消////息,显示信息
flag=1;
mainthread=AfxBeginThread(MainControl,NULL); //建立MainControl线程
return 0;
}
{
char sendbuff[100]; //发送缓冲区
char recvbuff[100]; //接受缓冲区
int num; //记录每次接受的字节数
CString szGetName;
CFileDialog * lpszOpenFile; //定义一个CfileDialog对象
lpszOpenFile = new CFileDialog(false,"","",OFN_FILEMUSTEXIST|OFN_HIDEREADONLY,"文件类型(*.*)|*.*||");//生成一个对话框
if(lpszOpenFile->DoModal()==IDOK)//假如点击对话框确定按钮
{
szGetName = lpszOpenFile->GetPathName(); //得到打开文件的路径
//SetWindowText(szGetName); //在窗口标题上显示路径
}
delete lpszOpenFile; //释放分配的对话框
memset(sendbuff,0,100);
strcpy(sendbuff,"@@sendagree");
send(sock,sendbuff,strlen(sendbuff),0); //发送@@sendagree告知对方开始发送吧
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
num=recv(sock,recvbuff,100,0); //接收数据
FILE *fp=fopen(szGetName,"wb"); //打开文件,路径为szGetName
fwrite(recvbuff,num,1,fp); //写入之前的数据
while(num==100) //根据接收是否为100字节判断文件是否结束
{
num=recv(sock,recvbuff,100,0);
fwrite(recvbuff,num,1,fp);
}
strcpy(buff,"接收完毕");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
return 0;
}
char recvbuff[100];//接受缓冲区
{
memset(buff,0,100);
while(flag)
{
memset(recvbuff,0,100); //每次接收前清空缓冲区
recv(sock,recvbuff,100,0); //进行阻塞接收数据,如果不是用单独的线程
//会造成程序假死,这也就是为什么我的程序使用单独的线程来处理
if(!strcmp(recvbuff,"@@end"))
{//消息判断为@@end则调用
//窗口的OnButtonEnd函数来结束连接的清理工作。
SendMessage(stopbutton,BM_CLICK,NULL,NULL);
return 0;
}
else if(!strcmp(recvbuff,"@@sendstart"))
{
//接受的消息判断为"@@sendstart"则调用FileRecv()
FileRecv();
memset(recvbuff,0,100);
}
else
{
strcpy(buff,"server:");
strcat(buff,recvbuff);
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL);
//发送给窗口消息,显示信息
}
}
return 0;
}
{
OPENFILENAMEA ofn;
char szFile[260];
char path[260];
char filebuff[100];
int num;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = NULL;
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = '';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = "所有文件*.*";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = 0;
//以上的定义是为了建立一个文件打开对话框。步骤
//上来说都是固定的,这里只是借用一下
if (GetOpenFileNameA(&ofn)==FALSE) //判断文件路径取得是否正确
{ //若失败则恢复建立MainControl线程,进行数据接收处理。
mainthread=AfxBeginThread(MainControl,NULL);
flag=1; //设定flag保证MainControl可以正常进行循环
return 0;
}
memset(filebuff,0,100); //清空filebuff
strcpy(path,ofn.lpstrFile); //将路径拷贝到path中
strcpy(filebuff,"@@sendstart");
send(sock,filebuff,strlen(filebuff),0); //发送请求信息
recv(sock,filebuff,100,0); //等待接收反馈信息
if(strcmp(filebuff,"@@sendagree")) //若接收到的信息不是@@sendagree
{
strcpy(buff,"对方接受错误");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
mainthread=AfxBeginThread(MainControl,NULL); //恢复建立MainControl线程
flag=1; //设定flag保证MainControl循环正常
return 0;
}
strcpy(buff,"对方已经同意,开始发送文件");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL);
////发送给窗口消息,显示信息
FILE *fp=fopen(path,"rb"); //打开文件
while(!feof(fp))
{
num=fread(filebuff,1,100,fp); //文件结束前每次读取100字节
send(sock,filebuff,num,0); //发送,最后一次不足100字节
//作为标志,可以让接受方知道文件结束
}
strcpy(buff,"发送完毕");
SendMessage(mydlg,WM_UpdateDATA,NULL,NULL); //发送给窗口消息,显示信息
memset(buff,0,100);
fclose(fp); //关闭文件
mainthread=AfxBeginThread(MainControl,NULL); //恢复建立MainControl线程
flag=1;
return 0;
}
{
m_ctrlstop.EnableWindow(true);
m_ctrlfile.EnableWindow(true);
m_ctrlconnect.EnableWindow(false);//以上使各个按钮进行使能
WSADATA WSAData;//开始建立socket
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0)
{
showmess("socket初始化错误");
return;
}
sock=socket(AF_INET,SOCK_STREAM,0);//采用流式套接字,ipv4
if(sock==SOCKET_ERROR)
{
showmess("SOCK Create FAIL!");
WSACleanup();
return;
}
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(2006); //使用2006端口
UpdateData(true); //读取ip 值
ServerAddr.sin_addr.s_addr = inet_addr(m_strip);
//使用编辑框中的ip地址,默认值127.0.0.1
//取得窗口句柄供线程函数使用
flag=0;
stopbutton=::GetDlgItem(mydlg,IDC_BUTTON_STOP);
//取得关闭连接按钮的句柄供线程函数使用
AfxBeginThread(WaitForConnect,NULL);
//启动WaitForConnect线程进行连接
return;
{
m_strmess+=mess;
m_strmess+="rn"; //在每条信息后添加回车
UpdateData(false); //更新信息显示
}
void CClientDlg::OnMyMessage()
{
showmess(buff);//仅仅是为了线程函数调用内部的信息显示函数
}
{
TerminateThread(mainthread,0x01);
//结束MainControl线程
strcpy(buff,"@@end");
send(sock,buff,strlen(buff),0);
//给对方发送信息告知结束
closesocket(sock);
//关闭套接字
WSACleanup();
m_ctrlstop.EnableWindow(false);//使一些按钮
m_ctrlfile.EnableWindow(false);
m_ctrlconnect.EnableWindow(true);
}
{
char talkbuff[100];
memset(talkbuff,0,100);
UpdateData(true);
strcpy(talkbuff,m_strsendmess); //取得对话框数据
send(sock,talkbuff,100,0); //发送给对方信息
}
void CClientDlg::OnButtonFile()
{
flag=0; //设定 flag
TerminateThread(mainthread->m_hThread,0x01); //中止MainControl
AfxBeginThread(FileTrans,NULL); //启用FileTrans线程
}
void CClientDlg::OnButtonSend()
flag=0; //将连接标志清零
void CClientDlg::OnButtonStop()
}
void CClientDlg::showmess(char *mess) //用来在信息窗口显示信息
mydlg=this->GetSafeHwnd();
void CClientDlg::OnButtonConnect()
UINT FileTrans(LPVOID pParam)
UINT MainControl(LPVOID pParam)
fclose(fp); //结束后关闭文件。
strcpy(buff,"开始接收文件");
UINT FileRecv() //接收文件函数
UINT MainControl(LPVOID pParam); //提前声明该线程函数
再次回到clientDlg.cpp中,接下来就是程序的主体部分了。
接下来是客户端的程序了,和server一样也要自定义消息
void CServerDlg::OnButtonStop()
void CServerDlg::OnButtonStart()
UINT MainControl(LPVOID pParam)
{
UINT WaitForAccept(LPVOID pParam) //等待accept 的线程
while(num==100) //根据接收是否为100字节判断文件是否结束
strcpy(buff,"开始接收文件");
mainthread=AfxBeginThread(MainControl,NULL); //恢复建立MainControl线程
FILE *fp=fopen(path,"rb"); //打开文件
strcpy(buff,"对方已经同意,开始发送文件");