MFC socket 编程
一,序言
大四了,这学期也没有课,实在无聊,这整天在寝室里面待着也实在无聊啊,于是就想起
了实现一个网络的五子棋,
也算四对自己在 VC++编程的一个熟练,同时以前对 socket 编程不是很了解,也可以通过这
个机会学习加深.因为在编写
过程中自己遇到了一些问题,也通过了网络和书籍自己解决了,最后也想通过这篇文章和
大家分享一些经验,希望对初
学 socket 编程的人有帮助.
二,五子棋设计
我们实在 MFC 中用单文档来完成我们的程序.
(1),我们定义一个二维数组来存储棋子:Node[20][20],并且初始化全部为 0,1 表
示是黑棋,-1 表示是白棋,0 表示没有下棋.
(2),定义一个类:MySocket,该类继承于 CSocket,用来实现服务器和客户端的通信
(3),还有其他一些变量,在开发过程需要在添加
三,具体实现
下面我们就一步一步的实现
1,建立工程
启动 Visual C++6.0,在 IDE 中建立一个单文档的 MFC 工程(具体怎么实现,相信不
大家都知道吧!),但是要记住在建工程的时候
一定要选择 Windows Socket ,假设我们建的工程名为:FiveNodeChess
2,完成窗口
在 ResourceView 中的 Dialog 中右键->插入 dialog,然后右键创建的 Dialog->建
立类向导->create new class,确定,为新建的 dialog
创建一个类,这个 Dialog 将是我们的登陆窗口,类名:LandDialog,如图(图-3)
然后在我们的 Dialog 上加上我们要用到的控件,如图(图-4),其中:
comb box 控件是选择作为服务器还是客户端用的------m_combox
Edit 控件是为输入 ip 地址用的----------------------m_ipEdit
然 后 在 类 向 导 中 为 comb box 控 件 的 CBN_SELENDCANCLE 添 加 一 个 处 理 函
数:OnSelchangeComboType(),并向名:LandDialog 类中添加一个
bool 变量 tempStaute,用来保存选择服务器还是客户端的状态,OnSelchangeComboType()
函数添加代码如下:
void LandDialog::OnSelchangeComboType()
{
// TODO: Add your control notification handler code here
UpdateData(TRUE);
if(m_combox.GetCurSel()==1)
{
tempStaute=true;
}
else
{
}
}
tempStaute=false;
接着就是添加一些变量用来保存是服务器还是客户端,和 IP 地址,并将这些变量声
明成 static,同是将类 LandDialog 声明成 CFiveNodeChessView 类的友元类,方便后面使
用变量. 后面就是将 dialog 类在单文档中创建对象为登陆窗口.
3,创建一个 MySocket 类并继承于 CSocket 类,下面介绍在单文档中 socket 使用一般
步骤:
(1)、假定你的工程名叫 CA
(2)、在 CA.CPP 文件中加入
#include
!AfxSocketInit()
CCAApp::InitInstance()
(3)、在 CAApp::InitInstance 中加入:
BOOL
{
if(
{
AfxMessageBox("IDP_SOCKETS_INIT_FAILED");
return
}
FALSE;
)
…………
}
CMySocket:public
(4)、增加一个 CMySocket 类,并且从 CSocket 类中派生
class
{
………………
};
CSocket
(5)、在 CMySocket 中增加变量:
CCAView
注 意 : 你 在 添 加 *pView 时 IDE 会 自 动 给 你 在 CMySocket.h 中 添 加 :#include
*pView;
"CCAView.h" ,将它删除
CCAView;
CMySocket:
并在 CMySocket 定义之前加入
class
class
{
…………………………………………………………
}
CSocket
public
并且在 CMySocket.cpp 中加入#include "CCAView.h"
(6)、在 CCAView 中加入变量 CMySocket
m_socket; 并在 CCAView.h 和 CCAView.cpp
中加入#include "CCADoc.h"
(7)、在 CCAView::InitUpdate 中初始化 m_socket
m_socket.pView
m_socket.Create(port,SOCK_DGRAM);
this;
=
(8)、在 CCAView 中添加成员函数:GetData()和 SendData()
(9)、在 CMySocket 中加入虚函数【用 ClassWazard 添加】
void
{
CCAView::OnReceive(int
nErrorCode)
pView->GetData();
}
(10)、在 CCAView 的 GetData()中添加:
{
m_socket.ReceiveFrom(………………)
MessageBox(..............);
}
(11)、在 CCAView::SendData()中添加:
{
SendTo(………………);
}
以上时在单文档中使用 socket 的一般步骤.
4,完成 view 中各种出了函数:
(1),添加 create 消息处理
//添加的一个 create 消息句柄
int CFiveNodeChessView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
//实现各种图片的载入
CClientDC dc(this);
m_dcMemo->CreateCompatibleDC(&dc);
m_dcTemp->CreateCompatibleDC(&dc);
CRect rect;
GetClientRect(&rect);
m_bmpMemo->CreateCompatibleBitmap(&dc,/*rect.Width(),rect.Height()*/1024,768)
;
m_dcMemo->SelectObject(m_bmpMemo);
m_bmpMap->m_hObject=::LoadImage(NULL,"res\\bkground.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
m_bmpBlack->m_hObject=::LoadImage(NULL,"res\\black.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
m_bmpWhite->m_hObject=::LoadImage(NULL,"res\\white.bmp",
IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
DrawChessBoard();
return 0;
}
(2),画棋盘函数
//实现画棋盘的功能
void CFiveNodeChessView::DrawChessBoard()
{
int i,j;
//绘制背景
BITMAP bmp;
m_bmpMap->GetBitmap(&bmp);
m_dcTemp->SelectObject(m_bmpMap);
int screen_x=::GetSystemMetrics(SM_CXSCREEN);
int screen_y=::GetSystemMetrics(SM_CYSCREEN);
for (i=0; i<=screen_x/bmp.bmWidth; i++)
{
for (j=0; j<=screen_y/bmp.bmHeight; j++)
{
m_dcMemo->BitBlt(i*bmp.bmWidth,j*bmp.bmHeight,bmp.bmWidth,bmp.bmHeight,
m_dcTemp,0,0,SRCCOPY);// 把
临时 DC 和内存 DC 相关联起来
}
}
//绘制棋盘
//m_nWidth+=20;
for(i=1;i<=20;i++)
{
for(j=1;j<=20;j++)
{
m_dcMemo->MoveTo(m_nWidth,m_nWidth*i);
m_dcMemo->LineTo(m_nWidth*20,m_nWidth*i);
m_dcMemo->MoveTo(m_nWidth*i,m_nWidth);
m_dcMemo->LineTo(m_nWidth*i,m_nWidth*20);
}
}
}
(3),画棋子函数
//实现画棋子的功能
void CFiveNodeChessView::DrawChess()
{
//绘制棋子
DrawChessBoard();
int i,j;
//CFiveNodeChessDoc *pDoc=GetDocument();
BITMAP bmp;
for(i=0;i<20;i++)
{
for(j=0;j<20;j++)
{
CRect rect=CRect((i+1)*m_nWidth-14,(j+1)*m_nWidth-14,
(i+1)*m_nWidth+14,(j+1)*m_nWidth+14);
if(Node[i][j]!=0)
{
if(Node[i][j]==1)
{
m_bmpBlack->GetBitmap(&bmp);
m_dcTemp -> SelectObject(m_bmpBlack);
::TransparentBlt(m_dcMemo->m_hDC,rect.left,
rect.top,rect.Width(),rect.Height(),
m_dcTemp->m_hDC,0,0,bmp.bmWidth,bmp.bmHeight,
msimg32.lib
RGB(255,0,0));
//TransparentBlt 使用需要中工程连接中加入
}
else if(Node[i][j]==-1)
{
m_bmpWhite->GetBitmap(&bmp);
m_dcTemp -> SelectObject(m_bmpWhite);
::TransparentBlt(m_dcMemo->m_hDC,rect.left,
rect.top,rect.Width(),rect.Height(),
m_dcTemp->m_hDC,0,0,bmp.bmWidth,bmp.bmHeight,
RGB(255,0,0));
}
}
else//显示背景即可,必须画透明,否则悔棋时,棋子依然在上面
{
//m_dcTemp->SetPixel(0,0,RGB(255,0,0));
CBitmap bitmap;
bitmap.CreateBitmap(1,1,0,0,NULL);
m_dcTemp->SelectObject(&bitmap);
m_dcMemo->BitBlt(0,0,1,1,m_dcTemp,0,0,SRCCOPY);
}
}
}
}
(4),鼠标点击下棋处理函数
void CFiveNodeChessView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//MessageBox(IPAddress);
//MessageBox(LandDialog::IP);
int nDraw_x,nDraw_y;
if (DrawOrWait==true) {
if(point.x>15 && point.x<615 && point.y>15 && point.y<615)
{
nDraw_x=(point.x+m_nWidth/2)/m_nWidth-1;
nDraw_y=(point.y+m_nWidth/2)/m_nWidth-1;
}
else
{
}
return;
if(Node[nDraw_x][nDraw_y]!=0)
{
MessageBox("此处已经有棋子,请重新下棋子");
return;
}
else
{
if (LandDialog::staute==true) {
Node[nDraw_x][nDraw_y]=1;
}
else if (LandDialog::staute==false) {
Node[nDraw_x][nDraw_y]=-1;
}
}
PlaySound("res\\sound1.wav",NULL,SND_ASYNC);
DrawChess();
SendMSG(nDraw_x,nDraw_y);
//刷新,保存棋子信息
CRect rect=CRect((nDraw_x+1)*m_nWidth-14,
(nDraw_y+1)*m_nWidth-14,
(nDraw_x+1)*m_nWidth+14,
(nDraw_y+1)*m_nWidth+14);
//Invalidate();//对屏幕进行刷新(清除原图形)和重绘
InvalidateRect(&rect);
UpdateWindow();
Judgement();
DrawOrWait=false;
}
CView::OnLButtonDown(nFlags, point);
}
(5)网络 socket 处理函数
/*************************** 下 面 实 现 对 网 络 通 信 功 能 , 即
CSocket*************************/
//该函数用于服务器接受请求
void CFiveNodeChessView::OnAccept()
{
m_ListenSocket.Accept(m_ConnectSocket);
MessageBox("已经建立连接!");
}
//用于关闭连接的套节字
void CFiveNodeChessView::OnClose()
{
m_ConnectSocket.Close();
}
void CFiveNodeChessView::OnReceive()
{
char *pBuff=new char[10];
int nBufSize=8;
int nReceive;
CString RecieveStr;
CString nstr_x,nstr_y,nValue_str;
int nDraw_x,nDraw_y,nValue;//保存接受的坐标和值
//接收消息
if(DrawOrWait==false){
nReceive=m_ConnectSocket.Receive(pBuff,nBufSize);
//判断接受是否成功
if (nReceive!=SOCKET_ERROR) {
//仅保留消息有效部分
pBuff[nReceive]=NULL;
//将消息转化为 CString 对象
RecieveStr=pBuff;
//MessageBox(RecieveStr);
//接收刀信息就重新画棋子
/*********下面实现重新画棋子的功能********/
//将接收到的字符串先处理
int num1,num2;
num1=RecieveStr.Find("_",0);
nstr_x=RecieveStr.Mid(0,num1);
num2=RecieveStr.Find("_",num1+1);
nstr_y=RecieveStr.Mid(num1+1,num2);
nValue_str=RecieveStr.Mid(num2+1,RecieveStr.GetLength()-1);
nDraw_x=atoi(nstr_x.GetBuffer(nstr_x.GetLength()));
nDraw_y=atoi(nstr_y.GetBuffer(nstr_y.GetLength()));
nValue=atoi(nValue_str.GetBuffer(nValue_str.GetLength()));
Node[nDraw_x][nDraw_y]=nValue;
//重新画棋子
PlaySound("res\\sound1.wav",NULL,SND_ASYNC);
DrawChess();
//刷新,保存棋子信息
CRect rect=CRect((nDraw_x+1)*m_nWidth-14,
(nDraw_y+1)*m_nWidth-14,
(nDraw_x+1)*m_nWidth+14,
(nDraw_y+1)*m_nWidth+14);
//Invalidate();//对屏幕进行刷新(清除原图形)和重绘
InvalidateRect(&rect);
UpdateWindow();
Judgement();
DrawOrWait=true;
AfxMessageBox("信息接收错误!",MB_OK|MB_ICONSTOP);
}
else
{
}
}
}
void CFiveNodeChessView::OnConnect()
{
}
//实现下棋发送消息函数
void CFiveNodeChessView::SendMSG(int x_width,int y_hight)
{
CString sendMSG;
int nSent;//已经发送消息长度
CString tempstr;
//对信息进行字符串化
//结构为:衡坐标_竖坐标_值
sendMSG.Format("%d",x_width);
sendMSG +="_";
tempstr.Format("%d",y_hight);
sendMSG += tempstr;