基于消息的异步套接字实现的聊天程序

基于消息的异步套接字

  Windows套接字在两种模型下执行I/O操作,阻塞和非阻塞。在阻塞模式下,在I/O操作完成前,执行操作的WinSock函数会一直等待下去,不会立即返回程序(将控制权交换给程序)。而在非阻塞模式下,WinSock函数无论如何都会立即返回。

  Windows Sockets为了支持Windows消息驱动极值,使应用程序开发者能够方便地处理网络通信,它对网络时间采用了基于消息的异步存取策略。

  Windows Sockets的异步选择函数WSAAsyncSelect()函数提供了消息机制的网络事件选择,当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息。

WSASocket:创建一个与指定传送服务提供者捆绑的套接口,可选地创建和/或加入一个套接口组。

SOCKET WSASocket(
  int af,
  int type,
  int protocol,
  LPWSAPROTOCOL_INFO lpProtocolInfo,
  GROUP g,
  DWORD dwFlags
);
/*
参数描述
af:[in]一个地址族规范。目前仅支持AF_INET格式,亦即ARPA Internet地址格式。
type:新套接口的类型描述。
protocol:套接口使用的特定协议,如果调用者不愿指定协议则定为0。
lpProtocolInfo:一个指向PROTOCOL_INFO结构的指针,该结构定义所创建套接口的特性。如果本参数非零,则前三个参数(af, type, protocol)被忽略。
g:保留给未来使用的套接字组。套接口组的标识符。
iFlags:套接口属性描述。
*/

socket() 函数创建一个通讯端点并返回一个套接口。但是在socket库中例程在应用于阻塞套接口时会阻塞。WSASocket()的发送操作和接收操作都可以被重叠使用。接收函数可以被多次调用,发出接收缓冲区,准备接收到来的数据。发送函数也可以被多次调用,组成一个发送缓冲区队列。可是socket()却只能发过之后等待回消息才可做下一步操作!

WSAAsyncSelect:基于windows消息的网络事件通知套接字请求

int WSAAsyncSelect(
  __in          SOCKET s,
  __in          HWND hWnd,
  __in          unsigned int wMsg,
  __in          long lEvent
);

/*
s:用于标识需要事件通知的套接字的描述符
hWnd:一个句柄,用于标识在发生网络事件时将接收消息的窗口。
wMsg:发生网络事件时要接收的消息。
lEvent:一个位掩码,指定应用程序感兴趣的网络事件的组合。
*/
Value Meaning
FD_READ Set to receive notification of readiness for reading.
FD_WRITE Wants to receive notification of readiness for writing.
FD_OOB Wants to receive notification of the arrival of OOB data.
FD_ACCEPT Wants to receive notification of incoming connections.
FD_CONNECT Wants to receive notification of completed connection or multipoint join operation.
FD_CLOSE Wants to receive notification of socket closure.
FD_QOS Wants to receive notification of socket Quality of Service (QOS) changes.
FD_GROUP_QOS Wants to receive notification of socket group Quality of Service (QOS) changes (reserved for future use with socket groups). Reserved.
FD_ROUTING_INTERFACE_CHANGE Wants to receive notification of routing interface changes for the specified destination(s).
FD_ADDRESS_LIST_CHANGE Wants to receive notification of local address list changes for the socket protocol family.

 

WSARecvFrom:接收套接字上的数据并存储源地址。

