前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【计网】从零开始使用UDP进行socket编程 --- 客户端与服务端的通信实现

【计网】从零开始使用UDP进行socket编程 --- 客户端与服务端的通信实现

作者头像
叫我龙翔
发布2024-09-17 10:49:01
1040
发布2024-09-17 10:49:01
举报
文章被收录于专栏:就业 C++ 综合学习

从零开始学习socket编程---UDP协议

  • 1 客户端与服务端的通信
  • 2 设计UDP服务器类
    • 2.1 基础框架设计
    • 2.2 初始化函数
    • 2.3 启动函数
  • 3 设计客户端

1 客户端与服务端的通信

我们了解了网络编程的大概,今天我们就来使用UDP协议来实现客户端与服务端之间的通信过程:

  1. 客户端可以向服务端发送数据,并接收服务端传回的反馈信息。
  2. 服务端接收客户端发送的数据,并根据数据进行处理,重新发送给客户端。

通过这个框架我们可以的扩展出翻译单词 , 多人聊天的功能。可以说只要实现服务端与客户端的通信,获取到的数据,就可对数据进行各种各样的处理!所以网络通信的基础很重要

我们先来回顾一下UDP socket编程的一些常用接口:

创建socket文件:

代码语言:javascript
复制
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

domain: 选择通信方式 — 本地通信与网络通信 type: 选择协议— UDP/TCP protocol: 默认使用0、 返回值是创建的socket文件操作符socketfd

bind绑定 ,将socket文件与IP地址绑定和端口号,也就是将进程与文件进行绑定。这样当数据包到达该端口和地址时,操作系统知道应该将数据传递给哪个应用程序。

代码语言:javascript
复制
NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

绑定时需要传入对应的struct sockaddr结构体指针和空间大小。我们知道其为父类,派生类有两种: sockaddr_in 和 sockaddr_un,按照需求强制类型转换就可以!

发送数据

