这学期什么都没学,一直在钻研VC,平时上课,老师在上面讲着,我就躲到后排,耳朵里赛个MP3,埋头看我的VC..马上就要开始考试了,居然也不紧张,看来自己已经成老油条了,也不怕什么挂科了(又不是没挂过,所以不在乎了)..
好了,费话不多说,我就说说我是怎么做这个程序的吧..其实这也谈不上是什么远程监控,好的远程监控软件,自己到网上看看报价就知道了,所以,我这个只能叫程序,不能称为软件...
这个程序共分为两部分:服务端(Server)和客户端(Client),程序能完成下面三大功能:
1.监视服务端主机的桌面,并传输给客户端显示.
2.录制服务端的声音,发送给客户端播放..
3.文件传输,能在客户端显示服务端的主机目录,类似windows资源管理器,并能进行文件传输.
当初在设计时,是想这个程序还能完成控制,就如同QQ的远程协助,可惜在调试程序时很麻烦,因为调试程序时,服务端和客户端都在自己机子上,不好看效果..所以为了省事,就跳过了这部分的功能,但这部分应该也不难,主要是两个API函数,模拟鼠标和按键功能,一个是VOID mouse_event(),一个是VOID keybd_event(),具体的使用你可以查看MSDN..有兴趣的朋友可以添加上这个功能,还可以加上别的功能,比如远程关机,注销等..下面我就按照上面的顺序,讲下如何实现这三大功能吧,程序的源码我已经传到CSDN的资源区,需要的朋友可以下来看看..http://download.csdn.net/source/513832..
1.监视服务端主机的桌面,并传输给客户端显示.
先从服务端说起吧,我在服务端创建了个类CServer,主要完成这些事情1、监听客户端的命令 2、向客户传送桌面画面 3、向客户端传送服务器语音数据 4、向客户端传送文件.
如果你没有一点网络编程基础,推荐你看本书《Visual C++ 网络通信编程实用案例精选》 人民邮电出版..建议先依照书上的例子写个点对点的聊天程序,熟悉下WinSock API..我个人认为,网络上两台主机间的通信就像是我们生活中的打电话,比如,我现在想给我的一个同学打电话,我首先要知道他们宿舍的电话号码,这个号码就对应网络上的IP地址,现在电话通了,可他们宿舍有6个人,该和谁通信呢??我当然会说**同学在吗,这个就相当于端口号..有了IP地址和端口号,网络上的两台电脑就可以实现通信了..这个比喻可能不是很恰当,但我目前对网络编程的理解就是这样的..
先来看看CServer类的数据成员和成员函数吧:
- class CServer
- {
- //私有成员函数
- private:
- void InitCommon(); //初始化,创建好socket,准备好与客户端连接,用于一般通信
- void InitSocket(SOCKET &socketForListen, int nPort); //初始化socket函数,参数传递的是socket和端口
- void InitVideo(); //初始化,创建好socket,准备好传送桌面画面
- void InitAudio(); //初始化,创建好socket,准备好传送主机声音
- void InitSendFile(); //初始化,创建好socket,准备好传送文件
- //------------------------------
- //说明,ScrBmpToFile()这个函数,我当时的想法是把屏幕的画面先保存在本机的磁盘上,
- //然后再以文件的形式发送到客户端,而客户端再读位图文件,显示画面.发现这样做效果不是很好,
- //所以就放弃了,这个函数就是这样留下的.
- //------------------------------
- void ScrBmpToFile(); //将屏幕图像保存为位图文件
- void CatchScrBmp(); //捕获屏幕位图结构和数据
- //公有成员函数
- public:
- CServer(CServerDlg* pDlg);
- ~CServer();
- void SendMsg(); //向客户端发送信息
- void StartSendVideo(); //开始传送主机桌面数据
- void StartSendAudio(); //开始传送主机语音信息
- void StartSendFile(CString strFilePath); //传送主机文件,参数为文件位置
- void SendScrBmp(); //传送桌面画面
- void Record(); //开始录音
- void Pause(); //暂停录音
- void SendAudioData(); //发送音频数据
- void SendLocalDrives(); //传送本机的驱动器信息
- void SendFolderInfo(CString strPath); //发送strPath目录下的所有文件和文件夹
- void SendFileInfo(CString strPath); //发送文件的基本信息,路径为strPath
- //私有数据成员
- private:
- int m_nCommonPort; //用于一般通信的端口,一般通信包括文件传输,监听客户端命令
- int m_nAudioPort; //用于语音通信的端口
- int m_nVideoPort; //用于传播服务器屏幕画面的端口
- int m_nSendFilePort; //用于传送文件的端口
- //线程句柄
- HANDLE m_hCommon; //线程句柄,用于普通通信
- HANDLE m_hVideo; //线程句柄,用于传送桌面画面
- HANDLE m_hAudio; //线程句柄, 用于传送主机声音
- BITMAP m_bmpBit; //桌面位图结构
- char *m_pBmpData; //桌面位图数据
- CSound *m_pSound; //服务器的声音
- //公有数据成员
- public:
- CServerDlg* m_pDlg; //程序窗口指针
- SOCKET m_socketListenForCommon; //监听套接字,为普通通信准备
- SOCKET m_socketListenForAudio; //监听套接字,为传送语音准备
- SOCKET m_socketListenForVideo; //监听套接字,为传送屏幕画面准备
- SOCKET m_socketListenForSendFile; //监听套接字,为传送文件准备
- SOCKET m_socketRealConversationForCommon; //真正会话套接字,为普通通信准备
- SOCKET m_socketRealConversationForAudio; //真正会话套接字,为传送语音准备
- SOCKET m_socketRealConversationForVideo; //真正会话套接字,为传送屏幕画面准备
- SOCKET m_socketRealConversationForSendFile; //真正会话套接字,为传送文件准备
- sockaddr_in m_sockaddrServer; //服务器地址
- CString m_strMsg; //需要发送的消息
- //bool m_bEndThreadCommon; //结束普通通信线程
- bool m_bEndThreadVideo; //结束传送桌面画面线程
- bool m_bEndThreadAudio; //结束传送语音数据
- bool m_bEndThreadSendFile; //结束传送文件
- CString m_strFilePath; //要传输的文件路径
- };
class CServer{//私有成员函数private: void InitCommon(); //初始化,创建好socket,准备好与客户端连接,用于一般通信void InitSocket(SOCKET &socketForListen, int nPort); //初始化socket函数,参数传递的是socket和端口void InitVideo(); //初始化,创建好socket,准备好传送桌面画面void InitAudio(); //初始化,创建好socket,准备好传送主机声音void InitSendFile(); //初始化,创建好socket,准备好传送文件//------------------------------//说明,ScrBmpToFile()这个函数,我当时的想法是把屏幕的画面先保存在本机的磁盘上,//然后再以文件的形式发送到客户端,而客户端再读位图文件,显示画面.发现这样做效果不是很好,//所以就放弃了,这个函数就是这样留下的.//------------------------------void ScrBmpToFile(); //将屏幕图像保存为位图文件void CatchScrBmp(); //捕获屏幕位图结构和数据//公有成员函数public:CServer(CServerDlg* pDlg);~CServer();void SendMsg(); //向客户端发送信息void StartSendVideo(); //开始传送主机桌面数据void StartSendAudio(); //开始传送主机语音信息void StartSendFile(CString strFilePath); //传送主机文件,参数为文件位置void SendScrBmp(); //传送桌面画面void Record(); //开始录音void Pause(); //暂停录音void SendAudioData(); //发送音频数据void SendLocalDrives(); //传送本机的驱动器信息void SendFolderInfo(CString strPath); //发送strPath目录下的所有文件和文件夹void SendFileInfo(CString strPath); //发送文件的基本信息,路径为strPath//私有数据成员private:int m_nCommonPort; //用于一般通信的端口,一般通信包括文件传输,监听客户端命令int m_nAudioPort; //用于语音通信的端口int m_nVideoPort; //用于传播服务器屏幕画面的端口int m_nSendFilePort; //用于传送文件的端口//线程句柄HANDLE m_hCommon; //线程句柄,用于普通通信HANDLE m_hVideo; //线程句柄,用于传送桌面画面HANDLE m_hAudio; //线程句柄, 用于传送主机声音BITMAP m_bmpBit; //桌面位图结构char *m_pBmpData; //桌面位图数据CSound *m_pSound; //服务器的声音//公有数据成员public:CServerDlg* m_pDlg; //程序窗口指针 SOCKET m_socketListenForCommon; //监听套接字,为普通通信准备SOCKET m_socketListenForAudio; //监听套接字,为传送语音准备SOCKET m_socketListenForVideo; //监听套接字,为传送屏幕画面准备SOCKET m_socketListenForSendFile; //监听套接字,为传送文件准备SOCKET m_socketRealConversationForCommon; //真正会话套接字,为普通通信准备SOCKET m_socketRealConversationForAudio; //真正会话套接字,为传送语音准备SOCKET m_socketRealConversationForVideo; //真正会话套接字,为传送屏幕画面准备SOCKET m_socketRealConversationForSendFile; //真正会话套接字,为传送文件准备sockaddr_in m_sockaddrServer; //服务器地址CString m_strMsg; //需要发送的消息//bool m_bEndThreadCommon; //结束普通通信线程bool m_bEndThreadVideo; //结束传送桌面画面线程bool m_bEndThreadAudio; //结束传送语音数据bool m_bEndThreadSendFile; //结束传送文件CString m_strFilePath; //要传输的文件路径};
服务端在初始化时,先启动个线程,一直监听客户端的命令,我这里自己定义了些命令
EXIT 客户端关闭了应用程序;
VIDEO 客户端请求查看主机的桌面画面 ; VIDEO_CLOSE 客户端要求停止传送桌面画面;
AUDIO 客户端请求监听主机的声音; AUDIO_CLOSE 客户端要求停止传送主机的声音;
DRIVES 客户端要求查看主机的磁盘驱动器;
FOLDER|strPath| 客户端要求查看文件夹下的目录,命令后面紧跟的是文件夹路径
FILE|strPath| 客户端要求查看一个文件的详细信息,命令后面紧跟的是文件路径
SEND_FILE|strPath| 客户端要求把主机的一个文件传过来,命令后面紧跟的是文件路径
SEND_FILE_CANCEL 客户端中断了传送
服务端在监听到各自的命令,完成相应的动作..现在服务端收到命令VIDEO 客户端请求查看主机的桌面画面 ; 则创建个线程,传送主机画面,下面是传送画面的线程函数
- //线程函数,用于传送主机桌面画面
- UINT ThreadFuncVideo(LPVOID pParam)
- {
- CServer *pServer = (CServer *)pParam;
- int nLength = sizeof(pServer->m_sockaddrServer);
- pServer->m_socketRealConversationForVideo = accept(pServer->m_socketListenForVideo, (sockaddr *)&pServer->m_sockaddrServer, &nLength);
- if(pServer->m_socketRealConversationForVideo == INVALID_SOCKET)
- {
- pServer->m_pDlg->MessageBox("调用accept()出错");
- return 1;
- }
- //传送画面的socket已建立
- //开始传送桌面画面
- while(true)
- {
- if(pServer->m_bEndThreadVideo == true) //结束传送桌面画面线程
- AfxEndThread(2);
- pServer->SendScrBmp(); //发送桌面画面
- }
- return 0;
- }
//线程函数,用于传送主机桌面画面UINT ThreadFuncVideo(LPVOID pParam){CServer *pServer = (CServer *)pParam;int nLength = sizeof(pServer->m_sockaddrServer);pServer->m_socketRealConversationForVideo = accept(pServer->m_socketListenForVideo, (sockaddr *)&pServer->m_sockaddrServer, &nLength);if(pServer->m_socketRealConversationForVideo == INVALID_SOCKET){pServer->m_pDlg->MessageBox("调用accept()出错");return 1;}//传送画面的socket已建立//开始传送桌面画面while(true){if(pServer->m_bEndThreadVideo == true) //结束传送桌面画面线程AfxEndThread(2);pServer->SendScrBmp(); //发送桌面画面}return 0;}
这里用到了CServer的成员函数SendScrBmp(),一个专门用于传送桌面画面的函数.函数原型如下:
- //发送屏幕画面函数
- void CServer::SendScrBmp()
- {
- CatchScrBmp(); //先捕获屏幕位图结构和数据,即得到m_bmpBit,和m_pBmpData
- //发送位图结构信息
- int nSend = send(m_socketRealConversationForVideo, (char *)&m_bmpBit, sizeof(m_bmpBit), 0);
- //发送位图数据信息
- int nBytesSent = 0;
- int nBytesThisTime = 0;
- char *pch = m_pBmpData;
- int size = m_bmpBit.bmWidthBytes * m_bmpBit.bmHeight;
- do{ //发送大量的数据时 采用循环 直到发送完要发送的数据为止
- if(m_bEndThreadVideo == true) //结束传送桌面画面线程
- break;
- nBytesThisTime = send(m_socketRealConversationForVideo, pch, size - nBytesSent, 0);
- nBytesSent += nBytesThisTime;
- pch += nBytesThisTime;
- }while(nBytesSent < size);
- delete []m_pBmpData; //发送完毕时删除位图的数据信息,清理申请的内存
- m_pBmpData = NULL;
- }
//发送屏幕画面函数void CServer::SendScrBmp(){CatchScrBmp(); //先捕获屏幕位图结构和数据,即得到m_bmpBit,和m_pBmpData//发送位图结构信息int nSend = send(m_socketRealConversationForVideo, (char *)&m_bmpBit, sizeof(m_bmpBit), 0);//发送位图数据信息int nBytesSent = 0;int nBytesThisTime = 0;char *pch = m_pBmpData; int size = m_bmpBit.bmWidthBytes * m_bmpBit.bmHeight;do{ //发送大量的数据时 采用循环 直到发送完要发送的数据为止 if(m_bEndThreadVideo == true) //结束传送桌面画面线程break; nBytesThisTime = send(m_socketRealConversationForVideo, pch, size - nBytesSent, 0);nBytesSent += nBytesThisTime;pch += nBytesThisTime;}while(nBytesSent < size); delete []m_pBmpData; //发送完毕时删除位图的数据信息,清理申请的内存 m_pBmpData = NULL;}
而要发送桌面的画面,你首先要捕获桌面的画面,所以,SendScrBmp()这个函数又调用了函数CatchScrBmp(); 截屏函数,把桌面画面以位图信息的形式保存到内存中,然后再发送出去..下面是CatchScrBmp()函数的原型.
- //捕获屏幕位图信息和数据
- void CServer::CatchScrBmp()
- {
- HDC hScrDC, hMemDC; //屏幕,内存设备描述表句柄
- HBITMAP hBmp; //位图句柄
- int nWidth, nHeight; //屏幕的宽和高
- hScrDC = ::CreateDC("DISPLAY", NULL, NULL, NULL); //创建屏幕设备描述表句柄
- hMemDC = ::CreateCompatibleDC(hScrDC); //创建与屏幕设备相兼容的内存设备描述表
- //分别得到屏幕的宽和高
- nWidth = ::GetDeviceCaps(
- hScrDC,
- HORZRES // HORZRES Width, in pixels, of the screen.
- // VERTRES Height, in raster lines, of the screen.
- );
- nHeight = ::GetDeviceCaps(hScrDC, VERTRES);
- //创建与屏幕设备相兼容的位图
- hBmp = ::CreateCompatibleBitmap(
- hScrDC, // handle to DC
- nWidth, // width of bitmap, in pixels
- nHeight // height of bitmap, in pixels
- );
- ::SelectObject(hMemDC, hBmp); //将位图选入内存设备描述表
- //复制屏幕设备描述表到内存设备描述表
- ::BitBlt(
- hMemDC, // handle to destination DC
- 0, // x-coord of destination upper-left corner
- 0, // y-coord of destination upper-left corner
- nWidth, // width of destination rectangle
- nHeight, // height of destination rectangle
- hScrDC, // handle to source DC
- 0, // x-coordinate of source upper-left corner
- 0, // y-coordinate of source upper-left corner
- SRCCOPY // raster operation code
- );
- CBitmap Bmp;
- Bmp.Attach(hBmp);//根据位图句柄,得到位图
- BITMAP Bitmap; //位图结构
- Bmp.GetBitmap(&Bitmap); //得到位图信息头和位图颜色信息
- BITMAPINFO BitmapInfo; //位图信息结构,包含位图信息头和位图颜色信息
- //位图信息头结构
- BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFO); //本结构所占用的字节数
- BitmapInfo.bmiHeader.biWidth = Bitmap.bmWidth; //位图宽度,以像素为单位
- BitmapInfo.bmiHeader.biHeight = Bitmap.bmHeight; //位图高度,以像素为单位
- BitmapInfo.bmiHeader.biPlanes = 1; //目标设备的级别,必须为1
- BitmapInfo.bmiHeader.biBitCount = Bitmap.bmBitsPixel; //每个像素所需的位数,必须为1,4,8,24,32
- BitmapInfo.bmiHeader.biCompression = 0; //位图压缩类型,必须为0,1,2
- BitmapInfo.bmiHeader.biSizeImage = Bitmap.bmWidthBytes * Bitmap.bmHeight; //位图大小,以字节为单位
- BitmapInfo.bmiHeader.biXPelsPerMeter = 0; //位图水平分辨率,每米像素数
- BitmapInfo.bmiHeader.biYPelsPerMeter = 0; //位图垂直分辩率,每米象素数
- BitmapInfo.bmiHeader.biClrUsed = 0; //位图实际使用的颜色表中的颜色数
- BitmapInfo.bmiHeader.biClrImportant = 0; //位图显示过程中重要的颜色数
- //位图数据信息
- m_pBmpData = new char[Bitmap.bmWidthBytes * Bitmap.bmHeight]; //用于保存位图数据的缓冲区
- int n = ::GetDIBits(
- hMemDC, // handle to DC
- hBmp, // handle to bitmap
- 0, // first scan line to set
- Bitmap.bmHeight, // number of scan lines to copy
- m_pBmpData, // array for bitmap bits
- &BitmapInfo, // bitmap data buffer
- DIB_RGB_COLORS // RGB or palette index
- );
- m_bmpBit = Bitmap;
- //创建了句柄,一定要释放,否则会浪费内存
- ::DeleteDC(hScrDC);
- ::DeleteDC(hMemDC);
- }
//捕获屏幕位图信息和数据void CServer::CatchScrBmp(){HDC hScrDC, hMemDC; //屏幕,内存设备描述表句柄HBITMAP hBmp; //位图句柄int nWidth, nHeight; //屏幕的宽和高hScrDC = ::CreateDC("DISPLAY", NULL, NULL, NULL); //创建屏幕设备描述表句柄hMemDC = ::CreateCompatibleDC(hScrDC); //创建与屏幕设备相兼容的内存设备描述表//分别得到屏幕的宽和高nWidth = ::GetDeviceCaps(hScrDC, HORZRES // HORZRES Width, in pixels, of the screen. // VERTRES Height, in raster lines, of the screen. );nHeight = ::GetDeviceCaps(hScrDC, VERTRES);//创建与屏幕设备相兼容的位图hBmp = ::CreateCompatibleBitmap(hScrDC, // handle to DCnWidth, // width of bitmap, in pixelsnHeight // height of bitmap, in pixels);::SelectObject(hMemDC, hBmp); //将位图选入内存设备描述表//复制屏幕设备描述表到内存设备描述表::BitBlt(hMemDC, // handle to destination DC0, // x-coord of destination upper-left corner0, // y-coord of destination upper-left cornernWidth, // width of destination rectanglenHeight, // height of destination rectanglehScrDC, // handle to source DC0, // x-coordinate of source upper-left corner0, // y-coordinate of source upper-left cornerSRCCOPY // raster operation code);CBitmap Bmp;Bmp.Attach(hBmp);//根据位图句柄,得到位图BITMAP Bitmap; //位图结构Bmp.GetBitmap(&Bitmap); //得到位图信息头和位图颜色信息BITMAPINFO BitmapInfo; //位图信息结构,包含位图信息头和位图颜色信息//位图信息头结构BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFO); //本结构所占用的字节数BitmapInfo.bmiHeader.biWidth = Bitmap.bmWidth; //位图宽度,以像素为单位BitmapInfo.bmiHeader.biHeight = Bitmap.bmHeight; //位图高度,以像素为单位BitmapInfo.bmiHeader.biPlanes = 1; //目标设备的级别,必须为1BitmapInfo.bmiHeader.biBitCount = Bitmap.bmBitsPixel; //每个像素所需的位数,必须为1,4,8,24,32BitmapInfo.bmiHeader.biCompression = 0; //位图压缩类型,必须为0,1,2BitmapInfo.bmiHeader.biSizeImage = Bitmap.bmWidthBytes * Bitmap.bmHeight; //位图大小,以字节为单位BitmapInfo.bmiHeader.biXPelsPerMeter = 0; //位图水平分辨率,每米像素数BitmapInfo.bmiHeader.biYPelsPerMeter = 0; //位图垂直分辩率,每米象素数BitmapInfo.bmiHeader.biClrUsed = 0; //位图实际使用的颜色表中的颜色数BitmapInfo.bmiHeader.biClrImportant = 0; //位图显示过程中重要的颜色数//位图数据信息m_pBmpData = new char[Bitmap.bmWidthBytes * Bitmap.bmHeight]; //用于保存位图数据的缓冲区int n = ::GetDIBits(hMemDC, // handle to DChBmp, // handle to bitmap0, // first scan line to setBitmap.bmHeight, // number of scan lines to copym_pBmpData, // array for bitmap bits&BitmapInfo, // bitmap data bufferDIB_RGB_COLORS // RGB or palette index);m_bmpBit = Bitmap;//创建了句柄,一定要释放,否则会浪费内存::DeleteDC(hScrDC);::DeleteDC(hMemDC);}
好了,服务端的工作完成了,现在轮到客户端接收位图信息,并显示了..和服务端一样,客户端也要另启动个线程,一直接收服务器的位图信息.
- //线程函数,用于接收主机屏幕画面
- UINT ThreadFuncVideo(LPVOID pParam)
- {
- CClient *pClient = (CClient *)pParam;
- while(true)
- {
- if(pClient->m_bEndThreadVideo == true) //结束传送桌面画面线程
- AfxEndThread(2);
- //先接收位图结构
- char BmpBuffer[24];
- int nRecv = recv(pClient->m_socketRealConversationForVideo, BmpBuffer, sizeof(BITMAP), 0);
- //得到位图结构信息
- BITMAP *pBitmap = (BITMAP *)BmpBuffer;
- //如果接收有错..跳过此次错误
- if(pBitmap == NULL)
- continue;
- pClient->m_bmpBit.bmBits = pBitmap->bmBits;
- pClient->m_bmpBit.bmBitsPixel = pBitmap->bmBitsPixel;
- pClient->m_bmpBit.bmHeight = pBitmap->bmHeight;
- pClient->m_bmpBit.bmPlanes = pBitmap->bmPlanes;
- pClient->m_bmpBit.bmType = pBitmap->bmType;
- pClient->m_bmpBit.bmWidth = pBitmap->bmWidth;
- pClient->m_bmpBit.bmWidthBytes = pBitmap->bmWidthBytes;
- //获得位图的数据
- int size = pClient->m_bmpBit.bmWidthBytes * pClient->m_bmpBit.bmHeight;
- pClient->m_pBmpData = new char[size];
- if(pClient->m_pBmpData == NULL)
- {
- pClient->m_pView->MessageBox("faile memery");
- return 0;
- }
- char *pch = pClient->m_pBmpData;
- int nBytesRec = 0;
- int nBytesThisTime = 0;
- do{ //发送的内容较大采用循环发送完成为止
- if(pClient->m_bEndThreadVideo == true) //结束传送桌面画面线程
- break;
- nBytesThisTime = recv(pClient->m_socketRealConversationForVideo, pch, size - nBytesRec, 0);
- nBytesRec += nBytesThisTime;
- pch += nBytesThisTime;
- }while(nBytesRec < size);
- pClient->ShowBmp(); //显示刚收到的位图
- }
- return 0;
- }
//线程函数,用于接收主机屏幕画面UINT ThreadFuncVideo(LPVOID pParam){CClient *pClient = (CClient *)pParam;while(true){if(pClient->m_bEndThreadVideo == true) //结束传送桌面画面线程AfxEndThread(2);//先接收位图结构char BmpBuffer[24];int nRecv = recv(pClient->m_socketRealConversationForVideo, BmpBuffer, sizeof(BITMAP), 0);//得到位图结构信息BITMAP *pBitmap = (BITMAP *)BmpBuffer;//如果接收有错..跳过此次错误if(pBitmap == NULL)continue;pClient->m_bmpBit.bmBits = pBitmap->bmBits;pClient->m_bmpBit.bmBitsPixel = pBitmap->bmBitsPixel;pClient->m_bmpBit.bmHeight = pBitmap->bmHeight;pClient->m_bmpBit.bmPlanes = pBitmap->bmPlanes;pClient->m_bmpBit.bmType = pBitmap->bmType;pClient->m_bmpBit.bmWidth = pBitmap->bmWidth;pClient->m_bmpBit.bmWidthBytes = pBitmap->bmWidthBytes;//获得位图的数据int size = pClient->m_bmpBit.bmWidthBytes * pClient->m_bmpBit.bmHeight;pClient->m_pBmpData = new char[size];if(pClient->m_pBmpData == NULL){ pClient->m_pView->MessageBox("faile memery");return 0; }char *pch = pClient->m_pBmpData;int nBytesRec = 0;int nBytesThisTime = 0;do{ //发送的内容较大采用循环发送完成为止if(pClient->m_bEndThreadVideo == true) //结束传送桌面画面线程break;nBytesThisTime = recv(pClient->m_socketRealConversationForVideo, pch, size - nBytesRec, 0);nBytesRec += nBytesThisTime;pch += nBytesThisTime;}while(nBytesRec < size);pClient->ShowBmp(); //显示刚收到的位图}return 0;}
接收到的数据都保存在客户端类CClient的数据成员中.. BITMAP m_bmpBit; //桌面位图结构 char m_pBmpData; //桌面位图数据,下面是在客户端显示服务端桌面画面函数ShowBmp();
- void CClient::ShowBmp()
- {
- CBitmap tbitmap;
- //根据位图结构信息创建位图对象
- if(tbitmap.CreateBitmapIndirect(&m_bmpBit) == NULL)
- {
- m_pView->MessageBox("b mull");
- return ;
- }
- if(tbitmap.m_hObject == NULL)
- {
- m_pView->MessageBox("NULL");
- return ;
- }
- BITMAP Bitmap = m_bmpBit;
- BITMAPINFO BitmapInfo; //位图信息结构,包含位图信息头和位图颜色信息
- //位图信息头结构
- BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFO); //本结构所占用的字节数
- BitmapInfo.bmiHeader.biWidth = Bitmap.bmWidth; //位图宽度,以像素为单位
- BitmapInfo.bmiHeader.biHeight = Bitmap.bmHeight; //位图高度,以像素为单位
- BitmapInfo.bmiHeader.biPlanes = 1; //目标设备的级别,必须为1
- BitmapInfo.bmiHeader.biBitCount = Bitmap.bmBitsPixel; //每个像素所需的位数,必须为1,4,8,24,32
- BitmapInfo.bmiHeader.biCompression = 0; //位图压缩类型,必须为0,1,2
- BitmapInfo.bmiHeader.biSizeImage = Bitmap.bmWidthBytes * Bitmap.bmHeight; //位图大小,以字节为单位
- BitmapInfo.bmiHeader.biXPelsPerMeter = 0; //位图水平分辨率,每米像素数
- BitmapInfo.bmiHeader.biYPelsPerMeter = 0; //位图垂直分辩率,每米象素数
- BitmapInfo.bmiHeader.biClrUsed = 0; //位图实际使用的颜色表中的颜色数
- BitmapInfo.bmiHeader.biClrImportant = 0; //位图显示过程中重要的颜色数
- CDC *pDC = m_pView->GetDC();
- CDC tmemdc;
- tmemdc.CreateCompatibleDC(pDC);
- tmemdc.SelectObject(&tbitmap);
- //SetDIBits函数功能:该函数使用指定的DIB位图中发现的颜色数据来设置位图中的像素。
- SetDIBits(tmemdc.m_hDC, tbitmap, 0, Bitmap.bmHeight, m_pBmpData, &BitmapInfo, DIB_RGB_COLORS);
- //在程序的客户区显示主机桌面
- CRect rect;
- m_pView->GetClientRect(&rect);
- pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &tmemdc,0,0,Bitmap.bmWidth, Bitmap.bmHeight,SRCCOPY);
- //释放资源
- pDC->DeleteDC();
- tmemdc.DeleteDC();
- delete []m_pBmpData;
- m_pBmpData = NULL;
- }
void CClient::ShowBmp(){CBitmap tbitmap;//根据位图结构信息创建位图对象if(tbitmap.CreateBitmapIndirect(&m_bmpBit) == NULL) {m_pView->MessageBox("b mull");return ;}if(tbitmap.m_hObject == NULL) {m_pView->MessageBox("NULL");return ;}BITMAP Bitmap = m_bmpBit;BITMAPINFO BitmapInfo; //位图信息结构,包含位图信息头和位图颜色信息//位图信息头结构BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFO); //本结构所占用的字节数BitmapInfo.bmiHeader.biWidth = Bitmap.bmWidth; //位图宽度,以像素为单位BitmapInfo.bmiHeader.biHeight = Bitmap.bmHeight; //位图高度,以像素为单位BitmapInfo.bmiHeader.biPlanes = 1; //目标设备的级别,必须为1BitmapInfo.bmiHeader.biBitCount = Bitmap.bmBitsPixel; //每个像素所需的位数,必须为1,4,8,24,32BitmapInfo.bmiHeader.biCompression = 0; //位图压缩类型,必须为0,1,2BitmapInfo.bmiHeader.biSizeImage = Bitmap.bmWidthBytes * Bitmap.bmHeight; //位图大小,以字节为单位BitmapInfo.bmiHeader.biXPelsPerMeter = 0; //位图水平分辨率,每米像素数BitmapInfo.bmiHeader.biYPelsPerMeter = 0; //位图垂直分辩率,每米象素数BitmapInfo.bmiHeader.biClrUsed = 0; //位图实际使用的颜色表中的颜色数BitmapInfo.bmiHeader.biClrImportant = 0; //位图显示过程中重要的颜色数CDC *pDC = m_pView->GetDC();CDC tmemdc; tmemdc.CreateCompatibleDC(pDC);tmemdc.SelectObject(&tbitmap);//SetDIBits函数功能:该函数使用指定的DIB位图中发现的颜色数据来设置位图中的像素。SetDIBits(tmemdc.m_hDC, tbitmap, 0, Bitmap.bmHeight, m_pBmpData, &BitmapInfo, DIB_RGB_COLORS);//在程序的客户区显示主机桌面CRect rect;m_pView->GetClientRect(&rect);pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &tmemdc,0,0,Bitmap.bmWidth, Bitmap.bmHeight,SRCCOPY);//释放资源pDC->DeleteDC();tmemdc.DeleteDC();delete []m_pBmpData;m_pBmpData = NULL;}
到目前为止,关于传送桌面画面的功能就实现了..补充一点,Windows系统好多图片都是bmp格式的,即位图..一张位图文件包括四个部分,1 位图文件头,2 位图信息头,3 位图颜色表,4 位图数据信息;具体的你自己Baidu或google下位图格式吧..我就不多说了..
2.录制服务端的声音,发送给客户端播放..
还是先从服务端说起,我在服务端创建了一个类CSound,专门用于声音的采集..先看看这个类吧:
- class CSound
- {
- private:
- WAVEFORMATEX m_WaveFormat; //音频格式
- //in
- HWAVEIN m_hWaveIn; //音频输入设备句柄
- WAVEHDR m_wavehdrIn; //标识输入缓冲的WAVEHDR结构
- CServerDlg *m_pDlg; //指向窗口的指针
- public:
- CHAR m_chWaveBufferIn[MAX_BUFFER_SIZE]; //保存音频数据的缓冲区
- CSound(CServerDlg *pDlg); //构造函数,传递窗口类指针
- ~CSound();
- void Init(); //初始化,准备录音
- void Record();//开始录音
- void Pause(); //暂停录音
- void FreeBufferIn(); //释放输入音频缓冲
- };
class CSound{private:WAVEFORMATEX m_WaveFormat; //音频格式//inHWAVEIN m_hWaveIn; //音频输入设备句柄 WAVEHDR m_wavehdrIn; //标识输入缓冲的WAVEHDR结构 CServerDlg *m_pDlg; //指向窗口的指针public:CHAR m_chWaveBufferIn[MAX_BUFFER_SIZE]; //保存音频数据的缓冲区CSound(CServerDlg *pDlg); //构造函数,传递窗口类指针~CSound(); void Init(); //初始化,准备录音void Record();//开始录音void Pause(); //暂停录音void FreeBufferIn(); //释放输入音频缓冲};
关于声音的采集,主要用到的API函数有:waveInOpen(), waveInPrepareHeader(),waveInStart(),关于这部分的用法,最好能看看我前面介绍的书,里面讲的很详细..每当一个内存块被录满时,就会向窗口发送消息MM_WIM_DATA,这样我就在CServeDlg,这个窗口类中添加了消息映射,和消息处理函数..
- //当音频输入缓冲录满时,释放缓冲
- void CServerDlg::OnAudioBufferFull()
- {
- m_pServer->SendAudioData();
- }
//当音频输入缓冲录满时,释放缓冲void CServerDlg::OnAudioBufferFull(){m_pServer->SendAudioData();}
下面是CServer类的成员函数SendAudioData(),发送语音数据.
- //向客户端发送音频数据
- void CServer::SendAudioData()
- {
- //发送音频数据
- send(m_socketRealConversationForAudio, m_pSound->m_chWaveBufferIn, MAX_BUFFER_SIZE, 0);
- //释放内存块,为下次录音准备
- m_pSound->FreeBufferIn();
- }
//向客户端发送音频数据void CServer::SendAudioData(){//发送音频数据send(m_socketRealConversationForAudio, m_pSound->m_chWaveBufferIn, MAX_BUFFER_SIZE, 0);//释放内存块,为下次录音准备m_pSound->FreeBufferIn(); }
每当发送一个内存块时,要清空一次,为下次录音做准备.下面函数是释放输入缓冲的内存块
- //当一内存块被录满时,调用此函数,为下次录音准备
- void CSound::FreeBufferIn()
- {
- MMRESULT result;
- //调用waveInUnprepareHeader(),释放输入音频的缓冲
- result = waveInUnprepareHeader(m_hWaveIn, &m_wavehdrIn, sizeof(WAVEHDR));
- if(result != MMSYSERR_NOERROR)
- {
- m_pDlg->MessageBox("调用waveInUnprepareHeader()出错");
- return ;
- }
- //再为下一次录音准备
- //调用waveInPrepareHeader(),准备输入音频的缓冲
- m_wavehdrIn.lpData = m_chWaveBufferIn;
- m_wavehdrIn.dwBufferLength = MAX_BUFFER_SIZE;
- m_wavehdrIn.dwBytesRecorded = 0;
- m_wavehdrIn.dwFlags = 0;
- result = waveInPrepareHeader(m_hWaveIn, &m_wavehdrIn, sizeof(WAVEHDR));
- if(result != MMSYSERR_NOERROR)
- {
- m_pDlg->MessageBox("调用waveInPrepareHeader()出错");
- return ;
- }
- //调用waveInAddBuffer(),增加内存
- result = waveInAddBuffer(m_hWaveIn, &m_wavehdrIn, sizeof(WAVEHDR));
- if(result != MMSYSERR_NOERROR)
- {
- m_pDlg->MessageBox("调用waveInAddBuffer()出错");
- return ;
- }
- }
//当一内存块被录满时,调用此函数,为下次录音准备void CSound::FreeBufferIn(){MMRESULT result;//调用waveInUnprepareHeader(),释放输入音频的缓冲result = waveInUnprepareHeader(m_hWaveIn, &m_wavehdrIn, sizeof(WAVEHDR));if(result != MMSYSERR_NOERROR){m_pDlg->MessageBox("调用waveInUnprepareHeader()出错");return ;}//再为下一次录音准备//调用waveInPrepareHeader(),准备输入音频的缓冲m_wavehdrIn.lpData = m_chWaveBufferIn;m_wavehdrIn.dwBufferLength = MAX_BUFFER_SIZE;m_wavehdrIn.dwBytesRecorded = 0;m_wavehdrIn.dwFlags = 0;result = waveInPrepareHeader(m_hWaveIn, &m_wavehdrIn, sizeof(WAVEHDR));if(result != MMSYSERR_NOERROR){m_pDlg->MessageBox("调用waveInPrepareHeader()出错");return ;}//调用waveInAddBuffer(),增加内存result = waveInAddBuffer(m_hWaveIn, &m_wavehdrIn, sizeof(WAVEHDR));if(result != MMSYSERR_NOERROR){m_pDlg->MessageBox("调用waveInAddBuffer()出错");return ;}}
好了,服务端的任务完成了..剩下客户端来接收数据,并播放录音了..客户端也用了个线程,一直接收数据,然后播放
- //线程函数,用于接收主机声音
- UINT ThreadFuncAudio(LPVOID pParam)
- {
- CClient *pClient = (CClient *)pParam;
- while(true)
- {
- if(pClient->m_bEndThreadAudio == true) //结束接收主机声音线程
- AfxEndThread(2);
- char *pAudioData = new char[MAX_BUFFER_SIZE];
- recv(pClient->m_socketRealConversationForAudio, pAudioData, MAX_BUFFER_SIZE, 0);
- pClient->Play(pAudioData);
- delete []pAudioData;
- }
- return 0;
- }
//线程函数,用于接收主机声音UINT ThreadFuncAudio(LPVOID pParam){CClient *pClient = (CClient *)pParam;while(true){if(pClient->m_bEndThreadAudio == true) //结束接收主机声音线程 AfxEndThread(2);char *pAudioData = new char[MAX_BUFFER_SIZE];recv(pClient->m_socketRealConversationForAudio, pAudioData, MAX_BUFFER_SIZE, 0);pClient->Play(pAudioData);delete []pAudioData; }return 0;}
3.文件传输,能在客户端显示服务端的主机目录,类似windows资源管理器,并能进行文件传输.
服务端:
服务端只要每次读文件,然后发送出去就行了,当然这都是要用到一个线程的
- //线程函数,用于发送文件
- UINT ThreadFuncSendFile(LPVOID pParam)
- {
- CServer *pServer = (CServer *)pParam;
- //创建用于音频数据传输的真正会话socket
- int nLength = sizeof(pServer->m_sockaddrServer);
- pServer->m_socketRealConversationForSendFile = accept(pServer->m_socketListenForSendFile, (sockaddr *)&pServer->m_sockaddrServer, &nLength);
- if(pServer->m_socketRealConversationForSendFile == INVALID_SOCKET)
- {
- pServer->m_pDlg->MessageBox("调用accept()出错");
- return 0;
- }
- //打开文件
- CFile file;
- //先发送文件大小
- if(file.Open(pServer->m_strFilePath, CFile::modeRead) == 0) //打开文件失败
- {
- //如果打开文件失败,发送文件长度为-1
- send(pServer->m_socketRealConversationForSendFile, "-1", 10, 0);
- }
- else //开始发送文件
- {
- //发送文件大小
- CFileStatus FileStatus;
- file.GetStatus(FileStatus);
- char cSize[10];
- DWORD nSize = FileStatus.m_size;
- itoa(nSize, cSize, 10);
- send(pServer->m_socketRealConversationForSendFile, cSize, 10, 0);
- //发送文件数据
- DWORD nSendOneTime = 1024/4; //每次只发送(1/2K)数据
- char *pFileData = new char[nSendOneTime];
- DWORD nSend = 0; //已发送
- while(nSend < nSize)
- {
- if(pServer->m_bEndThreadSendFile == true)
- {
- break;
- }
- file.Read(pFileData, nSendOneTime);
- nSend += send(pServer->m_socketRealConversationForSendFile, pFileData, nSendOneTime, 0);
- }
- //释放内存
- delete []pFileData;
- //关闭文件
- file.Close();
- }
- //文件传输完毕,关闭socket,并安全的结束线程
- closesocket(pServer->m_socketListenForSendFile);
- closesocket(pServer->m_socketRealConversationForSendFile);
- AfxEndThread(2);
- return 0;
- }
//线程函数,用于发送文件UINT ThreadFuncSendFile(LPVOID pParam){CServer *pServer = (CServer *)pParam;//创建用于音频数据传输的真正会话socketint nLength = sizeof(pServer->m_sockaddrServer);pServer->m_socketRealConversationForSendFile = accept(pServer->m_socketListenForSendFile, (sockaddr *)&pServer->m_sockaddrServer, &nLength);if(pServer->m_socketRealConversationForSendFile == INVALID_SOCKET){pServer->m_pDlg->MessageBox("调用accept()出错");return 0;}//打开文件CFile file;//先发送文件大小if(file.Open(pServer->m_strFilePath, CFile::modeRead) == 0) //打开文件失败{//如果打开文件失败,发送文件长度为-1send(pServer->m_socketRealConversationForSendFile, "-1", 10, 0); }else //开始发送文件{//发送文件大小CFileStatus FileStatus;file.GetStatus(FileStatus);char cSize[10];DWORD nSize = FileStatus.m_size;itoa(nSize, cSize, 10); send(pServer->m_socketRealConversationForSendFile, cSize, 10, 0);//发送文件数据DWORD nSendOneTime = 1024/4; //每次只发送(1/2K)数据 char *pFileData = new char[nSendOneTime]; DWORD nSend = 0; //已发送 while(nSend < nSize){if(pServer->m_bEndThreadSendFile == true){ break;}file.Read(pFileData, nSendOneTime); nSend += send(pServer->m_socketRealConversationForSendFile, pFileData, nSendOneTime, 0);} //释放内存delete []pFileData;//关闭文件file.Close();} //文件传输完毕,关闭socket,并安全的结束线程closesocket(pServer->m_socketListenForSendFile);closesocket(pServer->m_socketRealConversationForSendFile);AfxEndThread(2);return 0;}
服务端要做的事就这么多,下面是客户端要完成的任务:
- UINT ThreadFuncRecvFile(LPVOID pParam)
- {
- CClient *pClient = (CClient *)pParam;
- pClient->m_pDlg->m_bOnRecvFile = true;
- //选接收文件大小
- char cSize[10];
- DWORD nSize = 0;
- recv(pClient->m_socketRealConversationForRecvFile, cSize, 10, 0);
- nSize = atoi(cSize); //得到文件大小
- if(nSize == -1)
- {
- pClient->m_pDlg->MessageBox("服务器打开文件失败");
- //发送消息,使按钮可用
- pClient->m_pDlg->m_bOnRecvFile = false;
- pClient->m_pDlg->SendMessage(WM_ENABLEBUTTON);
- }
- else
- {
- DWORD nRecvOneTime = 1024/4; //每次只接收1/2K的大小
- DWORD nTime = nSize/nRecvOneTime; //总共需要接收的次数
- char *pFileData = new char[nRecvOneTime];
- CFile file;
- file.Open(pClient->m_strSaveAs, CFile::modeCreate | CFile::modeWrite); //打开文件
- //pClient->m_pDlg->m_ProgressCtrlRecv.SetRange(0, nSize);
- pClient->m_pDlg->m_ProgressCtrlRecv.SetRange32(0, nSize);
- DWORD nRecv = 0;
- //数据类型转换
- char cSize[10];
- itoa(nSize, cSize, 10);
- char cRecv[10];
- DWORD nSumRecv = 0;
- int i = 0;
- while(nSumRecv < nSize)
- {
- i++;
- if(pClient->m_bEndThreadRecvFile == true)
- break;
- nRecv = recv(pClient->m_socketRealConversationForRecvFile, pFileData, nRecvOneTime, 0);
- nSumRecv += nRecv;
- file.Write(pFileData, nRecvOneTime);
- //显示完成进度,由于文件传递速度快,每隔1000个循环才提示一次接收进度
- if(i%1000 == 0)
- {
- pClient->m_pDlg->m_ProgressCtrlRecv.SetPos(nSumRecv);
- itoa(nSumRecv, cRecv, 10);
- pClient->m_pDlg->SendMessage(WM_SHOWPROGRESS, (WPARAM)cRecv, (LPARAM)cSize);
- }
- }
- delete []pFileData;
- //最后再显示一次进度
- pClient->m_pDlg->m_ProgressCtrlRecv.SetPos(nSumRecv);
- if(nSumRecv > nSize)
- nSumRecv = nSize;
- itoa(nSumRecv, cRecv, 10);
- pClient->m_pDlg->SendMessage(WM_SHOWPROGRESS, (WPARAM)cRecv, (LPARAM)cSize);
- if(pClient->m_bEndThreadRecvFile == true)
- pClient->m_pDlg->MessageBox("已取消传送文件");
- else
- pClient->m_pDlg->MessageBox("文件传输完毕");
- //发送消息,使按钮可用
- pClient->m_pDlg->m_bOnRecvFile = false;
- pClient->m_pDlg->SendMessage(WM_ENABLEBUTTON);
- file.Close();
- //提示文本取消..进度条为0
- pClient->m_pDlg->SendMessage(WM_SHOWPROGRESS, (WPARAM)NULL, (LPARAM)cSize);
- pClient->m_pDlg->m_ProgressCtrlRecv.SetPos(0);
- }
- //文件接收完毕,关闭socket,结束线程
- closesocket(pClient->m_socketRealConversationForRecvFile);
- pClient->m_bEndThreadRecvFile = false;
- AfxEndThread(2);
- return 0;
- }
UINT ThreadFuncRecvFile(LPVOID pParam){CClient *pClient = (CClient *)pParam;pClient->m_pDlg->m_bOnRecvFile = true;//选接收文件大小char cSize[10];DWORD nSize = 0;recv(pClient->m_socketRealConversationForRecvFile, cSize, 10, 0);nSize = atoi(cSize); //得到文件大小if(nSize == -1){pClient->m_pDlg->MessageBox("服务器打开文件失败");//发送消息,使按钮可用pClient->m_pDlg->m_bOnRecvFile = false;pClient->m_pDlg->SendMessage(WM_ENABLEBUTTON);}else{DWORD nRecvOneTime = 1024/4; //每次只接收1/2K的大小DWORD nTime = nSize/nRecvOneTime; //总共需要接收的次数char *pFileData = new char[nRecvOneTime];CFile file;file.Open(pClient->m_strSaveAs, CFile::modeCreate | CFile::modeWrite); //打开文件//pClient->m_pDlg->m_ProgressCtrlRecv.SetRange(0, nSize);pClient->m_pDlg->m_ProgressCtrlRecv.SetRange32(0, nSize);DWORD nRecv = 0;//数据类型转换char cSize[10]; itoa(nSize, cSize, 10);char cRecv[10];DWORD nSumRecv = 0;int i = 0;while(nSumRecv < nSize){i++;if(pClient->m_bEndThreadRecvFile == true)break;nRecv = recv(pClient->m_socketRealConversationForRecvFile, pFileData, nRecvOneTime, 0);nSumRecv += nRecv;file.Write(pFileData, nRecvOneTime);//显示完成进度,由于文件传递速度快,每隔1000个循环才提示一次接收进度if(i%1000 == 0){pClient->m_pDlg->m_ProgressCtrlRecv.SetPos(nSumRecv);itoa(nSumRecv, cRecv, 10);pClient->m_pDlg->SendMessage(WM_SHOWPROGRESS, (WPARAM)cRecv, (LPARAM)cSize);}}delete []pFileData;//最后再显示一次进度pClient->m_pDlg->m_ProgressCtrlRecv.SetPos(nSumRecv);if(nSumRecv > nSize) nSumRecv = nSize;itoa(nSumRecv, cRecv, 10);pClient->m_pDlg->SendMessage(WM_SHOWPROGRESS, (WPARAM)cRecv, (LPARAM)cSize); if(pClient->m_bEndThreadRecvFile == true)pClient->m_pDlg->MessageBox("已取消传送文件");elsepClient->m_pDlg->MessageBox("文件传输完毕");//发送消息,使按钮可用pClient->m_pDlg->m_bOnRecvFile = false;pClient->m_pDlg->SendMessage(WM_ENABLEBUTTON);file.Close();//提示文本取消..进度条为0pClient->m_pDlg->SendMessage(WM_SHOWPROGRESS, (WPARAM)NULL, (LPARAM)cSize);pClient->m_pDlg->m_ProgressCtrlRecv.SetPos(0);}//文件接收完毕,关闭socket,结束线程closesocket(pClient->m_socketRealConversationForRecvFile);pClient->m_bEndThreadRecvFile = false;AfxEndThread(2);return 0;}
到现在,这个程序基本完成了,我只说了重要的部分,其实,我这程序还有些问题没解决,比如,传送文件过程,文件传过来了,可是有些文件很数据丢失,文件打不开等错误..由于得法是自己想的,所以不知如何解决,希望有经验的朋友能告诉我,在此,先谢谢你了..!!