int WSARecvFrom(
  SOCKET s,
  LPWSABUF lpBuffers,
  DWORD dwBufferCount,
  LPDWORD lpNumberOfBytesRecvd,
  LPDWORD lpFlags,
  struct sockaddr FAR* lpFrom,
  LPINT lpFromlen,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

/*
s:标识套接字描述符
lpBuffers:一个指向WSABUF结构体的指针,每一个WSABUF结构体包含一个缓冲区的指针和缓冲区的长度。
dwBufferCount:lpBuffers数组中WSABUF结构体的数目。
lpNumberOfBytesRecvd:如果接收操作立即完成,则为一个指向本次调用所接收的字节数的指针
lpFlags:一个指向标志位的指针
lpFrom:可选指针,指向重叠操作完成后存放源地址的缓冲区
lpFromlen:指向from缓冲区大小的指针,仅当指定了lpFrom才需要
lpOverlapped:一个指向WSAOVERLAPPED结构体的指针(对于非重叠套接字则忽略)
lpCompletionRoutine:一个指向接收操作完成时调用的完成例程的指针(对于非重叠套接字则忽略)
/*

WSASendTo:此函数使用重叠的I / O将数据发送到特定目标。

int WSASendTo(
  SOCKET s,
  LPWSABUF lpBuffers,
  DWORD dwBufferCount,
  LPDWORD lpNumberOfBytesSent,
  DWORD dwFlags,
  const struct sockaddr FAR* lpTo,
  int iToLen,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

以上就是我们本次需要用到的函数,另外我们需要引入头文件WinSock2.h,以及链接ws2_32.lib。

Chart.cpp中加载套接字库2.2版本

BOOL CChartApp::InitInstance()
{
	//加载套接字库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD( 2, 2 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		return FALSE;
	}

	if ( LOBYTE( wsaData.wVersion ) != 2 ||
		HIBYTE( wsaData.wVersion ) != 2 ) {
			WSACleanup( );
			return FALSE;
	}
    //省略....
}

ChartDlg.cpp

// ChartDlg.cpp : 实现文件
//

#include "stdafx.h"
#include "Chart.h"
#include "ChartDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


CChartDlg::CChartDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CChartDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_socket = 0;
}

CChartDlg::~CChartDlg()
{
	if (m_socket)
	{
		closesocket(m_socket);
	}
}

void CChartDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CChartDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_MESSAGE(UM_SOCK, OnSock)
	ON_BN_CLICKED(IDC_BTN_SEND, &CChartDlg::OnBnClickedBtnSend)
END_MESSAGE_MAP()


// CChartDlg 消息处理程序

BOOL CChartDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	//....

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

	((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->SetAddress(127,0,0,1);

	InitSocket();

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

LRESULT CChartDlg::OnSock(WPARAM wParam, LPARAM lParam)
{
	switch(LOWORD(lParam))
	{
	case FD_READ:
		WSABUF wsaBuf;
		wsaBuf.buf = new char[200];
		wsaBuf.len = 200;
		DWORD dwRead;
		DWORD dwFlag = 0;
		SOCKADDR_IN addrFrom;
		int len = sizeof(SOCKADDR);
		if (SOCKET_ERROR == WSARecvFrom(m_socket, &wsaBuf, 1, &dwRead, &dwFlag,
			(SOCKADDR*)&addrFrom, &len, NULL, NULL))
		{
			AfxMessageBox(_T("接收数据失败!"));
			return FALSE;
		}
		CString strMsg, strTemp;
		strMsg.Format(_T("%s say: %s"), inet_ntoa(addrFrom.sin_addr), wsaBuf.buf);
		strMsg.Append(_T("\r\n"));
		GetDlgItemText(IDC_EDIT_RECV, strTemp);
		strMsg.Append(strTemp);
		SetDlgItemText(IDC_EDIT_RECV, strMsg);
		break;
	}
	return TRUE;
}

BOOL CChartDlg::InitSocket()
{
	m_socket = WSASocket(AF_INET, SOCK_DGRAM, 0,NULL, 0, 0);
	if (INVALID_SOCKET  == m_socket)
	{
		AfxMessageBox(_T("创建套接字失败!"));
		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 ret = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR));
	if (SOCKET_ERROR == ret)
	{
		closesocket(m_socket);
		AfxMessageBox(_T("bind fail"));
		return FALSE;
	}
	if (SOCKET_ERROR == WSAAsyncSelect(m_socket, m_hWnd, UM_SOCK, FD_READ))
	{
		AfxMessageBox(_T("注册网络读取事件失败!"));
		return FALSE;
	}
	
	return TRUE;
}


void CChartDlg::OnBnClickedBtnSend()
{
	// TODO: 在此添加控件通知处理程序代码
	DWORD dwIp;
	DWORD dwSend;
	CString strSend;
	WSABUF wsaBuf;
	int len;
	((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIp);

	SOCKADDR_IN addrTo;
	addrTo.sin_family = AF_INET;
	addrTo.sin_port = htons(6000);
	addrTo.sin_addr.S_un.S_addr = htonl(dwIp);
	
	GetDlgItemText(IDC_EDIT_SEND, strSend);
	len = strSend.GetLength();
	wsaBuf.buf = strSend.GetBuffer(len);
	wsaBuf.len = len + 1;
	strSend.ReleaseBuffer(len);
	SetDlgItemText(IDC_EDIT_SEND, _T(""));

	if (SOCKET_ERROR == WSASendTo(m_socket, &wsaBuf, 1, &dwSend, 0,
		(SOCKADDR*)&addrTo, sizeof(SOCKADDR), NULL, NULL))
	{
		AfxMessageBox(_T("发送数据失败!"));
		return;
	}
}

ChartDlg.h

// ChartDlg.h : 头文件
//

#pragma once

#define UM_SOCK		WM_USER + 1
// CChartDlg 对话框
class CChartDlg : public CDialog
{
// 构造
public:
	CChartDlg(CWnd* pParent = NULL);	// 标准构造函数
	~CChartDlg();

// 对话框数据
	enum { IDD = IDD_CHART_DIALOG };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持


// 实现
protected:
	HICON m_hIcon;

	// 生成的消息映射函数
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	afx_msg LRESULT OnSock(WPARAM wParam, LPARAM lParam);
	DECLARE_MESSAGE_MAP()
private:
	SOCKET m_socket;

private:
	BOOL InitSocket();
public:
	afx_msg void OnBnClickedBtnSend();
};

 

你可能感兴趣的:(网络编程)