C++中UDP通讯详解

2023-05-16


C++ Socket编程及TCP/UDP通信代码实现
一、简介
Socket编程的目的是使网络的进程进行通信,基于TCP/IP协议簇,通过三元组(ip地址、协议、端口)标志进程,并通过该标志与其他进行进行交互。使用TCP/IP协议的应用程序通常采用应用编程接口,套接字Socket是当前的主流通信方式,“一切皆可Socket”。

Socket通信流程图:


二、Socket是什么
网络的进程通过Socket进行通信,Socket本身起源于Unix,基于“一切皆文件”理论,通过“打开->读写->关闭”的模式进行操作,而Socket本身也是基于这种模式,可以把Socket理解成一种特殊的文件,而Socket函数就是对其进行操作,如打开、读、写、关闭等。


三、Socket的基本操作
1.socket()函数
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

函数描述:创建一个socket描述符,唯一标志一个socket,用于后续的读写操作
参数解释:
domain:协议域(协议簇),决定了socket的地址类型,常用的协议有AF_INET(IPV4互联网协议簇)、PF_INET6/AF_INET6(IPV6互联网协议簇)、AF_UNIX/AF_UNIX(要用一个绝对路径名作为地址)。
type:指socket类型,有面向连接的套接字(SOCK_STREAM)和面向消息的套接字(SOCK_DGRAM),其中面向连接的套接字可以理解成TCP协议,数据稳定、按序传输,不存在数据边界,且收发数据在套接字内部有缓冲,所以服务器和客户端进行I/O操作时并不会马上调用,可能分多次调用;面向消息的套接字可以看做UDP,特点:快速传输、有数据边界、数据可能丢失、传输数据大小受限。
protocol:指计算机间通信中使用的协议信息。一般都可以为0(当protocol为0时,会自动选择type类型对应的默认协议。),如果同一协议簇中存在多个数据传输方式相同的协议,则才用第三个参数。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等。

2.bind()函数
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数描述:把一个地址族中的特定地址赋给socket(如果一个TCP客户或者服务器不用bind绑定一个端口,则调用connect或listen时,内核就要为相应的套接字选择一个临时端口(一般TCP客户端可这样做,TCP服务器不可,因为服务器的端口是大家熟知的))
参数解释:
sockfd:指的是通过socket()创建的描述字,唯一标识一个socket。
addr:一个指针,指向要绑定的协议地址。

struct sockaddr_in {
    sa_family_t      sin_family;   //地址簇,取值AF_INET(IPV4),AF_INET6(IPV6)
    unit16_t         sin_port;     //16位TCP/UDP端口号,以网络字节序保存
    struct in_addr   sin_addr;     //32位IP地址,以网络字节序保存
    char             sin_zero[8];  //不使用,但一般初始化为0
}

struct in_addr {
    In_addr_t    s_addr;   //32位IP地址
}

addrlen:对应的是地址的长度。
注意:
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
相关知识:

主机字节序:有大端和小端模式
(1)小端模式:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
(2)大端模式:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。
//short是16位,一般用于端口号,long一般用于地址
unsigned short htons(unsigned short);   //host to network把short类型数据
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

//还有两个可以把字符串形式的IP地址转换成32位整数型数据,成功调用后返回32位大端整数
in_addr_t inet_addr(const char * string);
int inet_aton(const char * string, struct in_addr * addr);
 
//iner_aton可以将转换后的IP地址信息带入sockaddr_in结构体中声明in_addr结构体变量。
char * inet_ntoa(struct in_addr adr);  //将网络字节序整数型IP地址转换成字符串
 
//以下两个函数ipv4和ipv6通用
//将点分十进制的ip地址转化为用于网络传输的数值格式
int inet_pton(int family, const char *strptr, void *addrptr);  
//将数值格式转化为点分十进制的ip地址格式
const char *inet_ntop(int family,const void *addrptr, char *strptr, size_t len);  


因此,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是大端模式。

3.listen()函数
#include <sys/socket.h>
int listen(int sockfd, int backlog);

函数描述:listen将socket设置为被动监听类型,等待客户的连接请求。
参数解释:
sockfd:要监听的socket描述字。
backlog:可以排队的最大连接个数。

4.connect()函数
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数描述:客户端调用connect()来连接服务器,进行信息传输。
参数解释:
sockfd:客户端的socket描述字。
addr:服务器的socket地址。
addrlen:socket地址的长度。

5.accept()函数
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

函数描述:TCP服务器在依次调用socket()、bind()、listen()后,开始监听指定socket地址,接着调用accept()获取请求,建立连接;TCP客户端依次调用socket()、connect()就可以发送连接请求。
参数解释:
sockfd:服务器的socket描述字。
addr:指向 struct sockaddr 的指针,用于返回客户端的协议地址。
addrlen:协议地址的长度。
返回值:
连接成功,会返回由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。
注意:
内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

6.read()、write()函数
网络I/O操作有以上几组,推荐使用recvmsg() - sendmsg()。

