MFC/Socket网络编程:[1]服务器
Windows程序开发中,如果涉及到网络编程的话,一般少不了socket,socket作为应用层与传输层之间的一个抽象层,可以理解为应用程序与网络协议之间的编程接口。我曾通过MFC开发了一个简单C/S(客户端/服务器)模式的应用程序,主要是为了实现手机控制电脑,android网络编程也可以通过socket实现,这里以PC端编写服务器程序和客户端程序为例说明socket编程的一般步骤。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/7d34fbf4fcf5ee0d68f12264f96b0ce264e7ba96.jpg)
2、项目配置,在向导过程中选择“基于对话框”模式,并选择“windows”套接字。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/65390a23beb9763e7dfd33656ad06de89b61b096.jpg)
3、设计服务器界面,控件有:4个静态文本(Static Text),最后一个用于指示用户连接个数;一个按钮(Button),用于打开或关闭服务器;2个编辑框(Edit Control),一个用于输入端口号,另一个只读的用于显示事件日志。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/cfa9ae04541bd10f95198529ba0e1799e82aa796.jpg)
5、 给控件添加变量和事件处理函数,这个通过类向导就可以完成,变量如上表所示,事件只有“按钮按下”一个,双击按钮自动生成函数,后面添加相关代码即可。2、 在类视图中添加一个新类CServerSocket,派生于CSocket类,对该类进行类向导添加三个函数:OnAccept()、OnClose()、OnReceive()
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/c255efc595ee41c1c41824e08d88912ca4ca9b96.jpg)
2、修改头文件ServerSocket.h,定义主对话框的指针变量#pragma once#include "PhoneServerDlg.h" // 主对话框头文件class CPhoneServerDlg; //别忘了加上 class CServerSocket : public CSocket{public: CPhoneServerDlg* m_pDlg; // 主对话框指针对象 CServerSocket(); virtual ~CServerSocket(); virtual void OnReceive(int nErrorCode); virtual void OnClose(int nErrorCode); virtual void OnAccept(int nErrorCode);};
3、修改源文件ServerSocket.cpp,注意其中调用的函数都在主对话框类中定义。void CServerSocket::OnReceive(int nErrorCode){ m_pDlg->RecvData(this); // 接收数据 CSocket::OnReceive(nErrorCode);}void CServerSocket::OnClose(int nErrorCode){ m_pDlg->RemoveClient(this); // 删除下线用户 CSocket::OnClose(nErrorCode);}void CServerSocket::OnAccept(int nErrorCode){ m_pDlg->AddClient(); //添加上线用户 CSocket::OnAccept(nErrorCode);}至此,CServerSocket类的代码就完成了,接下来编写主类相关函数。
编写主对话框类
1、修改头文件PhoneServerDlg.h1、 添加服务器类的头文件。#include "ServerSocket.h"class CServerSocket; //一定要加上2、 添加函数声明和变量定义CServerSocket* listenSocket; // 用于打开服务器CPtrList m_clientList; // 链表用于存储用户bool m_connect; // 用于标记服务器状态void AddClient(); // 增加用户,响应用户请求void RemoveClient(CServerSocket* pSocket); // 移除下线的用户void RecvData(CServerSocket* pSocket); // 获取数据void UpdateEvent(CString str); // 更新事件日志BOOL WChar2MByte(LPCWSTR srcBuff, LPSTR destBuff, int nlen);//字符转换void SendMSG(CString str); // 发送消息给各个客户端void ControlPC(CString AndroidControl); // 手机控制PC的响应函数
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/5e615d715fdb36203805453aabc5260f89358d96.jpg)
3、 编写AddClient函数,用于增加用户,响应用户请求void CPhoneServerDlg::AddClient(){ CServerSocket *pSocket = new CServerSocket; pSocket->m_pDlg = this; listenSocket->Accept(*pSocket); pSocket->AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE); m_clientList.AddTail(pSocket); m_userCount = m_clientList.GetCount(); UpdateData(false); UpdateEvent(_T("用户连接服务器.")); SendMSG(_T("Hello!"));}说明:本函数在CServerSocket类中的OnAccept消息中调用,用于响应用户连接服务器的请求,主要函数为Accept,当连接成功后,通过链表m_clientList保存新用户,更新日志,向新用户发送“Hello”表示欢迎。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/890dfb4a2f27e7efb139e3b219dd3340b7f3f596.jpg)
5、 编写RecvData函数,用于获取数据void CPhoneServerDlg::RecvData(CServerSocket* pSocket){ char* pData = NULL; pData = new char[1024]; memset(pData, 0, sizeof(char)* 1024); UCHAR leng = 0; CString str; if (pSocket->Receive(pData, 1024, 0) != SOCKET_ERROR) { str = pData; ControlPC(str); // 依据指令控制电脑 SendMSG(str); // 转发数据给所有用户,包括发送数据的用户 } delete pData; pData = NULL;}说明:本函数在CServerSocket类中的OnReceive消息中调用,用于处理接收到的数据并控制电脑,并将数据转发给所有用户(类似于群消息),通过CSocket类的GetPeerName函数可以获取用户的IP和端口号。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/f11f54237971fe1d3ae790aaea20a7cd0d6ee096.jpg)
7、 编写WChar2MByte函数,用于实现字符转换BOOL CPhoneServerDlg::WChar2MByte(LPCWSTR srcBuff, LPSTR destBuff, int nlen){ int n = 0; n = WideCharToMultiByte(CP_OEMCP, 0, srcBuff, -1, destBuff, 0, 0, FALSE); if (n<nlen)return FALSE; WideCharToMultiByte(CP_OEMCP, 0, srcBuff, -1, destBuff, nlen, 0, FALSE); return TRUE;}说明:本函数在发送函数SendMSG中调用,用于字符集的转换,将宽字符转换为多字符集,不经转换的话,接收方只能接收一个字节。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/cd93a56651598540d8158c51b5a23a42a17ac496.jpg)
9、 编写ControlPC函数,用于处理接收到的指令并控制电脑,主要是为了实现手机控制而写。void CPhoneServerDlg多唉捋胝::ControlPC(CString AndroidControl){ if (AndroidControl == "mop") //打开播放器 { ShellExecute(NULL, _T("open"), _T("C:\\Program Files (x86)\\KuGou\\KGMusic\\KuGou.exe"), NULL, NULL, SW_SHOWNORMAL); } else if (AndroidControl == "mcl") //关闭播放器 { DWORD id_num; HWND hWnd = ::FindWindow(_T("kugou_ui"), NULL); GetWindowThreadProcessId(hWnd, &id_num); //注意:第二个参数是进程的ID,返回值是线程的ID。 HANDLE hd = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id_num); TerminateProcess(hd, 0); } else if (AndroidControl == "mpl" || AndroidControl == "mpa") //播放/暂停 { keybd_event(VK_LMENU, 0, 0, 0); keybd_event(VK_F5, 0, 0, 0); keybd_event(VK_F5, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_LMENU, 0, KEYEVENTF_KEYUP, 0); }}说明:控制功能可以自己随意添加,这里只以音乐播放为例进行说明,ShellExecute函数用于调用其他应用程序,关闭进程比较麻烦一点,这里先获取应用程序窗口的ID,通过OpenProcess和TerminateProcess终止进程。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/0d55dc7bd282868976793f0265f97fbd4d7c3797.jpg)
11、经过以上步骤,服务器端的程序就完成了,虽然函数有点多,但只要理解了socket的使用流程并不难,总体可以概括为4步:1、创建服务器2、连接请求连接的客户端3、与客户端进行数据传输(发送和接收)4、客户端断开服务器加上一些辅助代码,可以更好的实现网络通讯。客户端的程序会在下一篇经验中介绍。
![MFC/Socket网络编程:[1]服务器](https://exp-picture.cdn.bcebos.com/f591ab03c8d246fea9714a30b8bf3bef344f1e97.jpg)