代码语言:javascript
复制
NAME

 send, sendto, sendmsg - send a message on a socket

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

       ssize_t send(int sockfd, const 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 sendmsg(int sockfd, const struct msghdr *msg, int flags);

一般使用sendto函数

  1. sockfd: socket文件操作符,绑定了确定的IP地址与端口,保证数据按照该文件绑定的方式进行通信
  2. buf:指向包含要发送数据的缓冲区的指针。这个缓冲区应该已经填充了您想要发送的数据。
  3. len:buf指向的缓冲区中数据的长度,以字节为单位。这个值告诉sendto函数要发送多少字节的数据。
  4. flags:这个参数通常设置为0,表示没有特殊的发送选项。不过,它可以是一些标志的组合,比如 MSG_CONFIRM(用于TCP,确认路径是有效的)或MSG_DONTROUTE(数据不应该通过网关发送)。
  5. dest_addr:指向sockaddr结构体的指针,该结构体包含了数据将要发送到的目标地址和端口。对于IPv4,这通常是一个sockaddr_in结构体,而对于IPv6,则是一个sockaddr_in6结构体。
  6. addrlen:dest_addr指向的sockaddr结构体的大小,以字节为单位。这确保了无论在何种平台上,传递给sendto的都是正确的字节大小。

获取数据

代码语言:javascript
复制
NAME
       recv, recvfrom, recvmsg - receive a message from a socket

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

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

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

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

一般使用recvfrom函数,从socket文件中获取数据,并可以得到发送者的信息

  1. sockfd:从指定的socket文件中读取数据
  2. buf:缓冲区,将数据读取到这里
  3. len:缓冲区的长度
  4. src_addr:输出型参数,获取发送者的信息
  5. addrlen:输出型参数,获取发送者结构体的长度

认识了这些基础的函数,接下来我们就可以来实现服务器类了!

2 设计UDP服务器类

2.1 基础框架设计

首先我们先来搭建基础框架:

  1. 通过智能指针建立Udp对象
  2. Udpserver对象不可拷贝!
  3. Udpserver类内部需要一个_sockfd 文件操作符,_localport端口号 , _localipIP地址,_isrunning运行判断符。
  4. 设置
    • 初始化接口 :设置socket文件 , 将文件与端口号和IP进行bind绑定。
    • 启动接口 : 主要的运行程序,不断进行接收数据和发送数据。
    • 暂停接口 :运行暂停!

我们需要的就是这样的一个整体框架,实现我们后面再说;

代码语言:javascript
复制
#include <aio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>

#include "Log.hpp"
#include "nocopy.hpp"

const int gsockfd = -1;
const uint16_t glocalport = 8888;
const std::string glocalip = "192.1.1.1";

using namespace log_ns;

class UdpServer:public nocopy
{
public:
  UdpServer( std::string ip , uint16_t localport = glocalport) : _sockfd(gsockfd),
                                               _localport(localport),
                                               _localip(ip),
                                               isrunning(false)
  {
  }
  void InitServer()
  {
  }
  void Start()
  {
      
  }
  ~UdpServer()
  {
      if (_sockfd > gsockfd)
          ::close(_sockfd);
  }

private:
  int _sockfd;
  uint16_t _localport;
  std::string _localip;
  bool isrunning = false;
};

这里解决不可拷贝的做法是设置一个父类,将这个父类的拷贝构造,赋值重载都delete,那么作为派生类的UdpServer自然就不能进行拷贝了!

其中还加入了我们之前完成的日志系统

2.2 初始化函数

初始化化函数中需要进行以下操作:

创建socket文件,使用UDP协议的网络通信

将socket文件与IP地址和端口号进行绑定! 注意需要struct sockaddr_in结构体,其中的成员变量要注意格式转换!!!主机序列和网络序列是不同的!!!可以通过以下函数进行转换:

代码语言:javascript
复制
NAME
   htonl, htons, ntohl, ntohs - convert values between host and network byte order

SYNOPSIS
   #include <arpa/inet.h>
   //主机序列转换网络序列
   //如果主机字节序是大端字节序,则该函数不执行任何转换;
   //如果主机字节序是小端字节序,则该函数将整数的高位字节和低位字节进行交换。
   uint32_t htonl(uint32_t hostlong);
   //用于确保16位整数在发送到网络之前是按照大端字节序排列的。其工作原理与htonl类似,但针对16位整数。
   uint16_t htons(uint16_t hostshort);
   //---------------------------------
   //用于将从网络接收到的32位整数转换为主机字节序。
   //如果主机字节序是大端字节序,则该函数不执行任何转换;
   //如果主机字节序是小端字节序,则该函数将整数的高位字节和低位字节进行交换。
   uint32_t ntohl(uint32_t netlong);
   //用于将从网络接收到的16位整数转换为主机字节序。其工作原理与ntohl类似,但针对16位整数
   uint16_t ntohs(uint16_t netshort);

同样IP地址也需要进行转换,从字符串进行转换!!!

代码语言:javascript
复制
void InitServer()
    {
        // 创建立socket文件 得到文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket failed!\n");
            exit(SOCKER_FD);
        }
        LOG(DEBUG, "create socket success , _sockfd:%d \n", _sockfd);

        // 创建struct sockaddr_in 结构体对象 先进行清空
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        // 设置通信类型 设置端口号(主机序列转网络序列)
        local.sin_family = AF_INET;
        local.sin_port = htons(_localport);
        // 设置IP地址 int_addr()
        local.sin_addr.s_addr = inet_addr(_localip.c_str());
        //local.sin_addr.s_addr = INADDR_ANY; // 服务器IP一般设置为0

        // bind将套接字与IP端口号进行绑定
        int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));

        if (n < 0)
        {
            LOG(FATAL, "bind failed!\n");
            exit(SOCKET_BIND);
        }
        LOG(DEBUG, "bind socket success\n");
    }

2.3 启动函数

