C语言实现udp客户端,子线程轮询接收数据,QT的界面框架,QMainWindow使用回调函数接收数据,支持windows和linux跨平台。
QMainWindow调用udp
///接收数据回调函数
void udpReceiveMsg(char *data, int32_t nb_data, void *user)
{
MainWindow * mw = (MainWindow*)user;
if(mw == nullptr) return;
mw->UdpRevMsgCallBack(data,nb_data);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
UdpHandle *pUdpHandle = (UdpHandle *)malloc(sizeof(UdpHandle));
pUdpHandle->receive = udpReceiveMsg;
pUdpHandle->user = this;
udp_create(pUdpHandle,(char*)"192.168.72.35",9000,9090);
udp_start(pUdpHandle);
}
MainWindow::~MainWindow()
{
udp_destroy(pUdpHandle);
delete ui;
}
///接收数据处理
void MainWindow::UdpRevMsgCallBack(char *data, int32_t nb_data)
{
QByteArray array(data,nb_data);
qDebug()<<"["<<__FILE__<<"]"<<__LINE__<<__FUNCTION__<<" "<<array;
}
///数据发送
void MainWindow::on_pushButton_clicked()
{
udp_sendData(pUdpHandle,ui->textEdit->toPlainText().toLatin1().data(),ui->textEdit->toPlainText().size());
}
UdpClient.h
#ifndef UDPHANDLE_H_
#define UDPHANDLE_H_
#include
#include
#ifdef _WIN32
#include
#include
//#define SOCKADDR_IN
#define socklen_t int
#else
#include
#include
#include
#endif
typedef struct {
int32_t fd;
int32_t localPort;
int32_t remotePort;
int32_t isStart;
int32_t isLoop;
int32_t notRemoteInit;
pthread_t threadId;
//char serverIp[30];
void* user;
struct sockaddr_in local_addr;
struct sockaddr_in remote_addr;
void (*receive)(char *data, int32_t nb_data,void* user);
void (*startStunTimer)(void* user);
}UdpHandle;
#ifdef __cplusplus
extern "C"{
#endif
int32_t udp_create(UdpHandle* udp,char* remoteIp,int32_t remotePort,int32_t plocalPort);//创建udp套接字
void udp_destroy(UdpHandle* udp);//销毁套接字
void udp_start(UdpHandle* udp);//创建线程,启动udp
void udp_stop(UdpHandle* udp);//停止线程处理函数
int32_t udp_sendData(UdpHandle* udp,char* p,int32_t plen);//udp发送数据
#ifdef __cplusplus
}
#endif
#endif
UdpClient.c
#include
#include
#include
#include
#include "UdpClient.h"
#ifndef _WIN32
#define GetSockError() errno
#define SetSockError(e) errno = e
#define closesocket(s) close(s)
#endif
/**
* @brief udp_create 创建udp套接字
* @param udp udp客户端结构体
* @param remoteIp
* @param remotePort
* @param plocalPort
* @return 0成功,-1失败
*/
int32_t udp_create(UdpHandle *udp, char *remoteIp,int32_t remotePort,int32_t plocalPort)
{
if (udp == NULL) return -1;
udp->localPort = plocalPort;
udp->fd = -1;
udp->remotePort = remotePort;
#ifdef _WIN32
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);//初始化socket库
#endif
udp->local_addr.sin_family = AF_INET;
udp->local_addr.sin_port = htons(udp->localPort);
udp->local_addr.sin_addr.s_addr = INADDR_ANY;
/**
* socket,创建套接字
* 参数1,domain:AF_INET,PF_INET代表IPV4,PF_INET6代表TPV6等
* 参数2,type:SOCK_STREAM代表TCP连接,SOCK_DGRAM支持UDP连接
* 参数3,protocol:用于制定某个协议的特定类型type,某协议中只有一种特定类型则为0
* 返回值,成功返回一个标识这个套接字的文件描述符,失败返回-1
**/
udp->fd = socket(AF_INET, SOCK_DGRAM, 0);
if(udp->fd < 0)
{
#ifdef _WIN32
printf("create socket error,udp->fd=%d,WSAGetLastError=%d\n",udp->fd,WSAGetLastError());///WSAGetLastError返回当前错误码,windowsAPI
#else
printf("create socket error,udp->fd=%d\n",udp->fd);
perror("error:");///linuxAPI,打印输出上一个函数发生的错误,"error:"会首先打印
#endif
return -1;
}
udp->remote_addr.sin_family = AF_INET;
/**
* htons,将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
* 参数1,u_short hostshort: 16位无符号整数
* 返回值:TCP / IP网络字节顺序
**/
udp->remote_addr.sin_port = htons(udp->remotePort);
printf("udp->remote_addr.sin_port=%d\n",udp->remote_addr.sin_port);
udp->notRemoteInit=remotePort>0?0:1;
/**
* inet_addr,将一个点分十进制的IP转换成一个长整型数(u_long类型)
* 参数1,const char* strptr,传统IP地址
* 返回值:成功将字符串转换为32位二进制网络字节序的IPV4地址,失败返回INADDR_NONE
**/
#ifdef _WIN32
udp->remote_addr.sin_addr.S_un.S_addr=inet_addr(remoteIp);
#else
udp->remote_addr.sin_addr.s_addr = inet_addr(remoteIp);
#endif
return 0;
}
/**
* @brief udp_destroy 销毁套接字
* @param udp
*/
void udp_destroy(UdpHandle *udp)
{
if (udp == NULL)
return;
udp_stop(udp);
if (udp->fd > 0)
{
/**
* closesocket,关闭套接字
* 参数1,s:套接口描述字
* 返回值:成功返回0,失败返回SOCKET_ERROR错误
**/
closesocket(udp->fd);
udp->fd = -1;
}
}
/**
* @brief udp_sendData udp发送数据
* @param udp UdpHandle
* @param data 数据
* @param nb 数据长度
* @return 成功则返回实际传送出去的字符数, 失败返回-1
*/
int32_t udp_sendData(UdpHandle *udp, char *data, int32_t nb)
{
if (udp == NULL || !udp->isStart || udp->notRemoteInit || data==NULL) return -1;
return sendto(udp->fd, data, nb, 0, (struct sockaddr*) &udp->remote_addr,sizeof(struct sockaddr)) > 0 ? 0 : 1;
/**
* sendto,socket发送数据
* 参数1,s 为已建好连接的socket
* 参数2,msg 为发送的数据内容
* 参数3,len为发送的数据长度
* 参数4,flags 一般设0
* 参数5,to 用来指定欲传送的网络地址
* 参数6,tolen 为sockaddr 的结果长度
* 返回值:成功则返回实际传送出去的字符数, 失败返回-1
**/
}
/**
* @brief run_udp_thread udp线程处理函数
* @param obj UdpHandle
* @return
*/
void* run_udp_thread(void *obj)
{
UdpHandle *udp = (UdpHandle*) obj;
udp->isStart = 1;
/**
* setsockopt,获取或者设置与某个套接字关联的选项
* 参数1,sock:将要被设置或者获取选项的套接字。
* 参数2,level:选项所在的协议层。
* 参数3,optname:需要访问的选项名。
* 参数4,tv_out:recv等函数默认为阻塞模式(block),当超过tv_out设定的时间而没有数据到来时recv()就会返回0值
* 参数5,optlen:选项的长度
* 返回值:成功将字符串转换为32位二进制网络字节序的IPV4地址,失败返回INADDR_NONE
**/
#ifdef _WIN32
int32_t timeout=200;
setsockopt(udp->fd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &timeout, sizeof(timeout));
#else
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 200000; // 200 ms
setsockopt(udp->fd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv, sizeof(struct timeval));
#endif
printf("udp server is starting,localPort=%d\n", udp->localPort);
/**
* bind,将套接字文件描述符、端口号和ip绑定到一起
* 参数1,sockfd 表示socket函数创建的通信文件描述符
* 参数2,addr 表示struct sockaddr的地址,用于设定要绑定的ip和端口
* 参数3,addrlen 表示所指定的结构体变量的大小
* 返回值:成功返回0,失败返回-1
**/
if (bind(udp->fd, (struct sockaddr*) &udp->local_addr,sizeof(struct sockaddr_in)) < 0)
{
printf("Udp server bind error\n");
#ifdef _WIN32
printf("WSAGetLastError=%d\n",WSAGetLastError());
#else
perror("error:");
#endif
return NULL;
}
char buffer[2048] = { 0 };
udp->isLoop = 1;
int32_t len = 0;
// if (!udp->notRemoteInit&&udp->startStunTimer) udp->startStunTimer(udp->user);
socklen_t src_len = sizeof(struct sockaddr_in);
while (udp->isLoop)
{
struct sockaddr_in src;
memset(&src, 0, src_len);
memset(buffer, 0, 2048);
/**
* recvfrom,接收一个数据报并保存源地址
* 参数1,s:标识一个已连接套接口的描述字
* 参数2,buf:接收数据缓冲区
* 参数3,len:缓冲区长度
* 参数4,flags:调用操作方式,一般设置为0
* 参数5,from:(可选)指针,用来指定欲接收数据的网络地址
* 参数6,fromlen:(可选)指针,指向from长度值
* 返回值:成功则返回接收到的字符数,失败返回-1
**/
if ((len = recvfrom(udp->fd, buffer, 2048, 0, (struct sockaddr*) &src, &src_len)) > 0)
{
if(udp->notRemoteInit)
{
udp->remotePort = ntohs(src.sin_port);
udp->remote_addr.sin_port = src.sin_port;//htons(udp->remotePort);
#ifdef _WIN32
udp->remote_addr.sin_addr.S_un.S_addr=src.sin_addr.S_un.S_addr;
#else
udp->remote_addr.sin_addr.s_addr = src.sin_addr.s_addr;
#endif
udp->notRemoteInit=0;
//if (udp->startStunTimer) udp->startStunTimer(udp->user);
}
if (udp->receive) udp->receive(buffer, len, udp->user);
}
}
udp->isStart = 0;
closesocket(udp->fd);
udp->fd = -1;
return NULL;
}
/**
* @brief udp_start 创建线程,启动udp
* @param udp UdpHandle
*/
void udp_start(UdpHandle *udp)
{
if (udp == NULL) return;
if (pthread_create(&udp->threadId, 0, run_udp_thread, udp))
{
printf("start could not start thread\n");
}
}
/**
* @brief 停止线程处理函数
* @param udp
*/
void udp_stop(UdpHandle *udp)
{
if (udp == NULL) return;
udp->isLoop = 0;
while (udp->isStart)
usleep(1000);
}