Linux环境下使用c++通过Socket套接字实现UDP协议通讯

提示:Linux环境下使用c++通过Socket套接字实现UDP协议通讯。本文源码可直接运行使用。


前言

本文介绍了如何在Linux环境下,使用c++通过Socket套接字实现UDP协议通讯。对Socket套接字中需要使用的函数、传参及函数返回值等细节问题做出了详细的解释。另外需要提醒的是,在学习UDP协议时应先了解Socket套接字、g++和Linux操作命令等基础知识。


一、Socket实现UDP协议的通讯

1.socket函数及参数返回值详解

    int serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (serverSocket < 0)
    {
        std::cout << "socket error" << std::endl;
        return 1;
    }

这行代码是用来创建一个基于 IPv4 地址族(AF_INET)和 TCP 协议(SOCK_STREAM)的套接字,用于接受客户端的连接请求并与之建立 TCP 连接。接下来还需要调用 bind() 绑定地址和端口、listen() 开始监听连接请求等操作,以完成服务器端的初始化工作。

socket()函数的返回值 serverSocket,用于表示服务器端的套接字,当serverSocket<0时,表示创建失败。

在使用 socket 函数创建套接字时,包括地址族(Address Family)、套接字类型(Socket Type)和协议(Protocol)三个参数。具体参数如下:

地址族(Address Family):
AF_INET:IPv4 地址族,用于指定使用 IPv4 地址。
AF_INET6:IPv6 地址族,用于指定使用 IPv6 地址。
AF_UNIX 或 AF_LOCAL:本地通信地址族,用于在同一台计算机上的进程间通信。

套接字类型(Socket Type):
SOCK_STREAM:面向连接的字节流套接字,提供可靠的、基于字节流的数据传输服务,对应 TCP 协议。
SOCK_DGRAM:无连接的数据报套接字,提供不可靠的数据传输服务,对应 UDP 协议。
SOCK_RAW:原始套接字,可以直接访问底层网络协议,适用于特定的网络操作需求。

协议(Protocol):
通常设置为 0,表示根据地址族和套接字类型自动选择合适的协议。
对于 SOCK_STREAM 类型的套接字,通常会选择 IPPROTO_TCP(TCP 协议)。
对于 SOCK_DGRAM 类型的套接字,通常会选择 IPPROTO_UDP(UDP 协议)。

2.sockaddr_in结构体、inet_pton函数及参数返回值详解

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) < 0)
    {
        close(serverSocket);
        std::cout << "inet_pton error" << std::endl;
        return -1;
    }

先要定义一个sockaddr_in结构体serverAddr,然后设置了该结构体的成员sin_family为AF_INET 表示使用 IPv4 地址,sin_port 为服务器的端口号,sin_addr 为服务器的 IP 地址。最后一行代码使用了 inet_pton 函数将点分十进制的 IP 地址转换为网络字节顺序的二进制形式,并存储到 serverAddr.sin_addr 中。

sockaddr_in 结构体是用于表示 IPv4 地址和端口的数据结构,通常用于网络编程中。该结构体定义如下:

struct sockaddr_in {
    short int      sin_family;  // 地址族,一般设置为 AF_INET
    unsigned short sin_port;    // 端口号,网络字节顺序
    struct in_addr sin_addr;    // IPv4 地址结构体
    char           sin_zero[8]; // 未使用,填充使结构体大小与 sockaddr 相同
};

sin_family:地址族,一般设置为 AF_INET 表示使用 IPv4 地址。
sin_port:端口号,使用 unsigned short 类型表示,需要使用 htons() 函数将主机字节顺序转换为网络字节顺序。
sin_addr:struct in_addr 类型的结构体,用于表示 IPv4 地址。
sin_zero:填充字段,用于使整个结构体的大小与 struct sockaddr 相同,通常不使用。

3.bind函数及参数返回值详解

    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0)
    {
        close(serverSocket);
        std::cout << "bind error" << std::endl;
        return -1;
    }

