题目大意是使用socket实现一个聊天程序。
server
//
#include "stdafx.h"
#include "server.h"
#include "serverDlg.h"
#include <cstring>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// serverDlg dialog
serverDlg::serverDlg(CWnd* pParent /*=NULL*/)
: CDialog(serverDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(serverDlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
n=0;
}
void serverDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(serverDlg)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(serverDlg, CDialog)
//{{AFX_MSG_MAP(serverDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_SEND, OnButtonSend)
ON_MESSAGE(WM_SOCKET,OnSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/
// serverDlg message handlers
BOOL serverDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 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
::WSAStartup(MAKEWORD(2, 2), &m_wsaData);
std::memset(&addr, 0, sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=::htons(1234);
addr.sin_addr.s_addr=::inet_addr("127.0.0.1");
m_socket=::socket(AF_INET,SOCK_STREAM,0);
::bind(m_socket,(sockaddr*)&addr,sizeof(addr));
::listen(m_socket,5);
::WSAAsyncSelect(m_socket,this->m_hWnd,WM_SOCKET,FD_ACCEPT|FD_READ);
return TRUE; // return TRUE unless you set the focus to a control
}
// 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 serverDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (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
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR serverDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
void serverDlg::OnButtonSend()
{
CString str="";
GetDlgItemText(IDC_EDIT_SEND_MESSAGE,str);
if(str=="")
{
MessageBox("消息不能为空!");
return;
}
CString log;
GetDlgItemText(IDC_EDIT_LOG_MESSAGE,log);
log+="\r\n";
char *buff=str.GetBuffer(1);
if(::send(m_socket2,buff,strlen(buff),0)!=SOCKET_ERROR)
{
log+="我说: ";
log+=str;
}
else
log+="消息发送失败";
SetDlgItemText(IDC_EDIT_LOG_MESSAGE,log);
SetDlgItemText(IDC_EDIT_SEND_MESSAGE, "");
}
LRESULT serverDlg::OnSocket(WPARAM wParam,LPARAM lParam)
{
CString str;
char cs[100]={0};
switch(WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
{
int len=sizeof(addr2);
m_socket2=::accept(m_socket,(sockaddr*)&addr2,&len);
n++;//客户端数量
str.Format("%d",n);
SetDlgItemText(IDC_CLIENT_NUM,str);
GetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
str+="\r\n";
str+=::inet_ntoa(addr2.sin_addr);
str+=" 登陆\r\n";
}
break;
case FD_READ:
{
::recv(m_socket2,cs,100,0);
GetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
str+="\r\n";
str+=::inet_ntoa(addr2.sin_addr);
str+="对您说: ";
str+=cs;
}
break;
}
SetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
return true;
}
client
//
#include "stdafx.h"
#include "client.h"
#include "clientDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// clientDlg dialog
clientDlg::clientDlg(CWnd* pParent /*=NULL*/)
: CDialog(clientDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(clientDlg)
m_port = 0;
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void clientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(clientDlg)
DDX_Control(pDX, IDC_SERVER_IPADDRESS, m_iPaddr);
DDX_Text(pDX, IDC_SERVER_PORT, m_port);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(clientDlg, CDialog)
//{{AFX_MSG_MAP(clientDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_SEND, OnButtonSend)
ON_BN_CLICKED(IDC_BUTTON_CONNECT, OnButtonConnect)
ON_MESSAGE(WM_SOCKET,OnSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/
// clientDlg message handlers
BOOL clientDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 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
GetDlgItem(IDC_EDIT_SEND_MESSAGE)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(false);
m_socket=::socket(AF_INET,SOCK_STREAM,0);
::WSAAsyncSelect(m_socket,this->m_hWnd,WM_SOCKET,FD_READ);
return TRUE; // return TRUE unless you set the focus to a control
}
// 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 clientDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (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
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR clientDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
void clientDlg::OnButtonSend()
{
CString msg;
GetDlgItemText(IDC_EDIT_SEND_MESSAGE,msg);
if(msg=="")
{
MessageBox("发送消息不能为空!");
return;
}
char *buff=msg.GetBuffer(1);
::send(m_socket,buff,strlen(buff),0);
CString str;
GetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
str+="\r\n";
str+="我说:";
str+=msg;
SetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
SetDlgItemText(IDC_EDIT_SEND_MESSAGE,"");
}
void clientDlg::OnButtonConnect()
{
UpdateData(TRUE);
if(0!=m_iPaddr.IsBlank())
{
MessageBox("请输入正确的服务器IP地址");
return;
}
BYTE nf1,nf2,nf3,nf4;
m_iPaddr.GetAddress(nf1,nf2,nf3,nf4);
CString ipStr;
ipStr.Format("%d.%d.%d.%d",nf1,nf2,nf3,nf4);//这里的nf得到的值是IP值了.
addr.sin_family=AF_INET;
addr.sin_port=htons(m_port);
addr.sin_addr.s_addr=inet_addr(ipStr.GetBuffer(1));
CString str;
GetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
if(::connect(m_socket,(sockaddr*)&addr,sizeof(addr)))
{
str+="连接服务器成功!\r\n";
GetDlgItem(IDC_EDIT_SEND_MESSAGE)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_SEND)->EnableWindow(true);
GetDlgItem(IDC_SERVER_IPADDRESS)->EnableWindow(false);
GetDlgItem(IDC_SERVER_PORT)->EnableWindow(false);
}
else
{
str+="连接服务器失败!请稍后再试......\r\n";
}
SetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
}
LRESULT clientDlg::OnSocket(WPARAM wParam,LPARAM lParam)
{
char cs[100]={0};
if(lParam==FD_READ)
{
CString str="";
recv(m_socket,cs,100,0);
GetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
str+="\r\n服务器说:";
str+=(LPTSTR)cs;
SetDlgItemText(IDC_EDIT_LOG_MESSAGE,str);
}
return true;
}
第四次实验报告
一、 题目分析
实现类QQ通信功能:我的理解是,就是利用最基本的socket编程实现简单的网络间通讯。
1. 实现基本socket连接
这一条如题,需要写两个工程,一个server,一个client。
2. 实现收、发同步进行
这一条就要求使用异步socket而不是同步socket了。
3. 建立MFC简单界面
使用MFC类库,创建界面。
二、 流程分析
1. server:
server项目是服务器端。
其中最重要的一个类是serverDlg,继承自CDialog,主要负责窗口初始化和窗口消息的相应,也是实现通讯的类。其他的类都是由IDE自动生成的。
重要函数:
virtual BOOL OnInitDialog()
afx_msg LRESULT OnSocket(WPARAM wParam,LPARAM lParam);
afx_msg void OnButtonSend();
OnInitDialog()负责初始化,即初始化套接字,设置服务器ip和端口,绑定套接字端口号,并开启监听模式。最重要的是调用了WSAAsyncSelect()函数,将套接字设置成非阻塞模式,并将网络事件转化成窗口消息。
OnButtonSend()函数是处理窗口中单击发送按钮事件的函数。
OnSocket()函数是消息处理函数,负责处理WSAAsyncSelect()中的窗口信息。
2. client
client项目是客户端。
与server相近,最重要的一个类是clinetDlg,继承自CDialog,负责与服务器端通讯。
virtual BOOL OnInitDialog();
afx_msg void OnButtonSend();
afx_msg void OnButtonConnect();
afx_msg LRESULT OnSocket(WPARAM wParam,LPARAM lParam);
同样的,初始化函数负责创建初始化套接字,并调用WSAAsyncSelect()函数,OnButtonConnect()负责根据ip地址链接服务器,OnButtonSend()负责发送信息,OnSocket负责接收网络消息。
三、程序难点
1.入门socket编程和MFC界面编写。
Socket编程和MFC都是我以前从来没有接触过的东西,所以当两样东西碰到一起并且需要综合使用的时候,我就有些懵了。由于没有计算机网络这门课的基础,所以一开始接触socket的时候一些概念并不是十分明白,不过我找了几篇blog,这些blog讲的都非常好,让我基本理解了ip port socket 阻塞 粘包 三次握手四次握手等概念,虽然细节并不清除,但这也并不影响写代码了。
另一个比较头疼的事情就是MFC了,MFC机制我完全不了解,上网搜的一些东西又似乎对新手不够友好,而对新手友好的东西却又太罗嗦,比如同学在班群分享了一个视频,长达130min,我看了10min就看不下去了。于是,我在网上找了一个MFC的通信类程序,单步调试打log,比照在VS上创建的MFC程序的IDE自己补充的代码,不懂的地方就百度,最终明白了一些比较小白的问题,并且顺带解决了阻塞的问题。
2.VS与VC在WSAAsyncSelect()上的少许差异
最开始,我并不明白WSAAsyncSelect()是做什么的,在我编译一份网上的原码的时候,VS报错了:
“error C2440: “static_cast”: 无法从“void (__thiscall CTCPDlg::* )(WPARAM,LPARAM)”转换为“LRESULT (__thiscall CWnd::* )(WPARAM,LPARAM)” 解决问题”
对应的原码:
BEGIN_MESSAGE_MAP(clientDlg, CDialog)
//{{AFX_MSG_MAP(clientDlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_SEND, OnButtonSend)
ON_BN_CLICKED(IDC_BUTTON_CONNECT, OnButtonConnect)
ON_MESSAGE(WM_SOCKET,OnSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
错误就是在ON_MESSAGE(WM_SOCKET,OnSocket)这里报的。
我一开始为了关注MFC,把这一句注释掉了,最后在我搞懂了MFC,动手敲的时候,我就把这里的事情给忘记了。
结果写好了之后发现,server和client之间根本不能通信!这让我很惊愕。找了半天,最后在搜WSAAsyncSelect()的时候,在一篇blog上找到了:
在MFC编程环境中,使用该消息处理函数
BEGIN_MESSAGE_MAP(CXXXDlg, CDialog)
//{{AFX_MSG_MAP(CXXXDlg)
........
//}}AFX_MSG_MAP
ON_MESSAGE(WM_USER_SERVER, OnServerMsg)
........
END_MESSAGE_MAP()
处理消息函数声明为:
afx_msg void OnSERVERMsg(WPARAM wparam,LPARAM lParam);
我才明白,这里的这一段是把处理网络事件转换成的消息交给了OnServerMsg这个函数处理,所以这一段肯定是不能注释的。要是那个函数根本没有入口,又没有任何关联,怎么能处理消息呢?
但是问题还是没有解决,去掉注释后编译器还是会报错的,我又搜了一下这个错误,发现这是由于VC和VS之间的不同引起的,在VC中,这个函数返回的是void,而在VS中,这个函数返回的是LRESULT。
四、 优点与缺点
优点:
a) 实现了网络间的无阻塞的通讯
b) 搞定了MFC界面
c) 界面比较友好
缺点:
a) 主机的ip和端口固定在了代码中,没有实现输入
b) 使用的函数还是C语言中的那一套,并没有用MFC封装好的类实现
c) 主要是照猫画虎,借鉴的成分比较多。