面向对象程序设计第四次实验课——socket初试

题目大意是使用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) 主要是照猫画虎,借鉴的成分比较多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值