基于MFC平台实现SOCKET通信

一、就先谈谈MFC

  MFC是Microsoft Foundation Classes的缩写,是由微软公司提供的一个类库,这是来帮助你完成图形界面的设计和软件平台的开发,但是不管是你软件的开发还是图形界面的设计都都不支持各位去用MFC,看了很多大牛博客都会教导你这个东西操作繁杂,而且不易入门,可以说当时选择在这个平台上做这个软件是一个错误。就我所了解的到现今更加倾向于Qt,也是各种大牛给我的最一致的软件。

二、实现通信我们就必须了解一下什么是SOCKET?

  Socket也成为套接字,套接字是支持TCP/IP网络通信的基本操作单元。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket)的接口。

 

上面便是采用SOCKET实现通信的大致流程,当然计算机内部的实现肯不是这么简单,其中会涉及到TCP握手的建立连接和断开连接等等。

三、了解到了SOCKET就必须了解一下SOCKET通信的基本操作:

1.socket()函数

2.bind()函数

3.listrn()函数,connect()函数

4.accept()函数

5.read()函数、write()函数、receive()函数等

6.close()函数

7.TCP三次握手建立连接

8.TCP四次握手释放连接

3.1、socket()函数

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket()描述符,它唯一标识一个socket.这个socket描述字跟文件描述字一样,后续操作都有用它来进行一些读写操作。

3.2、bind()函数

bind()函数是把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket.

3.3、listen()、connect()函数

如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这是调用connect()发出连接请求,服务器就会收到这个请求。

connect()函数则是用来发送连接请求的。

3.4、accept()函数

TCP服务器端依次调用socket()、bind()、listen()之后,就会监听制定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作。

3.5、read()、write()、receive()等函数

当服务器与客户已经建立号连接之后就可以调用网络I/O进行读写操作了,即实现网络中不同进程之间的通信!网络I/O操作有下面几组:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

3.6、close()函数

在服务器与客户端建立连接后会进行一些读写操作,就要关闭相应的socket()描述字,好比操作完打开的文件要调用fclose关闭打开的文件

其中各种函数的具体参数的设置可以参考http://www.360doc.com/content/18/0902/22/38894361_783383225.shtml这位大牛的博客,关于socket通信的基本原理还是讲的较为清楚的。

3.7、TCP中三次握手建立连接详解:

1.客户方法端向服务器发送一个SYN J

2.服务器向客户端响应一个 SYN K,并对STN J进行确认ACK J+1

3.客户端再向服务端发送一个确认ACK K+1

(1) 服务器监听:服务器并不定位具体客户端的套接字,而是时刻处于监听状态;

(2) 客户端请求:客户端的套接字要描述它要连接的服务器的套接字,提供地址和端口号,然后向服务器套接字提出连接请求;

(3) 连接确认:当服务器套接字收到客户端套接字发来的请求后,就响应客户端套接字的请求,并建立一个新的线程,把服务器端的套接字的描述发给客户端。一旦客户端确认了此描述,就正式建立连接。而服务器套接字继续处于监听状态,继续接收其他客户端套接字的连接请求.

3.8、TCP中四次握手释放连接详解

1.某个应用进程首先调用close主动关闭连接,这时TCP发送一个 FIN M;

2.另一端接收到FIN M之后,执行被动关闭,对这个FIN 进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着该应用进程在这个相应的连接上再也接收不到其他额外的数据

3.一段时间之后,接收到文件结束符的应用进程会调用close()函数进行关闭socket操作,与此同时它的TCP也发送一个FIN N;

4.接收到这个FIN的源发送端的TCP会对它进行确认

四、具体的实现过程

1.图形界面的设计

2.客户端与服务端代码的写进

3.客户端与服务端之间能后实现信息的交互,最终实现局域网聊天

4.当然加入数据库后能实现更多的功能我也算是只是想实现聊天记录被实时送进数据库,然后返回出来被客户读取

5.最终构成了一个聊天软件

  在这个整体的实现过程中就主要是信息的交互,这是主要运用到了TCP/IP网络通信,然后就是MFC中按钮的设置,什么可以响应,什么时候无法响应,这些东西都只是运用到MFC中的函数就行,逻辑上面也没有什么难度。

  还有就是服务端开启和关闭,点击开启按钮如果已经开启则将其关闭,同时那个显示出来的按钮上信息也会改变,如果没有则开启服务端,然后就是客户端连接服务端,首先判断是否连接,如果连接上了就断开服务器,此后也会进行一系列的客户从服务端移除等等操作,如果没有此时状态判断是没有连接上的话就进行ip地址的读取和匹配,如果匹配成功则代表连接上同时更新聊天成员信息,如果没有匹配失败则显示连接失败信息,至于了客户端和服务端如何连接则是参考上面的3次握手建立TCP连接,断开同样参考上文四次握手释放连接。

  然后就是数据库的连接和数据入库了,首先会有一个数据库测试连接,我采用ODBC连接本地的MySQL数据库,测试连接成功是没有任何问题的,如果失败则会显示数据库连接失败,然后就是数据入库的问题,我的数据库只包含三个信息:1. 用户名 2. 发送的信息(str) 3.信息发送的时间(时间通过CTime来实现记录发送信息时的时间记录的),最后就是数据库中的数据读取,如果数据库中数据为空则不返回任何信息,如果有信息则按照上面的顺序依次将数据库中的信息打印出来,以便客户端的客户读取。