read() - write()
recv() - send()
readv() - writev()
recvmsg() - sendmsg()
recvfrom() - sendto()

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);


#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);


函数描述:上述描述了几组I/O操作的函数
参数解释:
read:负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题
write:将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置error变量。

write的返回值大于0,表示写了部分或者是全部的数据。
返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
7.close()函数
#include <unistd.h>
int close(int fd);

函数描述:数据传输结束时调用close()函数。close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。

三、Win10下VS2019实现TCP Socket通信demo(多线程)
1.环境配置
在win10环境下直接使用 #include <sys/socket.h> 会报错,所以需要改用 #include <WinSock2.h> 实现通信,配置过程如下:

[右键]项目->属性->链接器->输入->附加依赖项->编辑->添加ws2_32.lib,取消勾选"从父级或项目默认设置继承"


属性->C/C+±>常规->SDL检查 设置为否

配置结束后即可开始通信。

2.TCP服务器代码(多线程)
#include <stdio.h>
#include <WinSock2.h> //windows socket的头文件
#include <Windows.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <process.h>
#pragma comment(lib, "ws2_32.lib") //连接winsock2.h的静态库文件
using namespace std;

//mutex 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
//同一时刻,只能有一个线程持有该锁。
mutex m;

//定义结构体用来设置
typedef struct my_file
{
    SOCKET clientSocket; //文件内部包含了一个SOCKET 用于和客户端进行通信
    sockaddr_in clientAddr; //用于保存客户端的socket地址
    int id; //文件块的序号
}F;

DWORD WINAPI transmmit(const LPVOID arg)
{
    //实际上这里为了追求并发性不应该加锁,上锁是为了方便看输出
    m.lock();

    F* temp = (F*)arg;
    //获取文件的序号
    //int file_id = temp->id;
    //获取客户机的端口号
    //ntohs(temp -> clientAddr.sin_port); 
    cout << "测试开始,等待客户端发送消息..." << endl;
    //从客户端处接受数据
    char Buffer[MAXBYTE] = { 0 }; //缓冲区
    recv(temp->clientSocket, Buffer, MAXBYTE, 0); //recv方法 从客户端通过clientScocket接收
    cout << "线程" << temp->id << "从客户端的" << ntohs(temp->clientAddr.sin_port) << "号端口收到:" << Buffer << endl;

    //发送简单的字符串到客户端
    const char* s = "Server file";
    send(temp->clientSocket, s, strlen(s) * sizeof(char) + 1, NULL);
    cout << "线程" << temp->id << "通过客户端的" << ntohs(temp->clientAddr.sin_port) << "号端口发送:" << s << endl;

    m.unlock();

    return 0;
}

int main()
{
    //加载winsock库,第一个参数是winsocket load的版本号(2.3)
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 3), &wsaData);
    
    //创建服务器端的socket(协议族, sokcet类型)
    SOCKET servSocket = socket(AF_INET, SOCK_STREAM, 0);//如果改成SOCK_DGRAM则使用UDP
    
    // 初始化socket信息
    sockaddr_in servAddr; //服务器的socket地址,包含sin_addr表示IP地址,sin_port保持端口号和sin_zero填充字节
    memset(&servAddr, 0, sizeof(SOCKADDR)); //初始化socket地址
    
    //设置Socket的连接地址、方式和端口,并绑定
    servAddr.sin_family = PF_INET; //设置使用的协议族
    servAddr.sin_port = htons(2017); //设置使用的端口
    servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //设置绑定的IP地址
    ::bind(servSocket, (SOCKADDR*)&servAddr, sizeof(SOCKADDR)); //将之前创建的servSocket和端口,IP地址绑定

    HANDLE hThread[20]; //获取句柄
    listen(servSocket, 20); //监听服务器端口,最多20个连接
    for (int i = 0; i < 20; i++)
    {
        F* temp = new F; //创建新的传输结构体
        sockaddr_in clntAddr;
        int nSize = sizeof(SOCKADDR);
        SOCKET clientSock = accept(servSocket, (SOCKADDR*)&clntAddr, &nSize);
        //temp数据成员赋值
        temp->clientSocket = clientSock;
        temp->id = i + 1;
        temp->clientAddr = clntAddr;
        //通过句柄创建子线程
        hThread[i] = CreateThread(NULL, 0, &transmmit, temp, 0, NULL);
    }

    //等待子线程完成
    WaitForMultipleObjects(20, hThread, TRUE, INFINITE);
    cout << WSAGetLastError() << endl; //查看错误信息

    //关闭socket,释放winsock
    closesocket(servSocket);
    WSACleanup();

    cout << "服务器连接已关闭。" << endl;
    system("pause");

    return 0;
}


3.TCP客户端代码
#include <stdio.h>
#include <WinSock2.h> //windows socket的头文件
#include <Windows.h>
#include <iostream>
#include <thread>
#include <process.h>
#pragma comment(lib, "ws2_32.lib") //连接winsock2.h的静态库文件
using namespace std;

