Linux网络编程——UdpServer

服务端基本框架

cpp 复制代码
#include <iostream>
#include "LOG.hpp"

static const int defaultsockfd = -1; 
class UdpServer
{
public:
    UdpServer(uint16_t port)//构造
        :_sockfd(defaultsockfd)
        ,_port(port)
        ,_isrunning(false)
    {}
    //服务初始化
    void InitServer()
    {

    }
    //开始运行
    void Start()
    {
        
    }
    ~UdpServer()//析构
    {}
private:
    int _sockfd;
    uint16_t _port;
    bool _isrunning;//判断服务端是否运行
};

为什么没有ip呢?到时候IP整合在InitAddr.hpp中

UdpServer.hpp

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <functional>
#include<arpa/inet.h>
#include "LOG.hpp"

enum
{
    SOCKET_ERROR=1,
    BIND_ERROR,
    USAGE_ERROR
};
const static int defaultsockfd = -1; 
class UdpServer
{
public:
    UdpServer(uint16_t port)//构造
        :_sockfd(defaultsockfd)
        ,_port(port)
        ,_isrunning(false)
    {}
    //服务初始化
    void InitServer()
    {
        //创建套接字
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)//创建失败,日志报错,退出
        {
            LOG(FATAL,"socket error:%s,%d\n",strerror(errno),errno);
            exit(SOCKET_ERROR);
        }
        //创建成功
        LOG(INFO,"socket create success,sockfd:%d\n",_sockfd);

        //填充sockaddr_in结构
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));//清一下结构里面的数据
        local.sin_family=AF_INET;//网络通信
        //port需要经过网络传输给对面的client,主机序列要先转网络序列
        local.sin_port=htons(_port);
        local.sin_addr.s_addr=INADDR_ANY;

        //bind
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n<0)//绑定失败
        {
            LOG(FATAL,"bind error,%s:%d\n",strerror(errno),errno);
            exit(BIND_ERROR);
        }
        //绑定成功
        LOG(INFO, "socket bind success\n");
    }
    //开始运行
    void Start()
    {
        _isrunning = true;
        while(_isrunning)//服务一般都是一直运行的死循环
        {
            LOG(DEBUG,"sever is running...\n");
            sleep(1);
        }
    }
    ~UdpServer()//析构
    {}
private:
    int _sockfd;
    uint16_t _port;
    bool _isrunning;//判断服务端是否运行
};

填充sockaddr_in记得引头文件<apra/inet.h> ;struct sockaddr_in是系统提供的数据类型

端口号port主机序列转成网络序列用htons()

在网络编程中,当一个进程需要绑定一个网络端口以进行通信时,可以使用 INADDR_ANY 作为 IP 地址参数。这样做意味着该端口可以接受来自任何 IP 地址的连 接请求,无论是本地主机还是远程主机。

那么local还只是对象,还在栈上开辟空间,所以InitServer初始化函数只有创建套接字socket以及填充sockaddr_in是不够的,需要进行绑定bind ,bind完成后server的初始化才算完成。

下面就可以写启动server的函数了;现在的软件若不是用户主动退出,就会一直运行,服务器也一样会一直运行,直到管理者不想运行了。所以Server都是死循环,那么就需要一个对象来判断服务器是否一直再跑(_isrunning)

Server.cc

cpp 复制代码
#include<iostream>
#include"UdpServer.hpp"
#include <memory>
void Usage(std::string a)
{
    std::cout<<"Usage:\n\t"<<a<<"local port"<<std::endl;
}
int main(int argc,char *argv[])
{
    if(argc != 2)//./xxx port(端口号)
    {
        Usage(argv[0]);
        //参数错误直接报错
        exit(USAGE_ERROR);
    }
    
    EnableScreen();
    //端口号
    uint16_t port = std::stoi(argv[1]);
    //用端口号来创建udp对象
    std::unique_ptr<UdpServer> udps = std::make_unique<UdpServer>(port);
    //初始化
    udps->InitServer();
    //启动
    udps->Start();
    return 0;
}

测试一下啊

没有问题

下面我们来完成一些Udp服务的启动

1.我们需要让Server端先收数据

2.我们要让Server将收到的数据再发回去

接收可以用recvfrom

recvfrom() 函数是一个系统调用,用于从套接字接收数据。该函数通常与无连接的数据报服务(如 UDP)一起使用,但也可以与其他类型的套接字使用。

man recvfrom

参数

sockfd:一个已打开的套接字的描述符。

buf:一个指针,指向用于存放接收到的数据的缓冲区(输出型参数对应的缓冲区)。

len:缓冲区的大小/期望长度(以字节为单位)。

flags:读取方式。通常可以设置为0,但以下是一些可用的标志:

MSG_WAITALL:尝试接收全部请求的数据。函数可能会阻塞,直到收到所有数据。

MSG_PEEK:查看即将接收的数据,但不从套接字缓冲区中删除它【1】。

其他一些标志还可以影响函数的行为,但在大多数常规应用中很少使用。

有人给你发消息你想不想知道他是谁?为什么?

当然想,我还要给他回消息呢。

那么通过什么信息知道对方是谁呢?socket:IP和Port(通过以下参数就可以知道是谁发的)

src_addr:一个指针,指向一个 sockaddr 结构,用于保存发送数据的源地址。

