demo版的udp网络通信实现

目录

​编辑

一,引言

二,服务端

1,server类

2,构造函数

3,初始化服务函数

初始化函数整体代码:

4,启动函数

三,客户端

四,main函数调用


一,引言

今天我们要写的demo框架是基于CS架构的。也就是说要实现网络通信就必须要实现两端:1,客户端 2,服务端。这两端的任务如下:

服务端:

接收客户端发来的消息,并且打印显示出来,然后再向客户端反馈已收到消息。

客户端:

用户写下消息,并发送到服务端请求服务器处理。

二,服务端

1,server类

要实现网络通信必须要有的便是ip和端口号,所以我们的server类的成员当中一定要有的便是ip和端口号。并且我们的网络通信是依靠套接字完成的,所以在这个类中必不可少的还有套接字描述符。

所以类成员如下:

cpp 复制代码
class UdpServer
{

    private:
        std::string ip_;//ip地址,ip地址一般都是点分十进制的所以用string
        uint16_t port_;//端口号
        int socketfd_;//套接字描述符
};

2,构造函数

server端的构造函数实现非常的简单,就是简单的让类内的成员被赋值。这一步可有可无,但是为了完整性还是写下。

代码:

cpp 复制代码
UdpServer()
        : port_(0), socketfd_(0)
    {
    }

3,初始化服务函数

初始化服务有以下两个步骤:1,创建套接字 2,绑定ip和port

创建套接字 :

使用socket函数创建:

cpp 复制代码
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

​

domain:标识这个套接字的通信类型(本地/网络)

type:套接字提供的服务类型

protocol:协议

一般在实现UDP通信时填上0便可以。

返回值:socket函数的返回值是一个socket文件描述符。

代码:

cpp 复制代码
 socketfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
 if(socketfd_<0)//创建失败就退出进程
 {
    perror("socket error\n");
    exit(1);
 }

绑定套接字:

使用bind函数绑定ip和port:

cpp 复制代码
 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

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

sockfd:套接字描述符。

sockaddr:套接字地址结构体。

cpp 复制代码
struct sockaddr 
{
   sa_family_t sa_family;
   char sa_data[14];
 }

addrlen:套接字结构体长度。

套接字地址结构体初始化:

cpp 复制代码
 sockaddr_in si;//定义套接字结构体地址
 bzero(&si, sizeof si);  // 清空
 si.sin_family = AF_INET;//协议位ipv4
 si.sin_port = htons(port);//端口号要转化为网络字节序列
 si.sin_addr.s_addr = INADDR_ANY;//服务器端一般都要设置为可以接收任意ip地址发来的消息

开始绑定:

cpp 复制代码
if(bind(socketfd_, (sockaddr *)&si, sizeof si)<0)//绑定失败就直接退出
{
   perror("bind error\n");
   exit(1);
}
初始化函数整体代码:
cpp 复制代码
//定义一些变量来代表默认值
#define defaultip  "0.0.0.0"
#define defaultport 8008


    void Init(const std::string& ip = defaultip,int port = defaultport)
    {
        socketfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
        if(socketfd_<0)//创建失败就退出进程
        {
            perror("socket error\n");
            exit(1);
        }

        //创建成功,开始绑定
        sockaddr_in si;//定义套接字结构体地址
        bzero(&si, sizeof si);  // 清空
        si.sin_family = AF_INET;//协议位ipv4
        si.sin_port = htons(port);//端口号要转化为网络字节序列
        si.sin_addr.s_addr = INADDR_ANY;//服务器端一般都要设置为可以接收任意ip地址发来的消息
       if(bind(socketfd_, (sockaddr *)&si, sizeof si)<0)//绑定失败就直接退出
       {
           perror("bind error\n");
           exit(1);
       }
    }

4,启动函数

启动函数的作用如下:

1,接收客户端的请求。

2,将请求打印出来。

3,该函数是一个无限循环的函数。

使用recvfrom函数接收消息:

cpp 复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                  struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:服务器端的套接字描述符 。

buf:读取消息的缓冲区。

len:buf的长度 。

flags:读取消息的方式。

src_addr:自定义的地址结构体,用于存放用户端的ip地址和port。

addrlen:srd_addr的长度。
udp的服务是面向数据报的,也就是: SOCK_DGRAM。所以要使用recvfrom函数接收数据,而不能使用read。

使用sendto发消息:

cpp 复制代码
 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

sendto的参数和recvfrom的参数高度相似,这里就不一一介绍了。

整体代码如下:

cpp 复制代码
 void Run()
    {
        char inbuf[inbufSize];
        sockaddr_in si;
        socklen_t len;
        while (true)
        {
            int r1 = recvfrom(socketfd_, inbuf, sizeof inbuf-1, 0, (sockaddr *)&si, &len);//读取消息
            if(r1<0)//读取消息失败
            {
                perror("recvfrom error");
                exit(10);
            }

            inbuf[r1] = 0;
            std::string message = inbuf;
            std::string tostring = "client say#" + message;
            std::cout << message << std::endl;

            const char *response = "收到消息,正在处理";
            int r2 =  sendto(socketfd_, response, sizeof response, 0, (sockaddr *)&si, sizeof si);
            if(r2<0)
            {
                perror("server send message error");
                continue;
            }
        }
    }

写到这,服务端的代码就算结束了。接下来可以运行这个程序,然后使用netstat -naup指令查看当前服务器的挂起状态:

三,客户端

客户端的代码编写与服务端的代码编写其实差不多。客户端的代码做的事如下:

1,创建套接字。

2,自定义一个套接字结构体,并把该结构体的ip地址和端口号port初始化。

3,user写入消息。

4,将消息发送给服务端。

5,接收服务端的消息并打印出来。

代码如下:

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

#define outbufSize 1024

class Client
{
public:
public:
    Client()
        : port_(0), socketfd_(0)
    {
    }

    void Init(const std::string &ip, int port)
    {
        socketfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
        if (socketfd_ < 0)                          // 创建失败就退出进程
        {
            perror("socket error\n");
            exit(1);
        }

        port_ = port;
        ip_ = ip;
        // 不用bind
    }

    void Run()
    {
        std::string requestes;
        sockaddr_in si;
        socklen_t len;
        si.sin_family = AF_INET;
        si.sin_port = htons(port_);
        si.sin_addr.s_addr = inet_addr(ip_.c_str());

        char outbuf[outbufSize];
        while (true)
        {
            std::cout << "请输入内容>> ";
            std::getline(std::cin, requestes); // client输入内容
            if (sendto(socketfd_, requestes.c_str(), sizeof requestes, 0, (sockaddr *)&si, sizeof si) < 0) // 发送消息
            {
                continue;
            }

            int r3 = recvfrom(socketfd_, outbuf, sizeof outbuf - 1, 0, (sockaddr *)&si, &len);

            if(r3<0)
            {
                continue;
            }

            outbuf[r3] = 0;

            std::cout << "server say# " << outbuf << std::endl;

            memset(outbuf, 0, sizeof outbuf);
        }
    }

private:
    std::string ip_; // ip地址
    uint16_t port_;  // 端口号
    int socketfd_;   // 套接字描述符
};

客户端的代码与服务端代码基本相似。

四,main函数调用

main函数主要起调用作用,在linux中我们的调用方式一般都是命令行的方式。如何使用命令行呢?在这里就不得不提到main函数的另一种形式了,如下:

cpp 复制代码
int main(int argc,char* argv[])

argc:表示命令行中的字符串的数量。

argv:表示字符串参数。

如命令:

cpp 复制代码
cd udp

argc = 2

argv[0] = cd argv[1]=udp

所以为了使用命令行的形式来调用服务端和客户端,我们的main函数也要写成这种形式。

Client.cc:

cpp 复制代码
#include "Client.hpp"
#include <memory>

void usage(const std::string &porc)命令行的输入必须符合格式:./可执行程序 ip地址 端口号
{
    std::cout << porc << "ip:"
              << "port"
              << "[1024+]" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    std::string ip = argv[1];
    int port = atoi(argv[2]);
    std::unique_ptr<Client> Cp(new Client);
    Cp->Init(ip,port);
    Cp->Run();
    return 0;
}

Server.cc:

cpp 复制代码
#include"Server.hpp"
#include<memory>

void usage(const std::string &porc)
{
    std::cout << porc << "ip:"
              << "port"
              << "[1024+]" << std::endl;
}

int main(int argc,char*argv[])
{
    if (argc != 2)//命令行的输入必须符合格式:./可执行程序  端口号
    {
        usage(argv[0]);
        exit(1);
    }
    
    int port = atoi(argv[1]);

    std::unique_ptr<UdpServer>Sp(new UdpServer);
    Sp->Init(port);
    Sp->Run();
    return 0;
}

运行后结果如下:

调用时一定要保证客户端和服务端的端口号相同!!!

相关推荐
秃头佛爷1 小时前
Python学习大纲总结及注意事项
开发语言·python·学习
Lary_Rock2 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
dayouziei3 小时前
java的类加载机制的学习
java·学习
热爱跑步的恒川3 小时前
【论文复现】基于图卷积网络的轻量化推荐模型
网络·人工智能·开源·aigc·ai编程
云飞云共享云桌面4 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮5 小时前
Linux 使用中的问题
linux·运维
音徽编程6 小时前
Rust异步运行时框架tokio保姆级教程
开发语言·网络·rust
dsywws6 小时前
Linux学习笔记之vim入门
linux·笔记·学习
晨曦_子画7 小时前
3种最难学习和最容易学习的 3 种编程语言
学习