VC++深入详解(15):简单聊天工具的实现

本文介绍了在VC++ MFC应用中使用AfxSocketInit和WSAStartup进行套接字初始化,探讨了阻塞与非阻塞模式的区别,并详细说明了如何利用WSAAsyncSelect进行基于消息的异步网络事件通知。通过实例展示了如何创建套接字、接收和发送数据,以及在程序中应用这些函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们看一个综合例子:使用MFC来实现一个网络聊天软件。看到这个例子有人可能觉得奇怪,前面网络编程时讲到过一个类似的控制台应用程序的例子,为什么要放在这里?原因在于,前面的那个例子,我们必须是一个人说完就得等另一个人说,不能自己连续说,这是由它的实现代码决定的;而我们这里想实现的是“自由”的对话,可以在任意时间发送或者接收数据。这就需要我们这一小节的知识来帮忙了:我们可以利用一个线程来实现接收消息。
下面我们一步步开始完成,先设计程序的外观:首先使用一个对话框应用程序,然后将对话框上的默认按钮“确认”和“取消”都删除。然后为它加上下面的控件:
控件名称	ID		作用
组框		IDC_STATIC	标识:接收数据
接收编辑框	IDC_EDIT_RECV	显示所有聊天数据
发送组框	IDC_STATIC	标识:发送数据
IP地址空间	IDC_IPADDRESS1	允许用户输入点分十进制IP
发送编辑框	IDC_EDIT_SEND	允许用户输入发送的内容
发送按钮	IDC_BTN_SEND	点击后把信息发送到指定IP上



完成了外观设计之后,我们就要开始编写网络程序了,回忆一下基于UDP的socket编写的步骤:
服务器端:
1.加载套接字库,协商版本号
2.创建套接字。
3.绑定端口
4.发送/接收数据
5.关闭套接字
6.释放资源
客户端:
1.加载套接字库,协商版本号
2.创建套接字
3.发送/接收消息
4.关闭套接字
5.释放资源


MFC为我们提供了AfxSocketInit函数来实现加载套接字,协商版本号的功能,由MSDN可知,我们应该把它放在我的自己应用程序类的InitInstance中:

BOOL CCH_15_CHATApp::InitInstance()  
{  
    if(!AfxSocketInit)  
    {  
        AfxMessageBox("加载套接字失败");  
        return FALSE;  
    }  
//其他代码省略  
}
注意如果要使用这个函数,必须包含Afxsock.h这个头文件。我们应该把这头文件包含在stdafx.h中。stdafx.h是一个预编译头文件,里面包含了MFC应用程序所需的一些必要的头文件。

接着,为从CDialog派生下来的两个类中的那个不是CAboutDlg的类(CAboutDlg是用来产生关于对话框的这里没有用)增加一个私有SOCKET类型的成员变量m_socket和BOOL类型的成员函数InitSocket,用来初始化套接字:

BOOL CCH_15_CHATDlg::InitSocket()  
{  
    m_socket = socket(AF_INET,SOCK_DGRAM,0);  
    if(INVALID_SOCKET == m_socket)  
    {  
        MessageBox("创建套接字失败");  
        return FALSE;  
    }  
    SOCKADDR_IN addrSock;  
    addrSock.sin_family = AF_INET;  
    addrSock.sin_port = htons(6000);  
    addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY);  
      
    int retval;  
    retval = bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR));  
    if(SOCKET_ERROR == retval)  
    {  
        closesocket(m_socket);  
        MessageBox("绑定失败!");  
        return FALSE;  
    }  
  
    return TRUE;  
}  
我们这个程序既要接收又要发送,对于接收程序来说,需要指定使用那个端口接收,接收什么IP的消息。我们应该在OnInitDialog中调用这个函数,完成套接字的初始化。
下面实现接收功能。如果没有数据到来,recvfrom函数会阻塞,从而导致程序暂停运行。所以我们可将接收数据的操作放置在一个单独的线程中完成,并给这个线程传递两个参数,一个是套接字,一个是对话框的句柄,这样,当接收导数据后,可以将该数据传给对话框,经过处理之后显示在接收编辑框控件上。但是,我们知道CreateThread函数中只有第4个参数可以用来给创建的线程传递参数,这该怎么办呢?我们发现第4个参数是一个LPVOID,而他的实际类型是(void *)也就是一个空类型的指针,所以我们可以定义一个结构体,这个结构体中包含了前面说的2个参数,然后把这个结构体的地址传给函数即可。
我们先在这个类的头文件中定义一个结构体:

struct RECVPARAM  
{  
    SOCKET sock;  
    HWND hwnd;  
};

然后在OnInitDialog中完成对结构体的赋值并创建线程:

RECVPARAM *pRecvParam = new RECVPARAM;  
pRecvParam->sock = m_socket;  
pRecvParam->hwnd = m_hWnd;  
  
HA
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值