C语言实现udp客户端

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);
}

你可能感兴趣的:(C/C++,udp,c语言,网络协议)