第一章 UDP

UDPUser Datagram Protocol,用户数据报协议)具体是什么我不说了,随便GoogleBaidu一下,或者到书店、图书馆都能找到详细的答案。我只是打个比方,UDP的通信方式就好像你看见张三,然后你就扯着脖子喊“张三”,张三听见后,回喊你“李四”,你们就这么互相扯着脖子喊话。不过,要是张三的耳朵有点背,你就得多喊两声;再或者,有座隔音墙,你就是喊破喉咙,张三也不会理你,你只能自我安慰,那小子耳朵聋。

从上面的过程来看,UDP非常简单,根本不需要像打电话一样建立连接,虽然不是很可靠,但是可以靠确认、超时重传来保证。QQ的聊天部分就是使用的UDP,真的很怀念9x年代的只有几MOICQ

下面我们来亲手做一个使用UDP的聊天程序。用WTL的向导生成一个模式对话框程序,把对话框改成如下这样:

完整的类定义头文件如下

#pragma once

#include "UDPSocket.h"

class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg>

{

public:

       enum { IDD = IDD_MAINDLG };

       BEGIN_MSG_MAP(CMainDlg)

              MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)

              MESSAGE_HANDLER(WM_UDP, OnUDP)

              COMMAND_ID_HANDLER(ID_ABOUT, OnAbout)

              COMMAND_ID_HANDLER(IDOK, OnOK)

              COMMAND_ID_HANDLER(IDCANCEL, OnCancel)

              COMMAND_ID_HANDLER(IDC_SEND, OnBnClickedSend)

       END_MSG_MAP()

       BEGIN_DDX_MAP(CMainDlg)

              DDX_TEXT(IDC_ADDRESS, m_address)

              DDX_TEXT(IDC_MSG_SEND, m_msgSend)

              DDX_TEXT(IDC_MSG, m_msg)

              DDX_TEXT(IDC_NAME, m_name)

       END_DDX_MAP()

       LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);

       LRESULT OnUDP(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);

       LRESULT OnAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

       LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

       LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

       LRESULT OnBnClickedSend(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

private:

       CString m_address;

       CString m_msgSend;

       CString m_msg;

       CString m_name;

       CUDPSocket m_listen;

       CUDPSocket m_talk;

};

修改后的stdafx.h是这样的:

#pragma once

// Change these values to use different versions

#define WINVER          0x0400

//#define _WIN32_WINNT   0x0400

#define _WIN32_IE      0x0400

#define _RICHEDIT_VER    0x0100

#define WM_UDP WM_USER + 100

#include <atlbase.h>

#include <atlstr.h>

#include <atlapp.h>

extern CAppModule _Module;

#include <atlwin.h>

#include <atlframe.h>

#include <atlctrls.h>

#include <atldlgs.h>

#include <atlddx.h>

#include "resource.h"

先不说CUDPSocket是什么样子的,从上面的定义我们大致能猜出程序的执行流程,一个m_listen当耳朵,负责听;一个m_talk当嘴,负责说。只有这么两个socket就足够了,UDP够简单吧。现在我们来看一下CUDPSocket的定义:

#pragma once

class CUDPSocket

{

public:

       CUDPSocket(void);

       ~CUDPSocket(void);

       void Init(UINT port, HWND hWnd = NULL);

       void Send(const char* buffer, int length, const char* address = NULL);

       int Receive(char* buffer, int length);

private:

       SOCKET m_socket;

       SOCKADDR_IN m_addr;

};

自然的,使用之前总要初始化一下吧,当然最重要的是port端口号,相信你一定不会对这个名词陌生,具体是什么,自己查资料吧,如果把机器比喻成一座楼,IP地址就是楼号,端口就是机器上应用程序的门牌号。Send当然就是说话的人用的,Receive自然是听话的人用的。

这里必须要提到socket的两种工作方式:

阻塞方式就是在I/O操作完成前,执行操作的Winsock调用(例如sendrecv)会一直等下去,因此为了避免无限制的等待影响主流程的运行,一般需要为这个socket建立一个线程,还要处理好同步,这不符合我们的篇名,故此不做介绍。