启动函数时服务器端的主要的运行过程,进行接收数据和发送数据:

  1. 首先通过recvfrom函数从socket文件中读取文件,并获取发送者的信息。
  2. 进行数据的处理,后期可以加入回调函数,进行处理!通过这个灵活的回调函数,我们可以实现非常多的功能!!!
  3. 将处理后的数据向客户端发送,
代码语言:javascript
复制
void Start()
    {
        isrunning = true;
        char inbuffer[1024];
        while (isrunning)
        {
            // 获取发送者的IP和端口号
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // LOG(DEBUG , "开始读入数据!\n");
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
            // 读取到了数据
            if (n > 0)
            {
                // LOG(DEBUG , "读到了数据!\n");
                //  结尾标志
                inbuffer[n] = 0;
                uint16_t peerport = ntohs(peer.sin_port);
                std::string peerip = inet_ntoa(peer.sin_addr);
                std::cout << "[" << peerip << ": " << peerport <<"] =#" << inbuffer << std::endl;
                std::string str = "[udp_server echo]#";
                str += inbuffer;
                size_t m = sendto(_sockfd, str.c_str(), str.size(), 0, (struct sockaddr *)&peer, len);
            }
            else
            {
                std::cout << "recvfrom ,  error!" << std::endl;
            }
        }
    }

这样服务器类就写好了,接下来简单处理一下客户端

3 设计客户端

客户端相对服务端要简单一些:

  1. 首先根据传入的参数进行IP地址和端口号的确定,
  2. 创建socket文件,注意不能进行bind绑定,如果客户端可以主动,那么就会导致很多进程出现冲突!!!所以只能进行随机分配。
  3. 设置 sockaddr_in server结构体设置
  4. 进入客户端主要运行过程,获取数据,然后通过sendto函数发送数据!
  5. 通过recvfrom获取数据,并且获取自身的IP地址和端口号,此时操作系统就对进程进行分配了端口!
  6. 获取数据之后进行处理!
代码语言:javascript
复制
#include <aio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>

#include "Log.hpp"

enum
{
    SOCKER_FD = 1,
    SOCKET_BIND
};

using namespace log_ns;

int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }
    //根据传入的参数获取服务端的IP和端口号
    std::string ip = argv[1];-+
    int port = std::stoi(argv[2]);
    //建立套接字socket 
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(FATAL, "socket failed!\n");
        exit(SOCKER_FD);
    }
    LOG(DEBUG, "Client create socket success , _sockfd:%d \n", sockfd);

    // client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢?
    // client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口, 
    // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口

    //设置服务器结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));//数据归零
    server.sin_family = AF_INET;
    server.sin_port = htons(port); //端口号
    server.sin_addr.s_addr = inet_addr(ip.c_str());//ip地址

    //循环发送接收数据
    while(1)
    {
        //发送数据
        std::string line;
        std::cout << "Please Enter: " ;
        std::getline(std::cin , line );
        //std::cout << "line message is@ " << line << std::endl;

        //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
        int n = sendto(sockfd , line.c_str() , line.size() , 0 , (struct sockaddr *)&server , sizeof(server));
        if(n > 0)
        {
            //进行获取数据
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            char buffer[512];
            //ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
            ssize_t m = recvfrom(sockfd , buffer , sizeof(buffer) - 1 , 0 , (struct sockaddr *)&temp , &len);
            if(m > 0)
            {
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
            else
            {
                std::cout << "m < 0 程序退出 !"<<std::endl;
                break;
            }
        }   
        else
        {
            std::cout << "n < 0 程序退出 !"<<std::endl;
            break;
        }
    }
    ::close(sockfd);

    return 0;
}

此时就可以进行通信了!!! 下一篇我们来对通信的基础上进行功能扩展!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从零开始学习socket编程---UDP协议
  • 1 客户端与服务端的通信
  • 2 设计UDP服务器类
    • 2.1 基础框架设计
      • 2.2 初始化函数
        • 2.3 启动函数
        • 3 设计客户端
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档