MFC/Socket网络编程:[1]服务器

Windows程序开发中,如果涉及到网络编程的话,一般少不了socket,socket作为应用层与传输层之间的一个抽象层,可以理解为应用程序与网络协议之间的编程接口。

我曾通过MFC开发了一个简单C/S(客户端/服务器)模式的应用程序,主要是为了实现手机控制电脑,android网络编程也可以通过socket实现,这里以PC端编写服务器程序和客户端程序为例说明socket编程的一般步骤。

工具/原料

Visual Studio 2010/2013

创建项目

首先创建一个MFC项目,修改名称以及存放路径。

项目配置,在向导过程中选择“基于对话框”模式,并选择“windows”套接字。

设计服务器界面,控件有:4个静态文本(Static Text),最后一个用于指示用户连接个数;一个按钮(Button),用于打开或关闭服务器;2个编辑框(Edit Control),一个用于输入端口号,另一个只读的用于显示事件日志。

修改各个控件的属性,注意编辑框2还要把Multiline和Vertical Scroll属性选为true,以实现多行显示并自带滚动条。

1、 给控件添加变量和事件处理函数,这个通过类向导就可以完成,变量如上表所示,事件只有“按钮按下”一个,双击按钮自动生成函数,后面添加相关代码即可。

2、 在类视图中添加一个新类CServerSocket,派生于CSocket类,对该类进行类向导添加三个函数:

OnAccept()、OnClose()、OnReceive()

编写服务器类

看看CServerSocket的类视图,需要修改的有之前生成的三个函数。

修改头文件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);

};

修改源文件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类的代码就完成了,接下来编写主类相关函数。

编写主对话框类

修改头文件PhoneServerDlg.h

1、 添加服务器类的头文件。

#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 src="/blank.gif" data-echoBuff, LPSTR destBuff, int nlen);

//字符转换

void SendMSG(CString str); // 发送消息给各个客户端

void ControlPC(CString AndroidControl); // 手机控制PC的响应函数

修改PhoneServerDlg.cpp实现头文件中声明的函数

1、 首先实现“打开服务器”按钮的响应函数

void CPhoneServerDlg::OnBnClickedStartserver()

{

// TODO: 在此添加控件通知处理程序代码

if (m_connect)

{

delete listenSocket;

listenSocket = NULL;

m_connect = false;

SetDlgItemText(IDC_StartServer, _T("打开服务器"));

UpdateEvent(_T("系统关闭服务器."));

return;

}

listenSocket = new CServerSocket();

listenSocket->m_pDlg = this;

// 指定对话框为主对话框,不能少了这句

UpdateData(true);

if (!listenSocket->Create(m_port))

// 创建服务器的套接字,IP地址默认本机IP

{

AfxMessageBox(_T("创建套接字错误!"));

listenSocket->Close();

return;

}

if (!listenSocket->Listen())

{

AfxMessageBox(_T("监听失败!"));

listenSocket->Close();

return;

}

m_connect = true;

SetDlgItemText(IDC_StartServer, _T("关闭服务器"));

UpdateEvent(_T("系统打开服务器."));

}

说明:本函数用于打开或关闭服务器,主要用到Create函数和Listen函数用于创建服务器和监听客户端。其中端口号从编辑框获取,应用程序的可用端口范围是1024-65535。

2、 编写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”表示欢迎。

3、 编写RemoveClient函数,用于移除下线的用户

void CPhoneServerDlg::RemoveClient(CServerSocket* pSocket)

{

POSITION nPos = m_clientList.GetHeadPosition();

POSITION nTmpPos = nPos;

while (nPos)

{

CServerSocket* pSockItem = (CServerSocket*)m_clientList.GetNext(nPos);

if (pSockItem->m_hSocket == pSocket->m_hSocket)

{

pSockItem->Close();

delete pSockItem;

m_clientList.RemoveAt(nTmpPos);

m_userCount = m_clientList.GetCount();

UpdateData(false);

UpdateEvent(_T("用户离开."));

return;

}

nTmpPos = nPos;

}

}

说明:本函数在CServerSocket类中的OnClose消息中调用,用到POSITION结构,查找存储用户中哪位用户下线了,将下线用户释放,从链表中删除,并更新日志。

4、 编写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和端口号。

5、 编写UpdateEvent函数,用于更新事件日志

void CPhoneServerDlg::UpdateEvent(CString str)

{

CString string;

CTime time = CTime::GetCurrentTime();

// 获取系统当前时间

str += _T("\r\n");

// 用于换行显示日志

string = time.Format(_T("%Y/%m/%d %H:%M:%S ")) + str;

// 格式化当前时间

int lastLine = m_event.LineIndex(m_event.GetLineCount() - 1);

//获取编辑框最后一行索引

m_event.SetSel(lastLine+1,lastLine+2,0);

//选择编辑框最后一行

m_event.ReplaceSel(string); //替换所选那一行的内容

}

说明:本函数在所有需要更新日志的地方都有调用,方便服务器记录用户的登录和退出事件。

6、 编写WChar2MByte函数,用于实现字符转换

BOOL CPhoneServerDlg::WChar2MByte(LPCWSTR src="/blank.gif" data-echoBuff, LPSTR destBuff, int nlen)

{

int n = 0;

n = WideCharToMultiByte(CP_OEMCP, 0, src="/blank.gif" data-echoBuff, -1, destBuff, 0, 0, FALSE);

if (n<nlen)return FALSE;

WideCharToMultiByte(CP_OEMCP, 0, src="/blank.gif" data-echoBuff, -1, destBuff, nlen, 0, FALSE);

return TRUE;

}

