socket网络编程UDP
一、必备接口
1、接收数据
recvfrom
函数用于从套接字接收数据
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
返回值:返回正数表示成功接收到的数据字节数,返回0表示对方已经关闭了连接(对于TCP),返回-1表示发生错误
sockfd
:指定从哪个套接字接收数据
buf
:指向一个缓冲区,用于存储接收到的数据
len
:指定buf
的最大长度
flags
:设置接收数据的标志,无标志一般设为0,在这里可以设置非阻塞模式
src_addr
:用于存储发送方的地址信息,输出型参数
addrlen
:用于指定src_addr
结构体的长度,输入输出型参数,在调用 recvfrom
函数之前,需要将其初始化为 src_addr
结构体的最大长度,函数返回时,addrlen
会被更新为实际存储在 src_addr
中的地址信息的长度
2、发送数据
sendto
函数用于在无连接的套接字(如 UDP 套接字)上发送数据
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
返回值:返回正数表示成功发送的数据字节数,返回-1表示发送过程中出现错误
sockfd
:指定通过哪个套接字发送数据
buf
:指向要发送的数据的缓冲区
len
:表示要发送的数据的长度(以字节为单位),即 buf
缓冲区中实际要发送的数据的字节数
flags
:设置发送数据的标志,无标志一般设为0,在这里可以设置非阻塞模式
dest_addr
:该结构体包含了目标地址的信息
addrlen
:表示dest_addr
结构体的长度(以字节为单位)
二、实现UDP网络编程
1、服务器
(一)UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unistd.h>
// 下面两行都是代表着定义一个类似函数指针的变量func_t,参数和返回值都是string
// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;
//枚举错误类型
enum{
SOCKET_ERR=1,
BIND_ERR
};
//定义端口号和ip地址
uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
//size用来定义缓冲区大小
const int size = 1024;
class UdpServer{
public:
UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
:sockfd_(0), port_(port), ip_(ip),isrunning_(false)
{}
void Init()
{
// 创建udp socket
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd_ < 0)
{
exit(SOCKET_ERR);
}
// 定义一个local存储本地地址
struct sockaddr_in local;
//bzero的作用就是将local结构体清零
bzero(&local, sizeof(local));
//将local的前两个字节也就是它的地址族为AF_INET
local.sin_family = AF_INET;
//保证我们的端口号是网络字节序列,因为该端口号是要给对方发送的
local.sin_port = htons(port_);
//inet_addr函数将ip_转换为in_addr_t类型的IP地址,并存储在local.sin_addr.s_addr
local.sin_addr.s_addr = inet_addr(ip_.c_str());
//绑定sockfd_到本地地址local上,失败则退出
if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
{
exit(BIND_ERR);
}
}
void Run(func_t func) // 对代码进行分层
{
//设置服务器开始运行标志
isrunning_ = true;
//定义缓冲区存放接收到的数据
char inbuffer[size];
while(isrunning_)
{
//定义client结构体存储客户端的地址信息
struct sockaddr_in client;
socklen_t len = sizeof(client);
//获取客户端信息
ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0,
(struct sockaddr*)&client, &len);
if(n < 0)
{
continue;
}
//在最后添加字符串描述符'\0'
inbuffer[n] = 0;
//将所有的信息通过func函数处理为echo_string
std::string info = inbuffer;
std::string echo_string = func(info);
//将处理完后的数据发送到客户端
sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0,
(const sockaddr*)&client, len);
}
}
//析构,sockfd_本质上是一个文件描述符一样的东西,用close可以关闭
~UdpServer()
{
if(sockfd_>0) close(sockfd_);
}
private:
int sockfd_; // 网路文件描述符
std::string ip_; // 任意地址bind 0
uint16_t port_; // 表明服务器进程的端口号
bool isrunning_; // 服务器运行状态
};
(二)main.cpp
#include "UdpServer.hpp"
#include <memory>
#include <cstdio>
// 端口号一般是可以随便定义的,但是[0,1023]范围内的端口号一般都是有固定的应用层协议的
//所以我们都用1024+的端口号
//输入小于1024的端口号就提示重输
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
//该函数用于处理接收到的消息,将消息添加到固定的前缀 "Server get a message: " 后面
//并将结果输出到控制台
std::string Handler(const std::string &str)
{
std::string res = "Server get a message: ";
res += str;
std::cout << res << std::endl;
return res;
}
//该函数用于执行客户端发送的命令,并将命令执行的结果返回给 UDP 服务器
//从而实现服务器与客户端之间的交互,让客户端可以在服务器端执行命令并获取结果
std::string ExcuteCommand(const std::string &cmd)
{
FILE *fp = popen(cmd.c_str(), "r");
if(nullptr == fp)
{
perror("popen");
return "error";
}
std::string result;
char buffer[4096];
while(true)
{
char *ok = fgets(buffer, sizeof(buffer), fp);
if(ok == nullptr) break;
result += buffer;
}
pclose(fp);
return result;
}
// ./udpserver port
int main(int argc, char *argv[])
{
//在命令行中只带一个参,多带参和少带参都要执行Usage
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port = std::stoi(argv[1]);
//智能指针管理 UdpServer 对象的生命周期,确保对象在不再使用时自动释放内存
std::unique_ptr<UdpServer> svr(new UdpServer(port));
//初始化并启动服务器
svr->Init();
svr->Run(ExcuteCommand);
return 0;
}
2、客户端
UdpClient.cpp
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(std::string proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
//命令行参数不为三个就打印提示信息
if (argc != 3)
{
Usage(argv[0]);
exit(0);
}
//获取服务器端口号和服务器ip
std::string serverip = argv[1];
uint16_t serverport = std::stoi(argv[2]);
//下面似曾相识呢,和上面服务器的解释一致
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
socklen_t len = sizeof(server);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
cout << "socker error" << endl;
return 1;
}
//客户端套接字实际上也需要绑定,但通常不需要用户显式地进行绑定
//操作系统会在客户端首次发送数据时自动为其分配一个可用的端口号进行绑定
string message;
char buffer[1024];
while (true)
{
cout << "Please Enter@ ";
getline(cin, message);
//将消息发送到服务器
sendto(sockfd, message.c_str(), message.size(), 0,
(struct sockaddr *)&server, len);
//接收信息并打印
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
if(s > 0)
{
buffer[s] = 0;
cout << buffer << endl;
}
}
close(sockfd);
return 0;
}
最终我们实现的就是一个客户端可以通过指令操控服务器,然后将服务器应该输出的结果打印到客户端
今日分享就到这里了~