非阻塞方式,就是不管I/O操作是否完成,执行操作的Winsock调用都会返回,这就需要一个通知机制,轮询是不可取的。Winsock提供了几种异步机制,对于简单应用,推荐使用WSAAsyncSelect模型,应用程序可在socket上接收以Windows消息为基础的网络时间通知,用窗口的消息处理程序来处理。

用了你就知道,这个模型是及其简单的,MFCCSocket用的就是这个模型,但是对于TCP来说,这个模型的伸缩性不好,因此有些人不喜欢用CSocket。但是,对于UDP来说,确实是非常合适的,简单对简单,正符合我们的篇名。

思考一下就会明白,说话的人是不需要通知的,反正说就是了,至于听没听到那我可管不着,这让我想起了一个人在没人的地方练演讲的情形。而听话的人是需要通知的,就是说当有消息到达的时候要接收、处理。因此,初始化函数实际上是两个,如果给出了窗口句柄就表示需要通知,这时它是充当听话的人的;否则就是说话的人。下面给出CUDPSocket的完整实现:

#include "StdAfx.h"

#include "./udpsocket.h"

 

CUDPSocket::CUDPSocket(void)

{

       m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

}

 

CUDPSocket::~CUDPSocket(void)

{

       closesocket(m_socket);

}

 

void CUDPSocket::Init(UINT port, HWND hWnd)

{

       m_addr.sin_family = AF_INET;

       m_addr.sin_port = htons(port);

       if (hWnd != NULL)

       {

              m_addr.sin_addr.s_addr = htonl(INADDR_ANY);

              bind(m_socket, (SOCKADDR*)&m_addr, sizeof(m_addr));

              WSAAsyncSelect(m_socket, hWnd, WM_UDP, FD_READ);

       }

}

 

void CUDPSocket::Send(const char* buffer, int length, const char* address)

{

       if (address != NULL) m_addr.sin_addr.s_addr = inet_addr(address);

       sendto(m_socket, buffer, length, 0, (SOCKADDR*)&m_addr, sizeof(m_addr));

}

 

int CUDPSocket::Receive(char* buffer, int length)

{

       int i = sizeof(m_addr);

       return recvfrom(m_socket, buffer, length, 0, (SOCKADDR*)&m_addr, &i);

}

CMainDlg只需要自己实现三个函数:

LRESULT CMainDlg::OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)

{

       CenterWindow();

       HICON hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),

       IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);

       SetIcon(hIcon, TRUE);

       HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),

       IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);

       SetIcon(hIconSmall, FALSE);

       m_address = "127.0.0.1";

       DoDataExchange(FALSE);

       m_listen.Init(7788, m_hWnd);

       m_talk.Init(7788);

       return TRUE;

}

LRESULT CMainDlg::OnBnClickedSend(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)

{

       DoDataExchange(TRUE);

       m_name.AppendChar(':');

       m_name.Append(m_msgSend);

       m_talk.Send(m_name, m_name.GetLength(), m_address);

       m_msg.Append(m_name);

       m_msg.Append("/r/n");

       DoDataExchange(FALSE, IDC_MSG);

       return 0;

}

 

LRESULT CMainDlg::OnUDP(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)

{

       char buf[1024];

       int length = m_listen.Receive(buf, 1024);

       m_msg.Append(">>");

       m_msg.Append(buf, length);

       m_msg.Append("/r/n");

       DoDataExchange(FALSE, IDC_MSG);

       return 0;

}

最后,修改_tWinMain()

在主程序入口处添加

WSADATA wsaData;

WSAStartup(MAKEWORD(2,2), &wsaData);

在主程序的出口添加

WSACleanup();

这就是大致了,整理一下应该就可以运转了,没有人和你聊不要紧,在地址里添127.0.0.1自己和自己聊。

上面用到的socket函数简单介绍:

SOCKET socket(int af, int type, int protocol) 创建一个socket

closesocketSOCKET s 关闭一个socket

int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen) 无连接通信接收函数

int sendto(SOCKET s, const char FAR* buf, int len, int flags, const struct sockaddr FAR* to, int tolen) 无连接通信发送函数

int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent) 使一个socket可以接收到以Windows消息为基础的网络事件通知

具体的参数含义可以查MSDN或者Internet

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值