说明:本函数在发送函数SendMSG中调用,用于字符集的转换,将宽字符转换为多字符集,不经转换的话,接收方只能接收一个字节。

7、 编写SendMSG函数,用于发送消息给各个客户端

void CPhoneServerDlg::SendMSG(CString str)

{

char *pSend = new char[str.GetLength()];

memset(pSend, 0, str.GetLength()*sizeof(char));

if (!WChar2MByte(str.GetBuffer(0), pSend, str.GetLength()))

{

AfxMessageBox(_T("字符转换失败"));

delete pSend;

return;

}

POSITION nPos = m_clientList.GetHeadPosition();

while (nPos)

{

CServerSocket* pTemp = (CServerSocket*)m_clientList.GetNext(nPos);

pTemp->Send(pSend, str.GetLength());

}

delete pSend;

}

说明:发送函数,用于发送消息给所有用户,主要函数为Send,在AddClient和RecvData中都有调用,可以随时调用发消息给用户。

8、 编写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终止进程。

9、 类向导添加虚函数PreTranslateMessage,并编写代码

BOOL CPhoneServerDlg::PreTranslateMessage(MSG* pMsg)

{

switch (pMsg->wParam)

{

case VK_RETURN:

case VK_ESCAPE:

return true; break;

}

return CDialogEx::PreTranslateMessage(pMsg);

}

说明:该函数用于防止按下enter或者esc时退出程序。

经过以上步骤,服务器端的程序就完成了,虽然函数有点多,但只要理解了socket的使用流程并不难,总体可以概括为4步:

1、创建服务器

2、连接请求连接的客户端

3、与客户端进行数据传输(发送和接收)

4、客户端断开服务器

加上一些辅助代码,可以更好的实现网络通讯。客户端的程序会在下一篇经验中介绍。

注意事项

注意类与类之间头文件的引用

如果不重写虚函数PreTranslateMessage会导致按下回车或者esc时自动退出程序

合理设置端口号,不要与其他程序发生冲突

标签:网络, 应用程序, 客户端, 函数, 服务器
分类:电脑软件
时间:2014-08-07

MFC/Socket网络编程:[1]服务器的相关文章

MFC/Socket网络编程:[2]客户端

继上一篇有关服务器的网络编程,这里继续探讨客户端如何发出连接服务器的请求,如何与服务器进行数据传输,如何与其他客户端交换数据,最后如何断开与服务器之间的连接。关键技术就是TCP/IP协议,socket默认使用的是非阻塞式异步传输通讯方式,对应MFC中的CSoket类,采用的是面向连接的TCP协议而不是UDP协议。

Linux网络编程wait()和waitpid()的讲解

客户端断开连接后,服务器端存在大量僵尸进程。这是由于服务器子进程终止后,发送SIGCHLD信号给父进程,而父进程默认忽略了该信号。为避免僵尸进程的产生,无论我们什么时候创建子进程时,主进程都需要等待子进程返回,以便对子进程进行清理。为此,我们在服务器程序中添加SIGCHLD信号处理函数。 复制代码 代码如下: #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <strin

网络编程项目:Linux飞鸽传书通信[1]

相信学过网络编程很多都会去做飞鸽传书通信的项目吧。本经验是教会大家如何运用网络编程的知识来写出“Linux 飞鸽传书通信”的项目。本经验是通过linux系统终端来和飞秋软件(都是使用IPMSG协议)进行通信,具有聊天功能、上线提示、下线提示、广播和文件传输。

java网络编程方向具体该怎么去学

java的大方向就是j2eej2ee不仅仅是socket编程,具体包括13中核心技术J2EE的核心API与组 J2EE平台由一整套服务(Services)、应用程序接口(APIs)和协议构成,它对开发基于Web的多层应用提供了功能支持,下面对J2EE中的13种技术规范进行简单的描述(限于篇幅,这里只能进行简单的描述):

Linux网络编程之TCP网络编程

本文将实现在Linux系统下进行TCP网络编程的开发

android ndk 网络编程

最近一段时间不断的有朋友问我有没有android ndk相关的网络编程的测试用例或者资料,网上基本上又没有,本文主要: ① 不是android上Java网络编程; ② 不仅仅是liunx上网络编程; ③ 就连android ndk官方提供的测试用例也没有网络编程方面的资料; ④ 同时设计windows上服务器,android ndk上客户端的配置; ⑤ 本文是在android上的纯c/c++开发环境下进行的,不会设计Java代码; android ndk 如何进行网络编程?android ndk层进行网络

Java网络编程——客户端与服务端的简单连接

随着互联网技术的越来越快的发展,编程代码也从复杂变得越来越简单化,那么Java网络编程中,客户端与服务器端是如何建立起联系的呢?下面和我一起去看看吧!

C#网络编程系列文章索引

1.网络协议简介 介绍了网络分层: • 网络层 • 数据链路层 • 网络层 • 传输层 • 应用层 2.HTTP协议详解 介绍应用层的HTTP协议,是Asp.net开发人员必须掌握的协议 • HTTP请求 • HTTP响应 3.自定义Web服务器 介绍利用Socket自制一个Web服务器来响应浏览器发出的请求 • 实现一个简单的Web服务器 4.自定义Web浏览器 介绍利用WebBrowser控件实现自定义的Web浏览器 • 浏览器的组成 • WebBrowser控件的介绍 • 制作一个Web浏

解决vs2013中和网络编程相关的问题

最近在学网络编程,发现项目编译时有的时候有问题,今天总算解决了。