4.1、 图形界面设计

服务端图形界面:

// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO:  在此添加额外的初始化代码

    //----------------初始化------------------
    int n;
    CString noUser = _T("该用户不存在或者未上线");
    n = WideCharToMultiByte(CP_OEMCP, 0, noUser, -1, NULL, 0, 0, FALSE);
    m_strNoUser = new char[n];
    memset(m_strNoUser, 0, n*sizeof(char));
    WideCharToMultiByte(CP_OEMCP, 0, noUser.GetBuffer(0), n, m_strNoUser, n , 0, FALSE);
    m_users.AddString(_T("所有人"));
    m_connect = false;
    //设置“发送”按钮为不可用
    m_buttonSend.EnableWindow(FALSE);
    //----------------------------------------

void ClineDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        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;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

客户端图形界面:

// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO:  在此添加额外的初始化代码
    //------------------初始化---------------------
    SetDlgItemText(IDC_USERNAME,m_userName);
    //设置初始用户列表为只有一个,即“所有人”
    m_userslist.AddString(_T("所有人"));
    //设置“发送”按钮为不可用
    m_buttonSend.EnableWindow(FALSE);
    //设置“显示聊天记录”的按钮为不可用
    m_buttonShowHistory.EnableWindow(FALSE);

void ClineClientDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        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;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

4.2、客户端与服务端代码

服务端:

//启动/关闭服务器的服务
void ClineDlg::startServer()
{
    // TODO:  在此添加控件通知处理程序代码
    CString cport;
    //获取服务器的端口号
    GetDlgItemText(IDC_SERVERPORT,cport);
    //端口号的字符串转换为数字
    int port = _ttoi(cport);
    //如果没有填写端口号,则默认的端口号为1111
    if (port == 0)
    {
        m_port = 1111;
    }
    else
    {
        m_port = port;
    }
    //如果当前服务器的状态为开启,则关闭服务器
    if (m_connect)
    {
        //关闭服务器的socket
        delete m_listenSocket;
        m_listenSocket = NULL;
        m_connect = false;
        SetDlgItemText(IDC_BN_SERVER_START, _T("打开服务器"));
        UpdateEvent(_T("系统关闭服务器."));
        //关闭控件的只读状态
        m_editPort.SetReadOnly(FALSE);
        //设置“发送”按钮为不可用
        m_buttonSend.EnableWindow(FALSE);
        return;
    }
    //否则就打开服务器
    m_listenSocket = new CServerSocket();
    m_listenSocket->m_pDlg = this;
    m_listenSocket->m_userName = _T("服务器");
    // 指定对话框为主对话框,不能少了这句?
    UpdateData(true);
    // 创建服务器的套接字,IP地址默认本机IP
    if (!m_listenSocket->Create(m_port))
    {
        AfxMessageBox(_T("创建套接字错误!"));
        m_listenSocket->Close();
        return;
    }
    if (!m_listenSocket->Listen())
    {
        AfxMessageBox(_T("监听失败!"));
        m_listenSocket->Close();
        return;
    }
    m_connect = true;
    SetDlgItemText(IDC_BN_SERVER_START, _T("关闭服务器"));
    //更新聊天窗口中的消息
    UpdateEvent(_T("系统打开服务器."));
    //将服务器端口号的控件设置为只读状态
    if(port == 0)
    {
        m_editPort.SetWindowTextW(_T("1111"));
    }
    else 
    {
        m_editPort.SetWindowTextW(cport);
    }
    m_editPort.SetReadOnly(TRUE);
    //设置“发送”按钮为可用
    m_buttonSend.EnableWindow(TRUE);
}

//接收由客户端发来的消息
void ClineDlg::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;
        translateMsg(str, pSocket);
    }
    delete pData;
    pData = NULL;
}

//向服务器本身和所有客户端发送str消息
void ClineDlg::SendMSGToEvery(CString str)
{
    int n;
    //宽字节转多字节
    n = WideCharToMultiByte(CP_OEMCP, 0, str, -1, NULL, 0, 0, FALSE);
    char *pSend = new char[n];
    //初始化pSend
    memset(pSend, 0, n*sizeof(char));
    WideCharToMultiByte(CP_OEMCP, 0, str.GetBuffer(0), n, pSend, n , 0, FALSE);
    POSITION nPos = m_clientList.GetHeadPosition();
    while (nPos)
    {
        CServerSocket* pTemp = (CServerSocket*)m_clientList.GetNext(nPos);
        pTemp->Send(pSend, n);
    }
    delete pSend;
}