serverAddr是一个sockaddr结构体类型的变量,存储了服务器的IP地址和端口号信息,bind函数用于将一个本地地址(&serverAddr)绑定到一个socket上。具体来说,是将serverSocket与serverAddr绑定在一起。返回值>0。

4.recvfrom函数及参数返回值详解

    memset(buffer, 0, sizeof(buffer));
    if (recvfrom(serverSocket, buffer, sizeof(buffer), 0, (struct sockaddr*) &clientAddr, &clientAddrLen) < 0)
    {
        close(serverSocket);
        std::cout << "recv error" << std::endl;
    }

recvfrom 用于从指定套接字接收数据,并获取发送端地址信息。

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

sockfd:表示接收端套接字描述符。buf:指向存放接收数据的缓冲区。len:缓冲区的大小,即最大接收数据量。flags:控制接收操作的行为,通常为 0。src_addr:发送端的地址信息,调用该函数后会将发送端的地址填入其中。addrlen:src_addr 结构体的大小,需要传递一个指向该大小的指针。

函数功能:从 sockfd 接收数据,并将其存储在 buf 中。如果有数据可用,则将其复制到 buf 中。返回接收到的字节数,如果发生错误则返回 -1。

需要注意这个地方:&clientAddrLen这里用的指针,且指针是socklen_t类型,在TCP协议中使用accept函数时,参数也是socklen_t类型,可以简单记住在接收获取对方的地址长度时用socklen_t*类型指针。

5.sendto函数及参数返回值详解

	sendto(serverSocket, data, sizeof(data), 0, (struct sockaddr*)&clientAddr, sizeof(clientAddr));

sendto 用于向指定套接字发送数据,并指定目标地址信息。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

sockfd:表示发送端套接字描述符。buf:指向待发送数据的缓冲区。len:待发送数据的长度。
flags:控制发送操作的行为,通常为 0。dest_addr:目标地址信息,指定数据报应该发送到的地址。addrlen:dest_addr 结构体的大小。

**函数功能:**将 buf 中的数据发送到 sockfd 指定的套接字。数据发送到 dest_addr 指定的目标地址。返回实际发送的字节数,如果发生错误则返回 -1

6.缓存区输出及关闭套接字

    for (uint8_t value : buffer)
    {
        std::cout << std::hex << static_cast<int>(value) << " ";
    }
    std::cout << std::endl;
   
    close(serverSocket);
    close(clientSocket);

二、Socket实现UDP协议通讯源码

1.服务端代码

/*server.cpp*/
#include <iostream>
#include <string.h>
#include <cstdint>
#include <unistd.h>
#include <vector>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_PORT 8080
#define SERVER_IP "127.00.00.01"
#define LISTEN_NUM 10
#define BUFMAX 10

int main()
{
    uint8_t buffer[BUFMAX];

    int serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (serverSocket < 0)
    {
        std::cout << "socket error" << std::endl;
        return 1;
    }

    struct sockaddr_in serverAddr, clientAddr;

    memset(&clientAddr, 0, sizeof(clientAddr));
    socklen_t clientAddrLen = sizeof(clientAddr);

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) < 0)
    {
        close(serverSocket);
        std::cout << "inet_pton error" << std::endl;
        return -1;
    }
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0)
    {
        close(serverSocket);
        std::cout << "bind error" << std::endl;
        return -1;
    }

    
    memset(buffer, 0, sizeof(buffer));
    if (recvfrom(serverSocket, buffer, sizeof(buffer), 0, (struct sockaddr*) &clientAddr, &clientAddrLen) < 0)
    {
        close(serverSocket);
        std::cout << "recv error" << std::endl;
    }

    for (uint8_t value : buffer)
    {
        std::cout << std::hex << static_cast<int>(value) << " ";
    }
    std::cout << std::endl;

    uint8_t data[BUFMAX] = { 0xff, 0xff, 0xff, 0x12, 0x09, 0x12, 0x2c, 0xff, 0xff, 0xff };

    sendto(serverSocket, data, sizeof(data), 0, (struct sockaddr*)&clientAddr, sizeof(clientAddr));

    close(serverSocket);
    return 0;
}