int main()
{
    //加载winsock库
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2, 3), &wsadata);

    //客户端socket
    SOCKET clientSock = socket(PF_INET, SOCK_STREAM, 0);
    
    //初始化socket信息
    //memset:作用是在一段内存块中填充某个给定的值,它对较大的结构体或数组进行清零操作的一种最快方法。
    sockaddr_in clientAddr;
    memset(&clientAddr, 0, sizeof(SOCKADDR));
    
    //设置Socket的连接地址、方式和端口
    clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientAddr.sin_family = PF_INET;
    clientAddr.sin_port = htons(2017);
    
    //建立连接
    connect(clientSock, (SOCKADDR*)&clientAddr, sizeof(SOCKADDR));
    cout << "已建立连接。" << endl;

    //发送消息
    char* s = new char[100];
    cout << "请输入你要发送的文字消息: ";
    cin >> s;
    send(clientSock, s, strlen(s) * sizeof(char) + 1, NULL);
    cout << "已发送:" << s << endl;

    //接收消息
    system("pause");
    char Buffer[MAXBYTE] = { 0 };
    recv(clientSock, Buffer, MAXBYTE, 0);
    cout << "通过端口:" << ntohs(clientAddr.sin_port) << "接收到:" << Buffer << endl;

    //关闭连接
    closesocket(clientSock);
    WSACleanup();
    cout << "客户端连接已关闭。" << endl;
    
    system("pause");
    return 0;
}


四、Win10下VS2019实现UDP Socket通信demo
1.环境配置
在win10环境下直接使用 #include <sys/socket.h> 会报错,所以需要改用 #include <WinSock2.h> 实现通信,配置过程如下:

[右键]项目->属性->链接器->输入->附加依赖项->编辑->添加ws2_32.lib,取消勾选"从父级或项目默认设置继承"


属性->C/C+±>常规->SDL检查 设置为否

配置结束后即可开始通信。

2.UDP服务器
#include <Winsock2.h>
#include <stdio.h>

void main()
{
    //加载套接字库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1, 1);

    err = WSAStartup(wVersionRequested, &wsaData);//错误会返回WSASYSNOTREADY
    if (err != 0)
    {
        return;
    }

    if (LOBYTE(wsaData.wVersion) != 1 ||     //低字节为主版本
        HIBYTE(wsaData.wVersion) != 1)      //高字节为副版本
    {
        WSACleanup();
        return;
    }

    printf("server is operating!\n\n");
    //创建用于监听的套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);//失败会返回 INVALID_SOCKET
    //printf("Failed. Error Code : %d",WSAGetLastError())//显示错误信息

    SOCKADDR_IN addrSrv;     //定义sockSrv发送和接收数据包的地址
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);

    //绑定套接字, 绑定到端口
    bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));//会返回一个SOCKET_ERROR
    //将套接字设为监听模式, 准备接收客户请求


    SOCKADDR_IN addrClient;   //用来接收客户端的地址信息
    int len = sizeof(SOCKADDR);
    char recvBuf[100];    //收
    char sendBuf[100];    //发
    char tempBuf[100];    //存储中间信息数据

    while (1)
    {

        //等待并数据
        recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
        if ('q' == recvBuf[0])
        {
            sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrClient, len);
            printf("Chat end!\n");
            break;
        }
        sprintf_s(tempBuf, "%s say : %s", inet_ntoa(addrClient.sin_addr), recvBuf);
        printf("%s\n", tempBuf);

        //发送数据
        printf("Please input data: \n");
        gets_s(sendBuf);
        sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrClient, len);
    }
    closesocket(sockSrv);
    WSACleanup();
}


3.UDP客户端
#include <Winsock2.h>
#include <stdio.h>

void main()
{
    //加载套接字库
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD(1, 1);

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

    if (LOBYTE(wsaData.wVersion) != 1 ||     //低字节为主版本
        HIBYTE(wsaData.wVersion) != 1)      //高字节为副版本
    {
        WSACleanup();
        return;
    }

    printf("Client is operating!\n\n");
    //创建用于监听的套接字
    SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

    sockaddr_in  addrSrv;
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//输入你想通信的她(此处是本机内部)
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(6000);


    int len = sizeof(SOCKADDR);

    char recvBuf[100];    //收
    char sendBuf[100];    //发
    char tempBuf[100];    //存储中间信息数据

    while (1)
    {

        printf("Please input data: \n");
        gets_s(sendBuf);
        sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
        //等待并数据
        recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrSrv, &len);

        if ('q' == recvBuf[0])
        {
            sendto(sockSrv, "q", strlen("q") + 1, 0, (SOCKADDR*)&addrSrv, len);
            printf("Chat end!\n");
            break;
        }
        sprintf_s(tempBuf, "%s say : %s", inet_ntoa(addrSrv.sin_addr), recvBuf);
        printf("%s\n", tempBuf);

        //发送数据

    }
    closesocket(sockSrv);
    WSACleanup();
}