//移除已经退出连接的客户端
void ClineDlg::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)
        {
            CString userLeave;
            userLeave = pSockItem->m_userName;
            pSockItem->Close();
            delete pSockItem;
            int userIndex = m_users.FindString(0,userLeave);
            m_users.DeleteString(userIndex);
            m_clientList.RemoveAt(nTmpPos);
            m_userCount = m_clientList.GetCount();
            UpdateData(false);
            userLeave = _T("用户") + userLeave + _T("离开");
            UpdateEvent(userLeave);
            userLeave = _T("MUPDATEUSERLIST") + userLeave + _T("离开");
            //向服务器和所有客户端发送消息,更新服务器和所有客户端的聊天窗口内容
            SendMSGToEvery(userLeave);
            return;
        }
        nTmpPos = nPos;
    }

    CString userList = UPDATEUSERLIST;
    userList += translateUsersStr();
    //向服务器和所有客户端发送消息,更新服务器和所有客户端的聊天室内容
    SendMSGToEvery(userList);
}

//更新聊天窗口中的内容
void ClineDlg::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); 
}

//添加新的客户端
void ClineDlg::AddClient()
{
    CServerSocket *pSocket = new CServerSocket;
    pSocket->m_pDlg = this;
    m_listenSocket->Accept(*pSocket);
    pSocket->AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE);
    m_clientList.AddTail(pSocket);
    m_userCount = m_clientList.GetCount();
    UpdateData(false);
}

//拆分字符串并根据字符串提示转发消息给所有客户端或者转发给指定的客户端
void ClineDlg::translateMsg(CString str, CServerSocket* pSocket)
{
    // 转发数据给所有用户,包括发送数据的用户
    CString cstr;
    int calls = str.Find(_T("MEVERY"));
    //获取具体的内容
    cstr = str.Right(str.GetLength() - calls - 6);
    //如果选中的是“所有人”,则给所有人发消息
    if (calls != -1)
    {
        //将socket中的userName和具体内容的字符串拼接起来(如userName是“服务器”,则就是“服务器对每个人说:xxxxx”)
        cstr = pSocket->m_userName + _T("对每个人说:") + cstr;
        SendMSGToEvery(cstr);
    }
    //否则就给指定客户端发消息
    else
    {
        //这里表示如果用户连接了服务器,将发送带有MUSERNAME的字符串消息
        int userName = str.Find(_T("MUSERNAME"));
        if (userName != -1)
        {
            cstr = str.Right(str.GetLength() - userName - 9);
            pSocket->m_userName = cstr;
            m_users.AddString(cstr);
            cstr = _T("用户")+cstr + _T("连接服务器");
            UpdateEvent(cstr);
            //给所有客户端包括服务器都发送某某某连接上了服务器的消息
            SendMSGToEvery(cstr);
            Sleep(1000);
            CString userList = UPDATEUSERLIST;
            userList += translateUsersStr();
            //更新服务器和所有客户端的聊天室列表
            SendMSGToEvery(userList);
        }
        else
        {
            int call = str.Find(_T("M"));
            cstr = str.Right(str.GetLength() - call -1);
            if (call != -1)
            {
                SendMSGToOne(cstr,pSocket->m_userName);
            }
        }
    }
}

//服务器发送消息给指定的一个客户端
void ClineDlg::SendMSGToOne(CString str,CString userSendName)
{
    CString userName;
    bool flag = false;
    CServerSocket* pTemp = NULL;
    CServerSocket* pSendUser = NULL;
    int cone = str.Find(_T(" "));
    userName = str.Left(cone);
    str = str.Right(str.GetLength() - cone);
    str = userSendName + _T("对你说:") + str;
    int n;
    //宽字节转多字节
    n = WideCharToMultiByte(CP_OEMCP, 0, str, -1, NULL, 0, 0, FALSE);
    char *pSend = new char[n];
    memset(pSend, 0, n*sizeof(char));
    //宽字节转多字节
    WideCharToMultiByte(CP_OEMCP, 0, str.GetBuffer(0), n, pSend, n , 0, FALSE);
    POSITION nPos = m_clientList.GetHeadPosition();
    //查找目标客户端
    while (nPos)
    {
        pTemp = (CServerSocket*)m_clientList.GetNext(nPos);
        //如果找到了目标客户端(接收方)
        if (userName == pTemp->m_userName)
        {
            flag = true;
            pTemp->Send(pSend, n);
        }
        //如果找到了发送方
        if (userSendName == pTemp->m_userName)
        {
            pSendUser = pTemp;
        }
    }
    //如果没有找到发送方,则默认是服务器给一个指定的客户端发送消息
    if (!flag)
    {
        pSendUser->Send(m_strNoUser, strlen(m_strNoUser));
    }
    delete pSend;
}

