前几天处理一个关于UDP的项目,同一个程序里要使用两个UDP通信的套接字。我原计划分别使用2个QT的QUDPSocket类,结果发现使用两个QUDPSocket,造成其中一个QUDPSocket不能正常通信。所以只好用C++原生 的网络通信函数结合QThread完成UDP通信。从这件事之后,我意识到网络通信最好的选择是采用原生C++函数。从这篇博客开始,我将针对UDP通信给出编程实例,供大家参考。
本篇的例子介绍在linux平台下,不使用bind函数,实现UDP通信。提到udp和tcp通信,我以前首先想到的是如下三步:
1 建立套接字
2 绑定IP和端口
3 假如是TCP客户端,还要connect
事实上,对于UDP来说,第二步也可以省略。对于TCP客户端而言,第二步也是不必要的(后面的博客还会给具体例子)。
事实上,即使不调用bind,套接字也会拥有一个端口,只是这个端口不是由程序员指定的。既然拥有端口,这个套接字也就可以收发数据。下面的代码展示如何在不使用bind的情况下,与命令行的nc程序通信。nc占用的端口号是8080
UDP类的头文件"udpanonymous.h"
#ifndef UDPANONYMOUS_H
#define UDPANONYMOUS_H
#include <stdio.h>
#include <sys/socket.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
class udpAnonymous
{
public:
udpAnonymous();
~udpAnonymous();
static udpAnonymous * pGetInstance(void);
int m_skt;
struct sockaddr_in m_sinDst;//destination to receive msg
bool bInit(char * pAddrIP, int iPort);
bool bSend(char *, int);
};
#endif // UDPANONYMOUS_H
UDP的cpp文件:
#include "udpanonymous.h"
/*ref: https://lobert.iteye.com/blog/1769618*/
udpAnonymous * g_pUdpAnynms = NULL;
udpAnonymous::udpAnonymous()
{
/* socket returns -1 if failed. see "man socket"*/
m_skt = socket(PF_INET,SOCK_DGRAM,0);
g_pUdpAnynms = this;
}
bool udpAnonymous::bInit(char * pAddrIP, int iPort)
{
memset(&m_sinDst,0,sizeof(struct sockaddr_in));
/*****************************************************
* nt inet_pton(int af, const char *src, void *dst);
这个函数转换字符串到网络地址,第一个参数af是地址簇,
第二个参数*src是来源地址,第三个参数* dst接收转换后的数据
若成功则为1,若输入不是有效的表达格式则为0,若出错则为-1
http://www.cnblogs.com/loanhicks/p/7368151.html
**************************************************/
if(inet_pton(AF_INET,pAddrIP,&(m_sinDst.sin_addr))==1)
{
m_sinDst.sin_family = AF_INET;
m_sinDst.sin_port = htons(iPort);
return true;
}
else
{
return false;
}
}
udpAnonymous::~udpAnonymous()
{
close(m_skt);
}
bool udpAnonymous::bSend(char * pData, int iLen)
{
/*********************************
* https://lobert.iteye.com/blog/1769618
* int sendto ( int s , const void * msg, int len, unsigned int flags, const
struct sockaddr * to , int tolen ) ;
sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,
如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据内容,参数flags 一般设0,
详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。
参数tolen为sockaddr的结果长度。
成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。*/
if(-1 != m_skt)
{
int iRet = sendto(m_skt,(void *)pData, iLen, 0,
(struct sockaddr *)&m_sinDst,sizeof(struct sockaddr_in));
if(-1 == iRet)
return false;
else
return true;
}
else
{
return false;
}
}
udpAnonymous * udpAnonymous::pGetInstance(void)
{
return g_pUdpAnynms;
}
mainwindow的cpp文件:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "udpanonymous.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
udpAnonymous * pUDP = udpAnonymous::pGetInstance();
if(pUDP)
{
QString qstrIP=ui->edtIP->text();
QString qstrPort = ui->edtPort->text();
int iPort = qstrPort.toInt();
bool b = pUDP->bInit(qstrIP.toLatin1().data(), iPort);
if(b)
{
QString qstrMsg = ui->edtMsg->text();
pUDP->bSend(qstrMsg.toLatin1().data(), qstrMsg.size());
}
}
}
通信效果:
以上代码可在我的资源里下载。上面介绍的是从一个没有bind语句的UDP处向一个已知端口发送数据。下面讨论一下这个已知端口是否能反过来向对方,也就是向着没有bind的UDP处发数据。
假如不使用bind,则程序员不能指定其端口号。既然端口号未知,似乎没有bind的UDP是不能接收数据的。但是,利用recvfrom函数是可以查出发送方的IP和端口号的。也就是说,接收方一旦收到了数据,也就知道了发送方的地址和端口,也就可以向这个“端口不明”的UDP处发送消息。