/******************************
C++完成UDP接收数据功能
#include <stdio.h>
#include <WinSock2.h>
 
#pragma comment(lib,"WS2_32.lib")
 
int main(void)
{
    WSADATA wsd;    // 初始化Socket的变量
    SOCKET s;        // 用于通信的Socket句柄
    SOCKADDR_IN sRecvAddr, sSendAddr;    // 分别为接收地址和发送地址
    USHORT uPort = 14555;                // 通信端口
    CHAR szBuf[1024] = { 0 };            // 通信数据缓冲区
    int nBufLen = 1024, nResult = 0, nSenderAddrSize = sizeof(sSendAddr);
 
    // 初始化Socket2.2版本
    nResult = WSAStartup(MAKEWORD(2, 2), &wsd);
    if (nResult != NO_ERROR)
    {
        printf("WSAStartup failed:%d\n", WSAGetLastError());
        return 1;
    }
 
    // 创建一个Socket,SOCK_DGRAM表示UDP类型
    s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (s == INVALID_SOCKET)
    {
        printf("socket failed:%d\n", WSAGetLastError());
        return 1;
    }
 
    // 填充Socket接口
    sRecvAddr.sin_family = AF_INET;        // 地址协议,AF_INET支持TCP和UDP
    sRecvAddr.sin_port = htons(uPort);    // 通信端口,htons转为网络字节顺序
    //INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP
    sRecvAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);    // 接收任意地址数据
 
    // 绑定Socket至本机
    nResult = bind(s, (SOCKADDR *)&sRecvAddr, sizeof(sRecvAddr));
    if (nResult != 0)
    {
        printf("bind failed:%d\n", WSAGetLastError());
        return 1;
    }
 
    printf("Waiting recv data...\n");
 
    while (1)
    {
        // 阻塞式接收数据
        nResult = recvfrom(s, szBuf, nBufLen, 0, (SOCKADDR *)&sSendAddr, &nSenderAddrSize);
        if (nResult == SOCKET_ERROR)
        {
            printf("recvfrom failed:%d\n", WSAGetLastError());
        }
        else
        {
            //printf("SenderIP  :%s\n", inet_ntoa(sSendAddr.sin_addr));
            printf("SenderData:%s\n", szBuf);
        }
    }
    // 关闭Socket连接
    nResult = closesocket(s);
    if (nResult == SOCKET_ERROR)
    {
        printf("closesocket failed:%d\n", WSAGetLastError());
        return 1;
    }
    // 清理Socket
    WSACleanup();
 
    system("pause");
    return 0;
}

/***********************************
C++ UDP socket编程
客户端:

//#include "stdafx.h"
#include<stdio.h>
#include<tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
using namespace std;
 
 
#pragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
    WSAData wsd;           //初始化信息
    SOCKET soSend;         //发送SOCKET
    int nRet = 0;
    //int i = 0;
    int dwSendSize = 0;
    SOCKADDR_IN siLocal;    //远程发送机地址和本机接收机地址
 
    //启动Winsock
    if (WSAStartup(MAKEWORD(2,2),&wsd) != 0) {/*进行WinSocket的初始化,
        windows 初始化socket网络库,申请2,2的版本,windows socket编程必须先初始化。*/
        cout << "WSAStartup Error = " << WSAGetLastError() << endl;
        return 0;
    }
 
    //创建socket
 
    //AF_INET 协议族:决定了要用ipv4地址(32位的)与端口号(16位的)的组合
    //SOCK_DGRAM --  UDP类型,不保证数据接收的顺序,非可靠连接;
    soSend = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if (soSend == SOCKET_ERROR) {
        cout << "socket Error = " << WSAGetLastError() << endl;
        return 1;
    }
 
    //设置端口号
    int nPort = 5150;
    siLocal.sin_family = AF_INET;
    siLocal.sin_port = htons(nPort);
    siLocal.sin_addr.s_addr = inet_addr("127.0.0.1");
 
    for (int i = 0; i < 30; i ++){
        //开始发送数据
        //发送数据到指定的IP地址和端口
        nRet = sendto(soSend,"123 mutouren",strlen("123 mutouren"),0,(SOCKADDR*)&siLocal,sizeof(SOCKADDR));
        if (nRet == SOCKET_ERROR) {
            cout << "sendto Error " << WSAGetLastError() << endl;
            break;
        }
    }
    //关闭socket连接
    closesocket(soSend);
    //清理
    WSACleanup();
 
    return 0;
}
服务端:

//#include "stdafx.h"
#include<stdio.h>
#include<tchar.h>
#include <iostream>
#include <WinSock2.h>
#include <Windows.h>
using namespace std;
 
 
#pragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])//_tmain,要加#include <tchar.h>才能用
{
    WSAData wsd;           //初始化信息
    SOCKET soRecv;              //接收SOCKET
    char * pszRecv = NULL; //接收数据的数据缓冲区指针
    int nRet = 0;
    //int i = 0;
    int dwSendSize = 0;
    SOCKADDR_IN siRemote,siLocal;    //远程发送机地址和本机接收机地址
 
    //启动Winsock
    if (WSAStartup(MAKEWORD(2,2),&wsd) != 0) {
        cout << "WSAStartup Error = " << WSAGetLastError() << endl;
        return 0;
    }
 
    //创建socket
    soRecv = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if (soRecv == SOCKET_ERROR) {
        cout << "socket Error = " << WSAGetLastError() << endl;
        return 1;
    }
 
    //设置端口号
    int nPort = 5150;
 
    //int nPort = 1234;
    siLocal.sin_family = AF_INET;
    siLocal.sin_port = htons(nPort);
    siLocal.sin_addr.s_addr = inet_addr("127.0.0.1");
 
    //绑定本地地址到socket
    if (bind(soRecv,(SOCKADDR*)&siLocal,sizeof(siLocal)) == SOCKET_ERROR) {
        cout << "bind Error = " << WSAGetLastError() << endl;
        return 1;
    }
 
    //申请内存
    pszRecv = new char[4096];
    if (pszRecv == NULL) {
        cout << "pszRecv new char Error " << endl;
        return 0;
    }
 
    for (int i = 0; i < 30; i ++){
        dwSendSize = sizeof(siRemote);
        //开始接受数据
        nRet = recvfrom(soRecv,pszRecv,4096,0,(SOCKADDR*)&siRemote,&dwSendSize);
        if (nRet == SOCKET_ERROR) {
            cout << "recvfrom Error " << WSAGetLastError() << endl;
            break;
        }
        else if (nRet == 0) {
            break;
        }
        else{
            pszRecv[nRet] = '\0';
            cout << inet_ntoa(siRemote.sin_addr) << endl
                <<pszRecv << endl;
        }
 
    }
    //关闭socket连接
    closesocket(soRecv);
    delete[] pszRecv;
 
    //清理
    WSACleanup();
    system("pause");
    return 0;
}
 