2.客户端代码

/*client.cpp*/
#include <iostream>
#include <string.h>
#include <cstdint>
#include <unistd.h>
#include <iomanip>
#include <vector>
#include <sys/socket.h>
#include <arpa/inet.h>

#define CLIENT_PORT 8080
#define SERVER_IP "127.00.00.01"
#define BUFMAX 10

int main()
{

    int clientSocket = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientSocket < 0)
    {
        std::cout << "socket error" << std::endl;
        return -1;
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(CLIENT_PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) < 0)
    {
        std::cout << "inet_pton error" << std::endl;
        close(clientSocket);
        return -1;
    }

    uint8_t data[BUFMAX] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x2c, 0x17, 0x70, 0x7b};
    sendto(clientSocket, data, sizeof(data), 0, (struct sockaddr*) &serverAddr, sizeof(serverAddr));

    uint8_t buffer[BUFMAX];
    memset(buffer, 0, sizeof(buffer));
    socklen_t serverAddrLen = sizeof(serverAddr);
    if (recvfrom(clientSocket, buffer, sizeof(buffer), 0, (struct sockaddr*)&serverAddr, &serverAddrLen) < 0)
    {
        close(clientSocket);
        std::cout << "recv error" << std::endl;
    }

    for (uint8_t value : buffer)
    {
        std::cout << std::hex << static_cast<int>(value) << " ";
    }
    std::cout << std::endl;

    memset(data, 0, sizeof(data));
    close(clientSocket);

    return 0;
}

3.编译

Linux环境内使用g++编译生成可执行文件,操作如下(示例):

root@cpes:~/Desktop# g++ client.cpp -o client
root@cpes:~/Desktop# g++ server.cpp -o server

先运行server,操作如下(示例):

root@cpes:~/Desktop# ./server

再运行client,操作如下(示例):

root@cpes:~/Desktop# ./client

总结

本文介绍了如何在Linux环境下,使用c++通过Socket套接字实现UDP协议通讯。对Socket套接字中需要使用的函数、传参及函数返回值等细节问题做出了详细的解释。

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中,你可以使用多线程来接收UDP套接字传来的消息。以下是一个示例代码,展示了如何使用多线程进行UDP消息接收: ```cpp #include <iostream> #include <thread> #include <cstring> #include <arpa/inet.h> void receiveData(int sock) { while (true) { char buffer[1024]; struct sockaddr_in client_address; socklen_t client_address_len = sizeof(client_address); // 接收数据 ssize_t num_bytes = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_address, &client_address_len); if (num_bytes > 0) { buffer[num_bytes] = '\0'; std::cout << "Received data from " << inet_ntoa(client_address.sin_addr) << ": " << buffer << std::endl; } } } int main() { int sock; // 创建UDP套接字 if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { std::cerr << "Failed to create socket" << std::endl; return 1; } // 绑定地址和端口 struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = INADDR_ANY; server_address.sin_port = htons(8000); if (bind(sock, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { std::cerr << "Failed to bind socket" << std::endl; return 1; } // 创建多个线程进行数据接收 int num_threads = 4; std::vector<std::thread> threads; for (int i = 0; i < num_threads; i++) { threads.emplace_back(receiveData, sock); } // 等待所有线程结束 for (auto& thread : threads) { thread.join(); } // 关闭套接字 close(sock); return 0; } ``` 在上面的示例中,我们使用`socket`函数创建了一个UDP套接字,并绑定了地址和端口。然后,我们创建了多个线程来并行地接收数据。每个线程都运行`receiveData`函数,该函数使用`recvfrom`函数从套接字接收数据,并打印出接收到的数据和发送方的地址。 主函数`main`中创建了4个线程进行数据接收,你可以根据需要调整线程数。最后,我们等待所有线程结束,并关闭套接字。 希望这个示例能帮助到你实现UDP消息接收的多线程应用。如果有任何疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值