void ClineDlg::SendMSG()
{
    // TODO:  在此添加控件通知处理程序代码
    
    CString  msg;
    //获取消息框中的字符串
    GetDlgItemText(IDC_EDITMSG, msg);
    //获取当前选中的用户的下标(比如选中了某个人或者选中了“所有人”)
    int selectIndex = m_users.GetCurSel();
    if(selectIndex == -1)
    {
        MessageBox(_T("请在聊天室中选择发送对象!"));
        return ;
    }
    if(msg == _T(""))
    {
        MessageBox(_T("请输入要发送的内容!"));
        return ;
    }
    CString usermsg;
    //获取该下标的字符串名称
    m_users.GetText(selectIndex, usermsg);
    if (usermsg == "所有人")
    {
        usermsg = _T("MEVERY") + msg;
    }
    else
    {
        usermsg = _T("M") + usermsg + _T(" ") + msg;
    }
    translateMsg(usermsg, this->m_listenSocket);
}

客户端:

void ClineClientDlg::SendMsg()
{
    // TODO:  在此添加控件通知处理程序代码
    CString  msg;
    //获得消息框里的字符串
    GetDlgItemText(IDC_EDIT_MSG, msg);
    int selectIndex = m_userslist.GetCurSel();
    if(selectIndex == -1)
    {
        MessageBox(_T("请在聊天室中选择发送对象!"));
        return ;
    }
    if(msg == _T(""))
    {
        MessageBox(_T("请输入要发送的消息内容!"));
        return ;
    }
    CString usermsg;
    m_userslist.GetText(selectIndex, usermsg);

    if (usermsg == "所有人")

    {
        usermsg = _T("MEVERY") + msg;
    }
    else
    {
        usermsg = _T("M") + usermsg + _T(" ") + msg;
    }
    if (!usermsg.IsEmpty())
    {
        //未连接服务器则不执行
        if (!m_connect)
        {
            return;
        }
        UpdateData(true);
        GetDlgItemText(IDC_EDIT_MSG, m_DataSend);
        if (m_DataSend != "")
        {
            int n = 0;
            m_DataSend = usermsg;
            //通过将宽字节转换成多字节来获得多字节的位数
            n = WideCharToMultiByte(CP_OEMCP, 0, m_DataSend, -1, NULL, 0, 0, FALSE);
            char* pBuff = new char[n];
            //初始化数组
            memset(pBuff, 0, n);
            //将宽字节转换成多字节
            WideCharToMultiByte(CP_OEMCP, 0, m_DataSend.GetBuffer(0), n, pBuff, n, 0, FALSE);
            //发送消息
            m_pSock->SendMSG(pBuff, n);
        }
    }
}

//连接服务器
void ClineClientDlg::ConnectServer()
{
    // TODO:  在此添加控件通知处理程序代码
    GetDlgItemText(IDC_USERNAME, m_userName);
    //如果已经连接,则断开服务器
    if (m_connect)
    {
        //关闭并且删除客户端的socket
        m_connect = false;
        m_pSock->Close();
        delete m_pSock;
        m_pSock = NULL;
        m_ConPC.SetWindowTextW(_T("连接服务器"));
        m_buttonSend.EnableWindow(FALSE);
        m_buttonShowHistory.EnableWindow(FALSE);
        UpdateData(false);
        return;
    }
    //否则连接服务器
    else
    {
        //在这里创建新的socket
        m_pSock = new CClientSocket();
        //创建套接字
        if (!m_pSock->Create())
        {
            AfxMessageBox(_T("创建套接字失败!"));
            return;
        }
    }
    //获得ip控件的ip
    BYTE f1, f2, f3, f4;
    ((CIPAddressCtrl*)GetDlgItem(IDC_SERVERIPADDRESS))->GetAddress(f1, f2, f3, f4);
    m_strServerip.Format(_T("%d.%d.%d.%d"), f1, f2, f3, f4);
    m_port = GetDlgItemInt(IDC_SERVERPORT);
    //连接服务器
    if (!m_pSock->Connect(m_strServerip, m_port))
    {
        AfxMessageBox(_T("连接服务器失败!"));
        return;
    }
    else
    {
        m_connect = true;
        int n = 0;
        CString userName;
        userName = _T("MUSERNAME") + m_userName;
        //宽字节转多字节,确定要发送的字符串将占据多少个宽度的char
        n = WideCharToMultiByte(CP_OEMCP, 0, userName, -1, NULL, 0, 0, FALSE);
        char* pBuff = new char[n];
        memset(pBuff, 0, n);
        //宽字节转多字节,将要发送的字符串转化为字节流
        WideCharToMultiByte(CP_OEMCP, 0, userName.GetBuffer(0), n, pBuff, n, 0, FALSE);
        //通过socket发送多字节流连接
        m_pSock->SendMSG(pBuff, n);
        m_ConPC.SetWindowTextW(_T("断开服务器"));
        m_buttonSend.EnableWindow(TRUE);
        m_buttonShowHistory.EnableWindow(TRUE);
        UpdateData(false);
    }
}


