1 服务器端
由先得专门为服务器端做一个Socket通信类CNewSocket类,此类继承CAsyncSocket类,专门负责服务器端socket通信事情:
NewSocket.h:
#pragma once
#include "afxsock.h"
//此类专门用来与客户端进行socket通信
class CNewSocket :
public CAsyncSocket
{
public:
CNewSocket(void);
~CNewSocket(void);
virtual void OnReceive(int nErrorCode);
virtual void OnSend(int nErrorCode);
// 消息长度
UINT m_nLength;
//消息缓冲区
char m_szBuffer[4096];
};
对应实现代码如下:
NewSocket.cpp:
#include "StdAfx.h"
#include "NewSocket.h"
CNewSocket::CNewSocket(void)
: m_nLength(0)
{
memset(m_szBuffer,0,sizeof(m_szBuffer));
}
CNewSocket::~CNewSocket(void)
{
if(m_hSocket !=INVALID_SOCKET)
{
Close();
}
}
void CNewSocket::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
m_nLength =Receive(m_szBuffer,sizeof(m_szBuffer),0); //接收数据
m_szBuffer[m_nLength] ='\0';
//接收消息后就开始给客户端发相同的消息
AsyncSelect(FD_WRITE); //触发OnSend函数
CAsyncSocket::OnReceive(nErrorCode);
}
void CNewSocket::OnSend(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
char m_sendBuf[4096]; //消息缓冲区
strcpy(m_sendBuf,"server send:");
strcat(m_sendBuf,m_szBuffer);
Send(m_sendBuf,strlen(m_sendBuf));
//触发OnReceive函数
AsyncSelect(FD_READ);
CAsyncSocket::OnSend(nErrorCode);
}
如上,NewSocket类重载了CAsyncSocket类的接收与发送事件处理例程,一旦被触发了发送或接收事件,将调用此对应函数.
接下来服务器端在做一个专门类CListenSocket类,用来监听来自客户端的连接请求,如下:
ListenSocket.h:
#pragma once
#include "afxsock.h"
#include "NewSocket.h"
class CListenSocket :
public CAsyncSocket
{
public:
CListenSocket(void);
~CListenSocket(void);
CNewSocket *m_pSocket; //指向一个连接的socket对象
virtual void OnAccept(int nErrorCode);
};
由上可知,CListenSocket类还是继承了CAsyncSocket类,并重载了其接受事件,且包含了一个由之前定义好的CNewSocket类指针做为成员,用来指向与客户端连接好的连接.
其实现代码如下:
ListenSocket.cpp:
#include "StdAfx.h"
#include "ListenSocket.h"
CListenSocket::CListenSocket(void)
{
}
CListenSocket::~CListenSocket(void)
{
}
void CListenSocket::OnAccept(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
CNewSocket *pSocket =new CNewSocket();
if(Accept(*pSocket))
{
pSocket->AsyncSelect(FD_READ); //触发通信socket的Read函数读数据
m_pSocket =pSocket;
}
else
{
delete pSocket;
}
CAsyncSocket::OnAccept(nErrorCode);
}
这个OnAccept事件触发是在Listen之后,如果有客户端尝试连接时触发,且先看后续内容.
接下来就是对话框的代码了:
SocketServerDlg.h:
// SocketServerDlg.h : header file
//
#pragma once
#include "ListenSocket.h"
// CSocketServerDlg dialog
class CSocketServerDlg : public CDialogEx
{
// Construction
public:
CSocketServerDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_SOCKETSERVER_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
CListenSocket m_srvrSocket; //监听套接字
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedBtStart();
};
在这个CSocketServerDlg类的声明内包含了一个CListenSocket成员m_srvrSocket,并有一个start按键.
// SocketServerDlg.cpp : implementation file
//
#include "stdafx.h"
#include "SocketServer.h"
#include "SocketServerDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// Dialog Data
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CSocketServerDlg dialog
CSocketServerDlg::CSocketServerDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CSocketServerDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CSocketServerDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CSocketServerDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BT_START, &CSocketServerDlg::OnBnClickedBtStart)
END_MESSAGE_MAP()
// CSocketServerDlg message handlers
BOOL CSocketServerDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
void CSocketServerDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CSocketServerDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CSocketServerDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CSocketServerDlg::OnBnClickedBtStart()
{
// TODO: Add your control notification handler code here
if(m_srvrSocket.m_hSocket ==INVALID_SOCKET)
{
//创建监听套接字,激发FD_ACCEPT事件,默认端口7190
BOOL bFlag =m_srvrSocket.Create(7190,SOCK_STREAM,FD_ACCEPT); //第三个参数表示m_srvrSocket只对FD_ACCEPT事件感兴趣
if(!bFlag)
{
AfxMessageBox(_T("Socket创建失败!"));
m_srvrSocket.Close();
PostQuitMessage(0);
return;
}
GetDlgItem(IDC_BT_START)->EnableWindow(FALSE);
//监听成功,等待连接请求
if(!m_srvrSocket.Listen())//如果监听失败
{
int nErrorCode =m_srvrSocket.GetLastError(); //检测错误信息
if(nErrorCode !=WSAEWOULDBLOCK) //如果不是线程阻塞
{
AfxMessageBox(_T("Socket错误!"));
m_srvrSocket.Close();
PostQuitMessage(0);
return;
}
}
}
}
当用户按下start按键时,程序首先调用m_srvrSocket的Create函数创建一个socket,然后调用它的Listen函数监听客户端的连接请求,一旦客户端尝试连接服务器,那将触发m_srvrSocket的FD_ACCEPT事件.接着在m_srvrSocket的OnAccept函数内,将创建一个CNewSocket对象,并利用此对象来操作socket收发,到此,服务器端的代码完.
2 客户端
这里的客户端的功能就是与服务器建立连接,交用户的数据发送给服务器,并显示来自服务器的接收数据.
与服务器类似,首先做一个专门用于socket通信的类ClientSocket类:
ClientSocket.h:
#pragma once
#include "afxsock.h"
class ClientSocket :
public CAsyncSocket
{
public:
ClientSocket(void);
~ClientSocket(void);
// 是否连接
bool m_bConnected;
// 消息长度
UINT m_nLength;
//消息缓冲区
char m_szBuffer[5096];
virtual void OnConnect(int nErrorCode);
virtual void OnReceive(int nErrorCode);
virtual void OnSend(int nErrorCode);
};
由上类声明可知,此类重载了CAsyncSocket的连接,接收,发送事件例程.其实现代码如下:
#include "StdAfx.h"
#include "ClientSocket.h"
#include "SocketTest.h"
#include "SocketTestDlg.h"
ClientSocket::ClientSocket(void)
: m_bConnected(false)
, m_nLength(0)
{
memset(m_szBuffer,0,sizeof(m_szBuffer));
}
ClientSocket::~ClientSocket(void)
{
if(m_hSocket !=INVALID_SOCKET)
{
Close();
}
}
void ClientSocket::OnConnect(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
//连接成功
if(nErrorCode ==0)
{
m_bConnected =TRUE;
//获取主程序句柄
CSocketTestApp *pApp =(CSocketTestApp *)AfxGetApp();
//获取主窗口
CSocketTestDlg *pDlg =(CSocketTestDlg *)pApp->m_pMainWnd;
//在主窗口输出区显示结果
CString strTextOut;
strTextOut.Format(_T("already connect to "));
strTextOut +=pDlg->m_Address;
strTextOut += _T(" 端口号:");
CString str;
str.Format(_T("%d"),pDlg->m_Port);
strTextOut +=str;
pDlg->m_MsgR.InsertString(0,strTextOut);
//激活一个网络读取事件,准备接收
AsyncSelect(FD_READ);
}
CAsyncSocket::OnConnect(nErrorCode);
}
void ClientSocket::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
//获取socket数据
m_nLength =Receive(m_szBuffer,sizeof(m_szBuffer));
//获取主程序句柄
CSocketTestApp *pApp =(CSocketTestApp *)AfxGetApp();
//获取主窗口
CSocketTestDlg *pDlg =(CSocketTestDlg *)pApp->m_pMainWnd;
CString strTextOut(m_szBuffer);
//在主窗口的显示区显示接收到的socket数据
pDlg->m_MsgR.InsertString(0,strTextOut);
memset(m_szBuffer,0,sizeof(m_szBuffer));
CAsyncSocket::OnReceive(nErrorCode);
}
void ClientSocket::OnSend(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
//发送数据
Send(m_szBuffer,m_nLength,0);
m_nLength =0;
memset(m_szBuffer,0,sizeof(m_szBuffer));
//继续提请一个读的网络事件,接收socket消息
AsyncSelect(FD_READ);
CAsyncSocket::OnSend(nErrorCode);
}
接下来就是对话框代码了:
SocketTestDlg.h:
// SocketTestDlg.h : header file
//
#pragma once
#include "afxwin.h"
#include "ClientSocket.h"
// CSocketTestDlg dialog
class CSocketTestDlg : public CDialogEx
{
// Construction
public:
CSocketTestDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_SOCKETTEST_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
// 服务器IP地址
CString m_Address;
// 服务器端口号
int m_Port;
// 消息接收显示控件
CListBox m_MsgR;
afx_msg void OnBnClickedBtClear();
afx_msg void OnBnClickedOk();
// 用户输入的即将发送的内容
CString m_MsgS;
afx_msg void OnBnClickedBtConnect();
afx_msg void OnTimer(UINT_PTR nIDEvent);
// 连接服务器次数
int m_nTryTimes;
ClientSocket m_ClientSocket;
afx_msg void OnBnClickedBtSend();
afx_msg void OnBnClickedBtClose();
};
其实现代码如下:
// SocketTestDlg.cpp : implementation file
//
#include "stdafx.h"
#include "SocketTest.h"
#include "SocketTestDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// Dialog Data
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CSocketTestDlg dialog
CSocketTestDlg::CSocketTestDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CSocketTestDlg::IDD, pParent)
, m_Address(_T("127.0.0.1"))
, m_Port(0)
, m_MsgS(_T(""))
, m_nTryTimes(0)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CSocketTestDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_ET_IPADDRESS, m_Address);
DDX_Text(pDX, IDC_ET_PORT, m_Port);
DDX_Control(pDX, IDC_LIST_R, m_MsgR);
DDX_Text(pDX, IDC_ET_SEND, m_MsgS);
}
BEGIN_MESSAGE_MAP(CSocketTestDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BT_CLEAR, &CSocketTestDlg::OnBnClickedBtClear)
ON_BN_CLICKED(IDOK, &CSocketTestDlg::OnBnClickedOk)
ON_BN_CLICKED(IDC_BT_CONNECT, &CSocketTestDlg::OnBnClickedBtConnect)
ON_WM_TIMER()
ON_BN_CLICKED(IDC_BT_SEND, &CSocketTestDlg::OnBnClickedBtSend)
ON_BN_CLICKED(IDC_BT_CLOSE, &CSocketTestDlg::OnBnClickedBtClose)
END_MESSAGE_MAP()
// CSocketTestDlg message handlers
BOOL CSocketTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
void CSocketTestDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CSocketTestDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CSocketTestDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CSocketTestDlg::OnBnClickedBtClear()
{
// TODO: Add your control notification handler code here
m_MsgR.ResetContent();
}
void CSocketTestDlg::OnBnClickedOk()
{
// TODO: Add your control notification handler code here
/*CDialogEx::OnOK();*/
UpdateData(TRUE);
if(m_MsgS.IsEmpty())
{
AfxMessageBox(_T("please type the message you want to send!"));
return;
}
}
void CSocketTestDlg::OnBnClickedBtConnect()
{
// TODO: Add your control notification handler code here
//如果当前已经与服务器建立了连接,则直接返回
if(m_ClientSocket.m_bConnected)
{
AfxMessageBox(_T("当前已经与服务器建立连接"));
return;
}
UpdateData(TRUE);
if(m_Address.IsEmpty())
{
AfxMessageBox(_T("服务器的IP地址不能为空!"));
return;
}
if(m_Port <=1024)
{
AfxMessageBox(_T("服务器的端口设置非法!"));
return;
}
//使Connect按键失能
GetDlgItem(IDC_BT_CONNECT)->EnableWindow(FALSE);
//启动连接定时器,每1秒中尝试一次连接
SetTimer(1,1000,NULL);
}
void CSocketTestDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: Add your message handler code here and/or call default
if(m_ClientSocket.m_hSocket ==INVALID_SOCKET)
{
BOOL bFlag =m_ClientSocket.Create(0,SOCK_STREAM,FD_CONNECT); //创建套接字
if(!bFlag)
{
AfxMessageBox(_T("Socket创建失败!"));
m_ClientSocket.Close();
PostQuitMessage(0);//退出
return;
}
}
m_ClientSocket.Connect(m_Address,m_Port); //连接服务器
if(m_nTryTimes >=10)
{
KillTimer(1);
AfxMessageBox(_T("连接失败!"));
GetDlgItem(IDC_BT_CONNECT)->EnableWindow(TRUE);
return;
}
else if(m_ClientSocket.m_bConnected)
{
KillTimer(1);
GetDlgItem(IDC_BT_CONNECT)->EnableWindow(TRUE);
return;
}
CString strTextOut =_T("尝试连接服务器第");
m_nTryTimes ++;
CString str;
str.Format(_T("%d"),m_nTryTimes);
strTextOut +=str;
strTextOut +=_T("次...");
m_MsgR.InsertString(0,strTextOut);
CDialogEx::OnTimer(nIDEvent);
}
void CSocketTestDlg::OnBnClickedBtSend()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
if(m_ClientSocket.m_bConnected)
{
//将用户输入的数据从CString转化为char *字符串,即Unicode-->ANSI,并保存到m_ClientSocket.m_szBuffer中
int len =WideCharToMultiByte(CP_ACP,0,m_MsgS,-1,NULL,0,NULL,NULL);
WideCharToMultiByte(CP_ACP,0,m_MsgS,-1,m_ClientSocket.m_szBuffer,len,NULL,NULL );
m_ClientSocket.m_nLength =strlen(m_ClientSocket.m_szBuffer);
m_ClientSocket.AsyncSelect(FD_WRITE);//触发写事件
m_MsgS =_T("");
UpdateData(FALSE);
}
}
void CSocketTestDlg::OnBnClickedBtClose()
{
// TODO: Add your control notification handler code here
m_ClientSocket.ShutDown();
EndDialog(0);
}
OK,完!