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;
}

运行后结果如下:

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

相关推荐
xq5148636 分钟前
Linux系统下安装mongodb
linux·mongodb
柒七爱吃麻辣烫7 分钟前
在Linux中安装JDK并且搭建Java环境
java·linux·开发语言
起床学FPGA12 分钟前
异步FIFO的学习
学习·fpga开发
依年南台22 分钟前
搭建大数据学习的平台
大数据·学习
孤寂大仙v43 分钟前
【Linux笔记】——进程信号的产生
linux·服务器·笔记
小虎卫远程打卡app1 小时前
视频编解码学习10之成像技术原理
学习·计算机视觉·视频编解码
深海蜗牛1 小时前
Jenkins linux安装
linux·jenkins
愚戏师1 小时前
Linux复习笔记(三) 网络服务配置(web)
linux·运维·笔记
JANYI20182 小时前
嵌入式MCU和Linux开发哪个好?
linux·单片机·嵌入式硬件
X Y O2 小时前
神经网络初步学习——感知机
人工智能·神经网络·学习·感知机