//更新聊天室列表
void ClineClientDlg::UpdateUsers(CString str, int index)
{
    str = str.Right(str.GetLength()-strlen("MUPDATEUSERLIST"));
    CStringArray *strArray = translateUsersStr(str);
    int size = strArray->GetSize();
    m_userslist.ResetContent();
    m_userslist.AddString(_T("所有人"));
    for (int i = 0; i < size; i++)
    {
        m_userslist.AddString(strArray->GetAt(i));
    }
}

//通过“|”符号裁切字符串信息
CStringArray* ClineClientDlg::translateUsersStr(CString  str)
{
    CStringArray *strArray = new CStringArray;
    int length;
    CString *user=NULL;
    while (-1 != str.Find(_T("|") ))
    {
        //找到了一个“|”
        length = str.Find(_T("|"));
        user = new CString();
        //取“|”左边的字符串
        *user = str.Left(length);
        //更新字符串
        str = str.Right(str.GetLength() - length - 1);
        //更新数组
        strArray->Add(*user);
    }
    return strArray;
}


   我在客户端中定义发送消息的规则为发送消息之前将要发送消息的字符串进行打包(加首部),当消息传送到服务器端的时候服务器解包(去首部)就可以得到消息,还有包括图形界面的参数,文字规则的解读和发送都可以根据自己喜好更改。

4.3、客户端与服务端之间能后实现信息的交互,最终实现局域网聊天

4.4 数据库的连接

/测试连接数据库    

m_dataBase.Open(NULL,  false,  false, _T("ODBC;server=172.25.1.135;DSN=summer;UID=root;PWD=123456")  );     

if (!m_dataBase.IsOpen())     

{       

      AfxMessageBox(_T("DB open failed!"));       

      return false;     
}

数据库查询数据:

//查询数据库
    m_historyStr = _T("");

    CRecordset record(&m_dataBase); 
    int count = 0;  
        CString str2;  
        CString userNameFilter;
        //record.m_strFilter = _T("name='小白'");  
        userNameFilter.Format(_T("name='%s'"),m_userName);
        record.m_strFilter = userNameFilter;
        //my_record.m_strSort = _T("cust_id desc");  
        record.Open(CRecordset::snapshot  , _T("select * from history"));  
        record.MoveFirst();
        //如果数据库为空,则返回
        //int num = record.GetODBCFieldCount();
        if(record.IsBOF())  
        {  
            return ;  
        }  
        CDBVariant varValue;
  
        //获得表中全部数据
        while(!record.IsEOF())  
        {  
            record.GetFieldValue(2, str2);  
            m_historyStr += str2;
            m_historyStr +=_T("\r\n");
            record.GetFieldValue((short)0, str2);  
            m_historyStr += str2;
  
            record.GetFieldValue(1, str2);  
            m_historyStr += str2;
             
            m_historyStr +=_T("\r\n");
            record.MoveNext();  
            count++;  
        }  
    UpdateData(FALSE);
}

//每更改一次聊天窗口的内容,就更新数据库对应的聊天记录
void ClineClientDlg::updateDataBase(CString newStr)
{
    if(m_pSock == NULL)
    {
        return ;
    }
    //获取系统时间
    //SYSTEMTIME st;   
    //CString strDate,strTime;   
    //this->GetLocalTime(&st);   
    //strDate.Format("%4d-%2d-%2d",st.wYear,st.wMonth,st.wDay);   
    //strTime.Format("%2d:%2d:%2d",st.wHour,st.wMinute,st.wSecond);
    CTime curTime;
    curTime = CTime::GetCurrentTime();
    CString strCurTime;                 
    strCurTime.Format(_T("%4d-%2d-%2d %2d:%2d:%2d"), curTime.GetYear(),
    curTime.GetMonth(), curTime.GetDay(), curTime.GetHour(),
    curTime.GetMinute(), curTime.GetSecond());

    CString str;
    str.Format(_T("insert history value('%s','%s','%s')"),m_userName,newStr,strCurTime);
    m_dataBase.ExecuteSQL(str);
}

 