/******************************************************
class UDP {
public:
    UDP() {
        RecvAddrSize = sizeof(RecvAddr);
        int nResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (nResult != NO_ERROR) {
            std::cout << WSAGetLastError();
            return;
        }
        RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        //设置服务器地址
        RecvAddr.sin_family = AF_INET;
        RecvAddr.sin_port = htons(Port);
        RecvAddr.sin_addr.s_addr = inet_addr("192.168.1.111");
        int ret = bind(RecvSocket, (sockaddr *)&RecvAddr, sizeof(RecvAddr));
        if (ret < 0) {
            perror("bind");
            return;
        }
    }
    void sendDataToPython() {char temp[2000];
        sprintf(temp, "insert into Flight_data.AirData({:}, cdata) values({:}, '{:}')", m_str, s_str, date_time);
        int flag = sendto(RecvSocket, temp, sizeof(temp), 0, (sockaddr*)&RecvAddr, sizeof(RecvAddr));
        std::cout << flag;
        }
    }
    void recvDataToLocal() {
            memset(RecvBuf, '\0', sizeof(RecvBuf));
            printf("recv a datagram to the receiver...\n");
            int nResult = recvfrom(RecvSocket, RecvBuf, BufLen, 0, (SOCKADDR *)&RecvAddr, &RecvAddrSize);
            //发送完成,关闭Socket
            printf("%s\n", RecvBuf);
    }
    ~UDP() {
        closesocket(RecvSocket);
        WSACleanup();
    }
private:
    WSADATA wsaData;//初始化
    SOCKET RecvSocket;
    sockaddr_in RecvAddr, sendAddr;//服务器地址
    char RecvBuf[10240] = { 0 };//发送数据的缓冲区
    int Port = 4000;//服务器监听地址
    int BufLen = 10240;//缓冲区大小
    int RecvAddrSize = 0; //初始化SocketAddr的大小
};  

/**************************************

1、一个socket实现udp收发
socket用于udp通信时,是不区分Server与Client的。因为是无连接的,发送完了也就完了。同样接收到数据也就完成了一次通信。因此,Server端与Client端的措辞在Udp通信中的含义其实就退化了。

将socket用于tcp编程时,都比较喜欢send和recv函数。而用于udp通信编程时,个人感觉用sendto和recvfrom更方便。

因为socket用于udp通信时,如果用一种比较糙的方式实现,是无须connect操作、bind操作的。直接想要发送的时候用sendto指明接收端的参数,想要接收数据时直接用recvfrom指明发送端的参数。

2、粗糙方式
在这种粗糙的方式下,即不加任何bind、connect操作时,仅有一个缺点,也即接收时是无法指定发送方的端口。换句话说,监听的远端机器向本机任何一个端口发送数据,recvfrom都会认为收到数据了。

3、完全方式
而要Debug这个问题,添加一个bind操作即可。而且只需要bind一次。这样,需要的功能就都妥妥的了。指定远端ip的指定端口发送的数据才会触发本机recvfrom的接收。而发送功能,一直是好的。

4、代码
上一点代码。写的比较急,赶出来的。扎眼见谅啊。

1)、创建socket
//创建socket
m_sock = socket(AF_INET, SOCK_DGRAM, 0);

//设定远端地址、端口进行绑定
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.104");
addr.sin_family = AF_INET;
addr.sin_port = htonl(8889);
//绑定远端
int nRet = bind(m_sock, (sockaddr*)&addr, sizeof(addr));
TRACE("BINDRET = %d \r\n", nRet);
TRACE("errcode = %d \r\n", WSAGetLastError());

2)、发送代码
//设定目的端参数
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.104");
addr.sin_family = AF_INET;
addr.sin_port = htonl(8888);
char* buf="hehehahaheihei";
//发送数据
int sendRet=sendto(m_sock, buf, sizeof(buf), 0, (sockaddr*)&addr, sizeof(addr));
TRACE("Send errcode = %d \r\n", WSAGetLastError());
TRACE("sendRet= %d \r\n", sendRet);

3)、接收代码
//存储发送端的参数结构体,可取出ip地址端口号等
sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
//接收缓冲区、参数等
char rcvBuf[20];
int len = 20;
int stSize=sizeof(addr);
int rcvRet = 0;
len = sizeof(addr);
rcvRet=recvfrom(m_sock, rcvBuf, len, 0, (sockaddr*)&addr, &stSize);
//打印出错误信息码
TRACE("rcv errcode = %d \r\n", WSAGetLastError());
TRACE("rcvRet= %d\r\n", rcvRet);

5、注意事项
1)、socket默认是阻塞的
阻塞不影响发送,因为直接发送出去立即就返回了,不管对方是否接收到。
而接收时就会阻塞了,会一直等到有数据到来。
设置为非阻塞代码

ULONG nVal = 1;
ioctlsocket(m_sock, FIONBIO, &nVal);

2)、启动函数
在创建socket之前需要调用启动函数

WSAStartup(
    WORD wVersionRequested,     //版本号
    LPWSADATA lpWSAData         //返回的参数信息。详见msdn
    );

具体调用样例如下:

WSADATA data;
WSAStartup(0X11,&data);

对称的,有一个清理函数。在退出时可以调用。)
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中UDP通讯详解 的相关文章

  • memcached 使用 Django 监听 UDP

    Question 我无法获得memcached正在听UDP 上班 get set delete 与姜戈 我只让 memcached 监听UDP 11211 正如我在上一个问题 https stackoverflow com question
  • 移动提供商无法进行 UDP 打洞

    实际上 我正在编写一个 Android 应用程序 该应用程序接收连接到 PC 的网络摄像头的图片 为了获得更多的 fps 我使用 udp 协议而不是 tcp 这个想法是 电脑将图片发送到手机的 IP 和端口 但电话提供商有不同的公共端口 所
  • DatagramChannel.close() 在 Windows 上保持端口打开

    我正在实施一个发现流程 打开 UDP 套接字以侦听给定端口上的广播响应 发送一些请求 并期待稍后的响应 在给定时间段后关闭 UDP 套接字 第一次通话有效 但其他调用会出现绑定错误 地址已被使用 绑定 我运行的是Windows 7 我做了一
  • UDP sendto 上的 ECONNREFUSED 错误

    我在使用正在写入的应用程序时遇到一些无法解释的行为 使用 sendto 向多个端口发送 UDP 数据 所有端口均使用套接字 PF INET SOCK DGRAM 0 为了一组客户端读取进程的利益 这些 sendto 偶尔会不可预测地触发经济
  • 搜索所有网络上的设备

    我想实现一个代码 通过它我可以列出网络上连接的 upnp 兼容媒体渲染器设备 我用谷歌搜索了这个并找到了以下代码扭曲的网站 https twistedmatrix com documents current core howto udp h
  • Java UDP 服务器,并发客户端

    下面的代码足以接受并发 UDP 传输吗 更具体地说 如果 2 个客户端同时传输 当我调用 receive 时 DatagramSocket 会将传输排队并一一传送它们 还是只有一个能够通过 DatagramSocket socket new
  • iOS 14 在进行本地网络广播时给出“操作系统错误:错误的文件描述符,errno = 9”

    做一点Jeopardy 风格问答 https stackoverflow blog 2011 07 01 its ok to ask and answer your own questions here 我正在 Flutter 中开发一个应
  • 致命错误:netinet/in.h:没有这样的文件或目录

    套接字编程 UDP 服务器 我正在尝试使用 UDP 服务器进行消息加密和解密 代码在这里 https www geeksforgeeks org message encryption decryption using udp server
  • 我刚刚在哪个适配器上收到此 UDP 数据包?

    我正在尝试用 C 编写一个 BOOTP 服务器 我正在接收并解析来自客户端的 BOOTP 数据包 我需要回复我的服务器 IP 地址 问题是 计算机可以有多个网络适配器 客户端还没有 IP 地址 有什么方法可以查出 UDP 数据包是在哪个适配
  • 自 2012 年以来,WinSock 注册 IO 性能是否有所下降?

    我最近使用 MS 为该 API 提供的稍微可接受的文档编写了基于 WinSock Registered IO RIO 的 UDP 接收 最终的性能非常令人失望 单套接字性能有些稳定 约为每秒 180k 数据包 使用多个 RSS 队列 即多个
  • 使用 STUN 打孔

    我目前正在尝试通过 Internet 发送 UDP 消息 并且必须为端点 A 和 B 都位于 NAT 后面 设置防火墙 为此 我想使用 STUN 服务器进行打孔 当 A 创建对 STUN 服务器的请求 例如 私有 85 1 1 12 600
  • 在 macOS 10.12 上绑定到套接字时出现 NSPOSIXErrorDomain

    我正在玩CocoaAsyncSocket https github com robbiehanson CocoaAsyncSocket在 Swift 中绑定到 UDP 套接字并通过本地网络接收消息 我正在初始化一个套接字 并尝试绑定到一个端
  • 为多线程 UDP 客户端执行“close ()”时套接字描述符未释放

    我在下面编写了 UDP 客户端 它基本上生成一个单独的线程来接收数据报 但是数据报仅在主线程中发送 现在 在 Linux 发行版上实例化 udpClient 1 UDP 客户端后按 ctrl D 实现退出循环 围绕 getline 调用 并
  • F1 2019 UDP解码

    我目前正在为 F1 方向盘开发自己的显示器 F1 2019 由codemasters提供 通过UDP发送数据 该数据存储在字节数组中 我在解码返回的数组时遇到一些问题 问题是我得到了很多信息 但我不知道如何处理它们 我将向您介绍我所尝试过的
  • 我应该害怕使用 UDP 进行客户端/服务器广播通话吗?

    我在过去的两天里阅读了每一篇StackOverflow问题和答案 以及googling当然 关于印地TCP and UDP协议 以便决定在我的用户应用程序和 Windows 服务之间的通信方法中应该使用哪一种 从我目前所看到的来看 UDP是
  • 如何读取 UDP 连接直至超时?

    我需要读取 UDP 流量 直到超时 我可以通过在 UDPConn 上调用 SetDeadline 并循环直到出现 I O 超时错误来做到这一点 但这看起来很黑客 基于错误条件的流量控制 下面的代码片段看起来更正确 但并没有终止 在生产中 这
  • 用 C 语言进行非阻塞 udp 套接字编程:我能得到什么?

    我在理解从非阻塞 UDP 套接字返回什么recv recvfrom 时遇到问题 与 TCP 相比更具体一点 如果我错了 请纠正我 阻塞套接字 TCP 或 UDP 在缓冲区中有一些数据之前不会从 recv 返回 这可以是一定数量的字节 TCP
  • 使用多个 NIC 广播 UDP 数据包

    我正在 Linux 中为相机控制器构建嵌入式系统 非实时 我在让网络做我想做的事情时遇到问题 该系统有 3 个 NIC 1 个 100base T 和 2 个千兆端口 我将较慢的连接到相机 这就是它支持的全部 而较快的连接是与其他机器的点对
  • 跨 NAT 的 UDP 客户端无法从服务器接收数据

    我正在尝试在服务器 在公共 IP 上 和客户端 跨 NAT 之间使用 UDP 进行双向通信 我的逻辑是 如果服务器将一些数据发送到 IP 和它接收数据包的端口 客户端仍然应该收到它 因为 NAT 将具有最终将数据包发送到客户端的映射 客户端
  • boost 是否有可移植的方式来使用 ntohl/htonl/ntohs/htons 类型函数?

    我正在使用 UDP 特别是 boost asio ip udp socket 套接字 如果有帮助的话 头文件是什么 我需要哪些标头 类来处理 UDP 提升下的网络字节排序 刚刚发现就足够了 include

随机推荐

  • QT中关于类静态成员的编码格式错误(静态成员初始化必须放在.cpp文件中)

    qt中指针类型的单例 error LNK1169 找到一个或多个多重定义的符号错误原因 xff1a 因为创建了 h文件和 cpp文件 xff0c 但是静态成员却在 h文件中类的外面初始化了成员变量 只有仅存在 h文件声明定义时才能这么用 x
  • 8uftp怎么连到,8uftp怎么连到服务器

    8uftp是一款易用的FTP软件 xff0c 很多刚接触网站建设的人都会用到这个软件 但使用8uftp连接服务器相对来说较为繁琐 xff0c 很多小伙伴都希望能尽量节省工作时间 xff0c 那你就应该试试iis7 作为IIS7服务器管理工具
  • QT中QWIDGET动态增加控件

    QPushButton btn 61 new QPushButton this btn gt show QPushButton buttonTest 61 new QPushButton 动态创建按钮 buttonTest gt setTe
  • QT::::点击退出按钮,退出当前窗口(lamda表达式写法),实现点击按钮打开和关闭窗口

    点击退出按钮 xff0c 退出当前窗口 QObject connect ui pushButton 5 amp QPushButton pressed this QApplication app app gt exit 在这里两个函数一样的
  • C# 中线程同步使用信号量总结

    所谓线程同步 xff0c 就是多个线程在某个对象上执行等待 xff08 也可理解为锁定该对象 xff09 xff0c 直到该对象被解除锁定 C 中对象的类型分为引用类型和值类型 CLR在这两种类型上的等待是不一样的 我们可以简单地理解为在C
  • 对话框及窗体在多线程中的应用(阻塞和非阻塞)

    方式1 阻塞UI线程及消息循环 AutoResetEvent autoResetEvent 61 new AutoResetEvent false Task Factory StartNew 61 gt Form form 61 new F
  • Windows11装新环境问题

    系统重装 1 下载最新的老毛桃U盘启动盘制作工具 xff08 旧版的会不支持windows11启动项 xff09 xff0c 制作U盘启动盘 2 将电脑的bitLocker硬盘加密取消掉在所有设置安全加密里 xff0c 否则将无法启动提示硬
  • c#引用office组件库迁移源码问题

    1 当程序引用了office组件时 xff0c 需要安装与迁移的源码相同版本的offce Library才行 xff0c 否则会报异常 2 可以下载office三合一版快捷安装 xff0c vs在引用里的com选项卡里会自动识别到相应的of
  • TCP和UDP的发送缓冲区和接收缓冲区内存问题

    TCP协议是作用是用来进行端对端数据传送的 xff0c 那么就会有发送端和接收端 xff0c 在操作系统有两个空间即user space和kernal space 每个Tcp socket连接在内核中都有一个发送缓冲区和接收缓冲区 xff0
  • QtCreator修改项目构建目录

    使用QtCreator编译Qt项目时 xff0c 如有需求修改编译过程文件 xff08 即Makefile o exe等文件 xff09 存放目录 xff0c 简单在工具 gt 选项 gt 构建和运行中修改Default build dir
  • QT中删除信号于槽的连接

    如果是在UI里建立的 xff0c 那就在下面这个函数里删除连接槽函数的对应行 void MainForm qt static metacall QObject o QMetaObject Call c int id void a if c
  • C++位操作中按位置0、置1、取反操作

    一 指定的某一位数置1 宏 define setbit x y x 61 1 lt lt y 二 指定的某一位数置0 宏 define clrbit x y x amp 61 1 lt lt y 三 指定的某一位数取反 宏 define r
  • LED恒流驱动IC汇总

    这几天在找LED恒流驱动芯片 xff0c 无意间在LED网论坛上发现这个帖子 xff0c 分享给大家 xff01 LED恒流IC芯片大盘点 韩国LDT LD1016 16位最大90mA LED屏幕 护栏灯管恒流驱动IC LD1048 48位
  • qt C++中指针自动释放内存及程序中的内存操作、管理

    程序加载到内存后代码存储到代码区 xff0c 并将全局变量 静态变量初始化到全局 静态内存区 xff0c 然后会分配2M左右的栈内存区用于存储局部变量 xff0c 并在运行时根据需要可以在堆内存区 空闲内存区及硬盘的虚拟内存区 申请空间 程
  • sqlite数据库文件提示损坏修复方法

    第1章 说明 1 1 下载SQLite Tools 1 2 运行 2 注意 xff1a 为了方便 xff0c 可把要修复的数据库文件直接放到sqlite3 exe路径下然后运行sqlite3 exe就不用输入具体路径了 sqlite3 ex
  • 常用的dos网络命令总结

    一 ping 主要是测试本机TCP IP协议配置正确性与当前网络现状 ping命令的基本使用格式是 xff1a ping IP地址 主机名 域名 t a n count l size t xff1a 连续对IP地址 主机名 域名执行Ping
  • C#中隐藏窗体并执行窗体逻辑的方法

    c 隐藏窗体方方法 this WindowState 61 FormWindowState Minimized this ShowInTaskbar 61 false base SetVisibleCore true 示例如下 Task F
  • qt中xe运行缺少组件,Qt-c++桌面编程报错:qt.qpa.plugin: Could not find the Qt platform plugin “windows“ in ““,最终解决方案

    报以上错误是因为编译出来的exe程序缺少一些qt的组件 xff0c 需要补全放到exe同级目录下即可 编译库 xff1a Qt GUI xff0c qt5 12 1 软件类型 xff1a Qt application xff0c qt桌面软
  • C#中异步窗体的调用的几种方法

    在子线程里创建的窗体在其他线程里调用的异步调用操作方法 xff1a 1 在另一个线程里找到异步窗体句柄 xff0c 向其传送自定义消息ID 异步窗体的消息循环需要重写加入相应自定义方法 2 找到异步线程里的窗体 xff0c 用该窗体类型直接
  • C++中UDP通讯详解

    C 43 43 Socket编程及TCP UDP通信代码实现 一 简介 Socket编程的目的是使网络的进程进行通信 xff0c 基于TCP IP协议簇 xff0c 通过三元组 xff08 ip地址 协议 端口 xff09 标志进程 xff