addrlen:一个值-结果参数。开始时,它应该设置为 src_addr 缓冲区的大小。当 recvfrom() 返回时,该值会被修改为实际地址的长度(以字节为单位)。

返回值 ssize_t

在成功的情况下,recvfrom() 返回接收到的字节数。

如果没有数据可读或套接字已经关闭,那么返回值为0。

出错时,返回 -1,并设置全局变量 errno 以指示错误类型。

服务端接收完如何发送呢?

可以用sendto

sendto() 函数是一个系统调用,用于发送数据到一个指定的地址。它经常与无连接的数据报协议,如UDP,一起使用。不像 send() 函数只能发送数据到一个预先建立连接的远端,sendto() 允许在每次发送操作时指定目的地址

man sendto

sendto 和recvfrom用法差不多

以下参数:

sockfd:一个已打开的套接字的描述符。

buf:一个指针,指向要发送的数据的缓冲区。

len:要发送的数据的大小(以字节为单位)。

flags:控制发送行为的标志。通常可以设置为0。

下面两个参数决定发给谁

dest_addr:指向 sockaddr 结构的指针,该结构包含目标地址和端口信息。

addrlen:dest_addr 缓冲区的大小(以字节为单位)。

返回值:

成功时,sendto() 返回实际发送的字节数。

出错时,返回 -1 并设置全局变量 errno 以指示错误类型。

服务端启动方法实现

cpp 复制代码
void Start()
    {
        //之前提到的判断服务是否还在运行的变量
        _isrunning = true;
        while(_isrunning)//服务一般都是一直运行的死循环
        {
            //接收
            char buffer[1024];
            struct sockaddr_in peer;//对client端构建sockaddr
            socklen_t len = sizeof(peer);//必须初始化为sizeof(peer)/peer长度
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
            if(n > 0)//收到了
            {
                buffer[n]=0;//在接收数据的末尾手动添加空字符\0
                LOG(DEBUG,"服务端获取信息:%s\n",buffer);
                //你收到了才能发嘛
                sendto(_sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);
            }

        }
        _isrunning = false;
    }

客户端和服务端的步骤差不多,只是客户端不显示绑定

client.cc

cpp 复制代码
#include<iostream>
#include<string>
#include <cstring>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

void Usage(std::string a)
{
    std::cout<<"Usage:\n\t"<<a<<"ip address"<<" "<<"local port"<<std::endl;
}
int main(int argc,char *argv[])
{
    if(argc != 3)//./client ip地址 端口号
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    //client这边的步骤也差不多,需要注意的是,Udpclent不要显示的绑定,其首次发送数据时OS会自动随机的给client进行bind
    //创建socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0)
    {
        std::cerr<<"socket error"<<std::endl;
        exit(2);
    }

    //填充 struct sockaddr_in
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(serverport);
    server.sin_addr.s_addr=inet_addr(serverip.c_str());
    //少了步显示绑定
    std::string messagebuffer;
    //通信
    while(true)
    {
        std::cout<<"请输入#";
        getline(std::cin,messagebuffer);
        //先发送
        sendto(sockfd,messagebuffer.c_str(),messagebuffer.size(),0,(struct sockaddr*)&server,sizeof(server));

        //接收
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        ssize_t n = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
        if(n > 0)
        {
            buffer[n]=0;
            std::cout<<"服务端返回#"<<buffer<<std::endl;
        }
    }
    return 0;
}

client 要不要进行 bind? 一定要 bind

但是,不需要显示 bind,client 会在首次发送数据的时候会自动进行 bind

为什么?因为server 端的端口号,一定是不可改变的,client 需 要 port,bind 随机端口.

为什么?因为客户端 会非常多. 防止大量客户端bind端口后对大量端口进行占用

client 需要 bind,但是不需要显示 bind,让本地 OS 自动随机 bind, 选择随机端口号

测试代码

封装一下addr,方便获取Client端的ip和端口

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
class InetAddr
{
private:
    GetAddr(std::string *ip,uint16_t *port)
    {
        *port=ntohs(_addr.sin_port);//网络转主机
        //将32位网络字节序的IPv4地址转换为点分十进制字符串
        *ip = inet_ntoa(_addr.sin_addr);

    }
public:
    InetAddr(struct sockaddr_in addr)//构造
        :_addr(addr)
    {}
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {
        
    }
private:
    struct sockaddr_in _addr;
    uint16_t _port;
    std::string _ip;

};
相关推荐
薰衣草23332 小时前
linux-1
linux·运维·服务器
Lenyiin3 小时前
《 Linux 点滴漫谈: 三 》Linux 的骨架:文件系统与目录结构的完整图谱
linux·运维·服务器·lenyiin
ZLRRLZ3 小时前
【Linux操作系统】进程概念
linux·运维·服务器
XUE-52113145 小时前
BGP实验-路由优选
linux·服务器·网络·网络协议
七七七七076 小时前
【Linux 系统】理解Linux下一切皆文件
linux·运维·服务器
tjsoft6 小时前
专栏丨华为HN8145XR光猫获取超级管理员密码
运维·服务器·网络
盛满暮色 风止何安7 小时前
网络安全设备 防火墙
服务器·网络·网络协议·计算机网络·安全·web安全·网络安全
古月-一个C++方向的小白14 小时前
Linux——查看与创建进程
linux·运维·服务器
驱动探索者16 小时前
find 命令使用介绍
java·linux·运维·服务器·前端·学习·microsoft