提示: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套接字中需要使用的函数、传参及函数返回值等细节问题做出了详细的解释。