大致也就这样喽,个人比较菜,大牛看到错误望指出,让我加以更正!

 

 

  • 26
    点赞
  • 248
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
网络编程,当然要用到Windows Socket(套接字)技术。Socket相关的操作由一系列API函数来完成,比如socket、bind、listen、connect、accept、send、sendto、recv、recvfrom等。调用这些API函数有一定的先后次序,有些函数的参数还比较复杂,对于开发者来说,不是很好用。于是,微软的MFC提供了两个:CAsyncSocket和CSocket,极大地方便了Socket功能的使用。   CAsyncSocket在较低层次上封装了Windows Socket API,并且通过内建一个(隐藏的)窗口,实现了适合Windows应用的异步机制(Windows Socket API默认情况下工作在阻塞模式,不方便直接在消息驱动的Windows程序上使用)。CSocket从CAsyncSocket派生,进一步简化了Socket功能的应用。不过很遗憾,正因为这两个都内建了一个窗口,它们并不是线程安全的(thread-safe);如果要在多线程环境下应用Socket功能,建议自行封装Socket API函数。 基于TCP的socket编程的服务器端程序流程如下: 1、创建套接字 2、将套接字绑定到一个本地地址和端口号上(bind) 3、将套接字设为监听模式,准备接受客户请求(listen) 4、等待客户请求,请求到来时接受请求,建立链接,并返回 一个新的基于此次通信的套接字(accept) 5、用返回的套接字和客户端进行通信(send、recv) 6、返回,等待另一客户请求 7、关闭套接字 基于TCP的socket编程的客户端程序流程如下: 1、创建套接字 2、向服务器端发送请求(connect) 3、和服务器端进行通信(send、recv) 4、关闭套接字 基于UDP的socket编程的服务器端程序流程如下: 1、创建套接字 2、将套接字绑定到本地地址和端口号上(bind) 3、等待接收数据(recvfrom) 4、关闭套接字 基于UDP的socket编程的客户端程序流程如下: 1、创建套接字 2、和服务器端进行通信(sendto) 3、关闭套接字 异步方式指的是发送方不等接收方响应,便接着发下个数据包的通信方式;而同步指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式。   阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用recv()函数读取网络缓冲区中的数据,如果没有数据到达,将一直挂在recv()这个函数调用上,直到读到一些数据,此函数调用才返回;而非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步非阻塞模式的。   对于这些概念,初学者的理解也许只能似是而非,我将用一个最简单的例子说明异步非阻塞Socket的基本原理和工作机制。目的是让初学者不仅对Socket异步非阻塞的概念有个非常透彻的理解,而且也给他们提供一个用Socket开发网络通信应用程序的快速入门方法。操作系统是Windows 98(或NT4.0),开发工具是Visual C++6.0。   MFC提供了一个异步CAsyncSocket,它封装了异步、非阻塞Socket的基本功能,用它做常用的网络通信软件很方便。但它屏蔽了Socket的异步、非阻塞等概念,开发人员无需了解异步、非阻塞Socket的原理和工作机制。因此,建议初学者学习编网络通信程序时,暂且不要用MFC提供的,而先用Winsock2 API,这样有助于对异步、非阻塞Socket编程机制的理解。
主要介绍如何开发一个ActiveX控件,提供接口,与相应事件挂钩。文中涉及到VARIANT,SAFEARRAY,BSTR的详细使用方法。 另外还提供了WinSock的详细开发步骤,以及如何响应网络超时,网络断开的事件方法以及在VC,VB调用该控件的方法。 一、MFC ActiveX控件开发步骤(VC 6.0): New->Projects->MFC ActiveX ControlWizard,然后输入MFCWinSock工程名。如下图: 图一 创建工程 一路狂按Next,直至Finsh出现,再按下OK,如下图:     图二 创建完成 二、架设Socket环境: 首先在StdAfx.h文件中加入下面这句代码: #include <afxsock.h> // MFC socket extensions 打开MFCWinSock.cpp文件,添加代码,看起来如下: //////////////////////////////////////////////////////////////////////////// // CMFCWinSockApp::InitInstance - DLL initialization BOOL CMFCWinSockApp::InitInstance() { BOOL bInit = COleControlModule::InitInstance(); if (bInit) { // TODO: Add your own module initialization code here. if (!AfxSocketInit()) { AfxMessageBox("无法初始化Socket,请检查!"); return FALSE; } WSADATA wsaData; WORD wVersion = MAKEWORD(1, 1);//设定为Winsock 1.1版 int errCode; errCode = WSAStartup(wVersion, &wsaData);//启动Socket服务 if (errCode) { AfxMessageBox("无法找到可以使用的 WSOCK32.DLL"); return FALSE; } } return bInit; } //////////////////////////////////////////////////////////////////////////// // CMFCWinSockApp::ExitInstance - DLL termination int CMFCWinSockApp::ExitInstance() { // TODO: Add your own module termination code here. WSACleanup();//结束网络服务 return COleControlModule::ExitInstance(); } 三,提供控件接口和事件 在MFCWinSockCtl.cpp加入如下代码: #ifndef WM_MYWINSOCK #define WM_MYWINSOCK WM_USER+1888 #endif View->ClassWizard->Automation->Add Method…如下图: 图三 创建接口 这个时候,我们为这个控件添加了一个Connect()的接口,出于通用性,安全性和扩展性的考虑,我们采用了VARIANT型的参数, 很多人可能都不太了解该型,又或者有接触过,但被吓怕了,那么我们来看清它的本来面目: struct tagVARIANT { union { struct __tagVARIANT { VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { LONG lVal; BYTE bVal; SHORT iVal; FLOAT fltVal; DOUBLE dblVal; VARIANT_BOOL boolVal; _VARIANT_BOOL bool; SCODE scode; CY cyVal; DATE date; BSTR bstrVal; IUnknown __RPC_FAR *punkVal; IDispatch __RPC_FAR *pdispVal; SAFEARRAY __RPC_FAR *parray; BYTE __RPC_FAR *pbVal; SHORT __RPC_FAR *piVal; LONG __RPC_FAR *plVal; FLOAT __RPC_FAR *pfltVal; DOUBLE __RPC_FAR *pdblVal; VARIANT_BOOL __RPC_FAR *pboolVal; _VARIANT_BOOL __RPC_FAR *pbool; SCODE __RPC_FAR *pscode; CY __RPC_FAR *pcyVal; DATE __RPC_FAR *pdate; BSTR __RPC_FAR *pbstrVal; IUnknown __RPC_FAR *__RPC_FAR *ppunkVal; IDispatch __RPC_FAR *__RPC_FAR *ppdispVal; SAFEARRAY __RPC_FAR *__RPC_FAR *pparray; VARIANT __RPC_FAR *pvarVal; PVOID byref; CHAR cVal; USHORT uiVal; ULONG ulVal; INT intVal; UINT uintVal; DECIMAL __RPC_FAR *pdecVal; CHAR __RPC_FAR *pcVal; USHORT __RPC_FAR *puiVal; ULONG __RPC_FAR *pulVal; INT __RPC_FAR *pintVal; UINT __RPC_FAR *puintVal; struct __tagBRECORD { PVOID pvRecord; IRecordInfo __RPC_FAR *pRecInfo; } __VARIANT_NAME_4; } __VARIANT_NAME_3; } __VARIANT_NAME_2; DECIMAL decVal; } __VARIANT_NAME_1; }; 它先是一个结构体,里面有一个重要成员VARTYPE vt;vt即是指明当前的数据型,比如整型或者字符型,当指明vt后, 后面看到各种变量型包括在一个联合体当中,也就是说指明vt后,你只能使用对应的其中之一变量型。看着这众多的各种不同 型变量集中在一起,确实让人吓了一跳,但细细看来,大多数变量跟我们平时的用法相似。值得一提的是SAFEARRAY __RPC_FAR *parray; 也许有很多人还没有接触过SAFEARRAY型的变量,SAFEARRAY实际上也是一个结构,大家可以参考MSDN,我也将在后面介绍它的具体使用方法。 用同样的方法创建DisConnect()接口 创建两个事件,FireCloseWinsock()响应网络断开事件,FireRecvSockEvent()响应网络有数据到达的事件。创建方法如下图: 图四 创建事件 重载控件消息处理函数WindowProc(),在View->ClassWizard中打开向导,在消息映射中找到WindowProc,如下图: 图五 重载WindowProc() 四、编写代码 编写VariantToLong()转换函数,该函数代码如下: //型转换,将VARIANT型转换成Long型 long CMFCWinSockCtrl::VariantToLong(const VARIANT &var) { long r; switch(var.vt) { case VT_UI2://USHORT r = var.uiVal; break; case VT_UI4://ULONG r = var.ulVal; break; case VT_INT://INT r = var.intVal; break; case VT_UINT://UINT r = var.uintVal; break; case VT_I4://LONG r = var.lVal; break; case VT_UI1://BYTE r = var.bVal; break; case VT_I2://SHORT r = var.iVal; break; case VT_R4://FLOAT r = (long)var.fltVal; break; case VT_R8://DOUBLE r = (long)var.dblVal; break; default: r = -1;//无法转换该值 break; } return r; } 大家可以看到,该函数将最基本的若干中数据型转换成了long型,但VARIANT决不是个简单的谱,我将在后面继续揭开它的神秘面纱. 编写我们刚才的接口Connect(),代码代码如下: 在MFCWinSockCtrl.h中加入 SOCKET OnlySock;//建立的唯一Socket,不允许重复建立多个 bool isOnlyConnect;//是否建立了连接 然后再编写Connect(),看起来如下: BOOL CMFCWinSockCtrl::Connect(const VARIANT FAR& RemoteHost, const VARIANT FAR& RemotePort) { // TODO: Add your dispatch handler code here if(isOnlyConnect)//该连接已建立,还没有断开 return FALSE; CString IPAddress; int Port;//转换成整型的端口 switch(RemoteHost.vt) { case VT_BSTR://字符串型 IPAddress = CString(RemoteHost.bstrVal); break; case VT_BYREF|VT_I1://CHAR * IPAddress.Format("%s",RemoteHost.pcVal);//RemoteHost.pbstrVal); break; default: IPAddress = ""; return FALSE; } Port = VariantToLong(RemotePort);//我们编写的一个VARIANT转换成long型的函数 if(Port<=0) return FALSE; _TCHAR *ip = 0; struct hostent *host = 0; struct sockaddr_in addr; ULONG dotIP = inet_addr(IPAddress); OnlySock = socket(AF_INET, SOCK_STREAM, 0); // 判断是否为点IP地址格式 if (OnlySock == INVALID_SOCKET) { shutdown(OnlySock, 0x02); closesocket(OnlySock);//释放占有的SOCK资源 return FALSE; } memset(&addr, 0, sizeof(struct sockaddr_in)); // 设定 SOCKADDR_IN 结构的内容 // 如果通讯协议是选择IP Protocol,那此值固定为AF_INET // AF_INET 与 PF_INET 这两个常量值相同 addr.sin_family = AF_INET; addr.sin_port = htons(Port); addr.sin_addr.S_un.S_addr = dotIP; if (dotIP == INADDR_NONE) { host = gethostbyname(IPAddress); if (!host) { shutdown(OnlySock, 0x02); closesocket(OnlySock);//释放占有的SOCK资源 return FALSE; }; ip = inet_ntoa(*(struct in_addr*)(*host->h_addr_list)); addr.sin_addr.S_un.S_addr = inet_addr(ip); } //开始连线 if (connect(OnlySock, (LPSOCKADDR)&addr, sizeof(SOCKADDR))) { shutdown(OnlySock, 0x02); closesocket(OnlySock);//释放占有的SOCK资源 return FALSE; } int iError = WSAAsyncSelect(OnlySock, m_hWnd,WM_MYWINSOCK, FD_READ|FD_CLOSE); //只对网络断开和数据到达通知感兴趣 if(iError == SOCKET_ERROR)//无法绑定Winsock的事件通知 { shutdown(OnlySock, 0x02); closesocket(OnlySock);//释放占有的SOCK资源 return FALSE; } isOnlyConnect = true; return TRUE; } 有必要提一下WSAAsyncSelect(),这里接收网络数据到达和断开的两个消息,我们收到WM_MYWINSOCK消息时将处理该消息并作为事件传送给调用者. 第二个参数,窗口句柄,我们传送了m_hWnd,这是因为MFC ActiveX也属于一个窗口,并且是可见的,因此可以成功。 编写WindowProc(),代码看起来如下: LRESULT CMFCWinSockCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: Add your specialized code here and/or call the base class switch(message) { case WM_MYWINSOCK://响应自定义的消息 switch(WSAGETSELECTEVENT(lParam)) { case FD_READ://有新数据到达 FireRecvSockEvent(); break; case FD_CLOSE://对方已断掉当前连接 FireCloseWinsock(); break; } break; default: break; } return COleControl::WindowProc(message, wParam, lParam); } 本部分结束语: 好了,现在一个可以运行的控件已经完成,里面提供有Connect()和DisConnect()接口,和RecvSockEvent()及CloseWinsock()事件。以及WinSock的使用方法。 在下一部分(高级篇)将讲解两个重要接口SendData()和GetData(),下期内容如下: long SendData(const VARIANT FAR& Data, const VARIANT FAR& DataType,const VARIANT FAR& DataLength, const VARIANT FAR& TimeOut) long GetData(VARIANT FAR* Data, const VARIANT FAR& DataType, const VARIANT FAR& DataMaxLength, const VARIANT FAR& TimeOut) VARIANT和SAFEARRAY的复杂用法。 控件开发出来后在VC和VB环境下的使用方法。 声明: 部分资料来源于网络,本文所用的所有源代码仅供非商业用途,并请保留原版权,否则后果自负! 欢迎大家拍砖,或指正不足的地方,一起探导更好的方法。 欢迎访问www.59186618.com,感谢您的支持!
基于MFC的UDP通信是一种基于用户数据报协议的通信方式。UDP(User Datagram Protocol)是一种无连接的、不可靠的传输协议,适用于传输速度要求较高、数据可靠性要求较低的场景。 MFC(Microsoft Foundation Class)是微软提供的一套用于开发Windows图形界面应用程序的C++库。通过MFC,我们可以方便地创建窗口、对话框以及控件等。 在基于MFC的UDP通信中,我们可以使用MFC提供的Socket进行通信。首先,我们需要创建一个UDP套接字对象,通过调用Create函数来创建。然后,可以使用Bind函数来绑定本地端口。 对于发送数据报,我们可以调用SendTo函数来发送数据至指定的目标地址和端口。对于接收数据报,可以调用ReceiveFrom函数来接收来自远程主机的数据。 在MFC中,可以使用消息机制来处理收到的数据。当有数据到达时,Socket会触发一个自定义的消息,我们可以在消息的处理函数中对数据进行处理。可以通过重载窗口或对话框的消息映射函数来捕获和处理这个消息。 需要注意的是,UDP是一种无连接的传输协议,所以在通信过程中无法保证数据的可靠性。为了提高数据的可靠性,可以在应用层实现一些重传机制来保证数据的送达。 基于MFC的UDP通信具有灵活性和高效性的特点,适用于一些需要高速传输但对数据可靠性要求不高的场景,比如实时数据传输、视频流传输等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值