网络编程套接字(二)之UDP服务器简单实现

目录

一、服务端UdpServer

1、udp_server.hpp

1、服务器的初始化

2、服务器的运行

2、udp_server.cc

二、客户端UdpClient

udp_client.cc

三、完整代码


一、服务端UdpServer

1、udp_server.hpp

首先,我们在该文件中,将服务器封装成一个类,而作为一款服务器,必须要有自己的端口号,同时网络服务器需要有对应的IP地址,文件描述符sock_:进行各种各样的数据通信,在类内进行读写操作。然后对外提供初始化和运行的接口。

1、服务器的初始化

我们最开始需要先将它进行初始化。初始化的第一步就是创建套接字,而创建套接字我们需要用到下面的函数。

socket:其作用就是创建套接字。

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:域,用来表明套接字是进行网络通信还是本地通信,主要使用下面这两种 :AF_UNIX(本地通信)   AF_INET(网络通信)。

~ type:创建套接字时所需的服务类型。其中最常使用的是SOCK_STREAM和SOCK_DGRAM。如:UDP是数据报的网络通信形式,我们采用的就是SOCK_DGRAM(用户数据报服务),TCP是面向字节流式的网络通信,我们采用的就是SOCK_STREAM(叫做流式套接字,提供的是流式服务)。

~ protocol:创建套接字的协议类别。该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。

~ 返回值:套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。

初始化的第二步就是绑定端口号和IP,我们需要用到bind函数

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

参数说明:

~ sockfd:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。

~ addr:网络相关的属性信息,包括协议家族、IP地址、端口号等。

~ addrlen:传入的addr结构体的长度。 

~ 返回值:绑定成功返回0,绑定失败返回-1,同时错误码会被设置。

在绑定时需要将服务器网络相关的属性信息填充到结构体struct addr_in当中,其结构如下:

我们一般需要填充下面三个成员:

sin_family:填充AF_INET。

sin_port:表示服务器端口号,是一个16位的整数。需要注意主机序列和网络序列的转化。

sin_addr:表示服务器IP地址,是一个32位的整数。我们发现这个结构是一个结构体,所以一般是填充其中的成员:是一个32位的整数。

我们所看到的IP地址是点分十进制的,但是真正的IP地址是整数,所以我们在使用是需要将IP地址转换成系统能识别32位的整数,也需要注意主机序列和网络序列的转化,这些操作我们使用函数inet_addr可以一并实现。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);

注:实际上,一款网络服务器不建议指明一个IP,也就是不要显示地绑定IP,因为一个服务器上可能会有多张网卡,所以IP可能不止一个,如果只绑定一个明确的IP,最终的数据可能用别的IP来访问端口号,这就无法访问,所以真实的服务器IP一般采用INADDR_ANY(全0,任意地址)代表任意地址bind 。

所以我们在填充IP地址时最好使用INADDR_ANY。

2、服务器的运行

首先,作为一款服务器,我们必须能够随时给用户提供服务,所以服务器是一个不能够退出的进程,需要使用死循环。然后不断接收从客户端发送过来的请求,进行处理,将结果返回给客户端。

我们一般使用recvform函数接收客户端发送过来的请求:

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

参数说明:

sockfd:服务器绑定的套接字,buf:读取到特定缓冲区,len:缓冲区长度。

flags:读取的方式,默认为0,阻塞读取。

src_addr:收到除了消息本身,还得知道是谁发的,输入输出型参数,返回对应的消息内容是从哪一个客户端来的,len:src_addr大小。

返回值:返回-1表示失败,成功返回字节数

服务器收到消息,进行处理后,我们需要将结果发回给客户端,我们一般使用sendto函数:

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

2、udp_server.cc

服务端进行调用的代码逻辑:构建udpServer的对象,然后进行初始化,在进行启动起来;调用逻辑如下:

因为运行后服务器会自动绑定所有的IP,所以我们只需要绑定端口号即可。

#include <iostream>
#include "udp_server.hpp"
#include <string>
#include <memory>

static void usage(const std::string &proc)
{
    std::cout << "\nusage: " << proc << "  port\n" << std::endl;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(0);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UdpServer> usvr(new UdpServer(port));
    usvr->InitServer();
    usvr->start();

    return 0;
}

二、客户端UdpClient

udp_client.cc

客户端调用方式:./udpClient server_ip server_port,客户端想连接服务器,必须得知道服务器的IP(公网IP)以及端口号。

客户端的实现方式:创建套接字,发送请求给服务器,接收服务器返回的结果。

需要注意的是:在服务端bind的时候,最重要的不是绑定IP,而是绑定端口号,服务器需要显示地绑定端口号是为了客户端未来能够明确地找到服务器是对应的服务端进程,不能随意改变。

而客户端虽然也需要端口号,但是不重要,自己启动后有端口号就可以了,不需要显示地绑定是哪一个。因为如果不同公司的程序员在写不同软件客户端时,绑定了同一个端口号的话,就会发生冲突,所以我们在客户端不需要程序员自己去绑定端口号,而是由系统随机分配。

三、完整代码

udp_server.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <string>

class UdpServer
{
public:
    UdpServer(const uint16_t &port, std::string ip = "")
        : ip_(ip), port_(port), sock_(-1)
    {
    }

    void InitServer()
    {
        // 1.创建套接字
        sock_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sock_ < 0)
        {
            std::cerr << "创建套接字失败!" << errno << strerror(errno) << std::endl;
            exit(0);
        }

        // 2.进行绑定:绑定ip和端口号
        // 2.1 服务器的套接字填充结构
        struct sockaddr_in local_server;
        bzero(&local_server, sizeof(local_server));
        local_server.sin_family = AF_INET;
        local_server.sin_port = htons(port_);                                             // 端口号需要从主机转网络
        local_server.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str()); // ip地址需要主机转网络,并且要转化成4字节形式
        int len = sizeof(local_server);
        if (bind(sock_, (struct sockaddr *)&local_server, len))
        {
            std::cout << "绑定失败!" << errno << strerror(errno) << std::endl;
            exit(1);
        }
        std::cout << "服务器初始化完成!" << std::endl;
    }

    void start()
    {
        std::cout << "服务器运行成功!" << std::endl;
        char buffer[1024];
        char result[1024];
        std::string server_echo;
        // 服务器不能停下来,除非挂掉——死循环
        for (;;)
        {
            struct sockaddr_in src_client; // 服务器收到的客户端来源
            socklen_t len = sizeof(src_client);
            ssize_t s = recvfrom(sock_, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&src_client, &len);
            if (s < 0)
            {
                std::cout << "接收消息失败!" << std::endl;
            }
            buffer[s] = 0;
            //std::string src_client_ip = inet_ntoa(src_client.sin_addr);
            //uint16_t src_client_port = ntohs(src_client.sin_port);
            //std::cout << "[" << src_client_ip << "," << src_client_port << "]: " << buffer << std::endl;


             // 客户端发过来的信息是指令,服务器将结果返回给客户端
            FILE *fp = popen(buffer, "r");
            if (fp == nullptr)
            {
                std::cout << "popen失败!" << std::endl;
                continue;
            }
            while (fgets(result, sizeof(result), fp) != nullptr)
            {
                server_echo += result;
            }

            // 服务器发回消息
            sendto(sock_, server_echo.c_str(), server_echo.size(), 0, (struct sockaddr *)&src_client, len);
            //sendto(sock_, buffer, sizeof(buffer), 0, (struct sockaddr *)&src_client, len);
        }
    }

private:
    std::string ip_; // 服务器ip地址
    uint16_t port_;  // 服务器端口号
    int sock_;       // 套接字
};

udp_server.cc

#include <iostream>
#include "udp_server.hpp"
#include <string>
#include <memory>

static void usage(const std::string &proc)
{
    std::cout << "\nusage: " << proc << "  port\n" << std::endl;
}

// ./udpserver port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(0);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UdpServer> usvr(new UdpServer(port));
    usvr->InitServer();
    usvr->start();

    return 0;
}

udp_client.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cerrno>
#include <unistd.h>
#include <string>
#include <stdio.h>
#include <cstring>

static void usage(const std::string &proc)
{
    std::cout << "\nusage: "
              << proc << "  port ip\n"
              << std::endl;
}

// ./udpclient ip port  带上服务器的ip和端口号
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(0);
    }
    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "创建套接字失败!" << errno << strerror(errno) << std::endl;
        exit(1);
    }
    // 作为客户端,其不需要进行绑定,由os自动进行绑定
    std::string message;
    // 给哪个服务器发送消息,填充服务器信息
    struct sockaddr_in send_server;
    bzero(&send_server, sizeof(send_server));
    send_server.sin_family = AF_INET;
    send_server.sin_port = htons(atoi(argv[2]));
    send_server.sin_addr.s_addr = inet_addr(argv[1]);
    while (true)
    {
        // 2.从键盘获取信息
        std::cout << "请输入#";
        std::getline(std::cin, message);
        if (message == "quit")
            break;
        // 3.发送消息
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&send_server, sizeof(send_server));

        // 4.接收服务器的消息
        char buffer[1024];
        struct sockaddr_in revc_server;
        bzero(&revc_server, sizeof(revc_server));
        socklen_t len = sizeof(revc_server);
        ssize_t s = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&revc_server, &len);
        std::cout << "服务器说:" << buffer << std::endl;
    }
    close(sock);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/544661.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络抓包工具使用

一、下载安装 &#xff08;1&#xff09; linux&#xff1a; ① 使用 yum install tcpdump -y 安装 **tcpdump**工具 ② 编译安装 yum -y install gcc-c yum -y install flex yum -y install bison官网下载tcpdump和libpcap 官网地址:https://www.tcpdump.org/index.html#lat…

网红天水海英麻辣烫改名:还是商标的问题!

近日火爆网络的天水海英麻辣烫改名&#xff0c;改成“哈海英麻辣烫”&#xff0c;并打了TM&#xff0c;表示此商标名称商标局已经受理并下发通知书&#xff0c;普推知产老杨检索分析&#xff0c;改名的主要原因还是商标。 对于餐饮店和麻辣烫核心类别就在43类别及30类方便食品&…

表格单列相同字段值合并

用specName(el.specName row.specName)和id的区别(el.id row.id)&#xff0c;使用id的时候id是唯一值&#xff0c;判断的时候不会出现重复情况&#xff0c;使用specName的时候&#xff0c;如果有重复的值&#xff0c;会出现合并错位的情况。 解决方案&#xff1a;先按照specT…

【树哈希】CF1182D Complete Mirror

CF1182D - Complete Mirror Description 给定一个 n n n 个点的无根树&#xff0c;求一个树根 r o o t root root,使得对于任意两个节点 v 1 , v 2 v_1,v_2 v1​,v2​&#xff0c;若满足 d i s t ( v 1 , r o o t ) d i s t ( v 2 , r o o t ) dist(v_1,root)dist(v_2,ro…

CUDA编程---全局内存

CUDA内存模型概述 内存的访问和管理是所有编程语言的重要部分。在现代加速器中&#xff0c;内存管理对高性能计算有着很大的影响。因为多数工作负载被加载和存储数据的速度所限制&#xff0c;所以有大量低延迟、高带宽的内存对性能是十分有利的。 然而&#xff0c;大容量、高性…

第十五届蓝桥杯省赛C/C++大学B组真题及赛后总结

目录 个人总结 C/C 组真题 握手问题 小球反弹 好数 R 格式 宝石组合 数字接龙 爬山 拔河 ​编辑 再总结及后续规划 个人总结 第一次参加蓝桥杯&#xff0c;大二&#xff0c;以前都在在学技术&#xff0c;没有系统的学过算法。所以&#xff0c;还是花了挺多时间去备…

【8086汇编】汇编语言基础入门

文章目录 一、汇编简介1. 汇编语言的组成2. CPU、寄存器、内存3. CPU对存储器的读写4. 拓展5. 检测6. 解析 二、寄存器1. mov、add命令2. 物理地址3. CS:IP 装段地址和偏移地址3.1 如何改变CS:IP的值 4. 数据段DS:[address]4.1 前置知识&#xff1a;字与字节4.2 DS:[address] 5…

串口RS485

1.原理 全双工&#xff1a;在同一时刻可以同时进行数据的接收和数据的发送&#xff0c;两者互不影响 半双工&#xff1a;在同一时刻只能进行数据的接收或者数据的发送&#xff0c;两者不能同时进行 差分信号幅值相同&#xff0c;相位相反&#xff0c;有更强的抗干扰能力。 干…

【C语言】N子棋小游戏♔

目录 前言 一、何为N子棋游戏&#xff1f; 二、游戏思路 三、游戏实现 3.1 模块化 3.2 游戏棋盘 3.3 下棋操作 3.3.1 玩家下棋 3.3.2 电脑下棋 3.4 判断输赢 总结 前言 三子棋小游戏相信大家都玩过吧&#xff0c;类似的5子琪等等&#xff0c;这篇文章将带着大家从0到…

【leetcode面试经典150题】28.盛最多水的容器(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

分享一些有趣的 Linux 命令

1、sl 会显示一辆火车穿过你的终端屏幕 2、cmatrix 在终端中显示类似于《黑客帝国》电影中的绿色数字雨效果 3、fortune 显示一个随机的名人名言或者笑话 4、cowsay 让一头牛说出你输入的话 5、toilet 在终端中将输入的文本以艺术字体的形式呈现 6、figlet 类似于 toile…

回溯算法初识

文章目录 回溯算法初识什么是回溯算法回溯算法的步骤回溯算模版例题 回溯算法初识 什么是回溯算法 ​ 回溯算法是一种通过不断尝试可能的解决方案来解决问题的算法。它通常用于解决组合优化问题&#xff0c;如排列组合问题、子集和问题等。该算法通过尝试所有可能的候选解&am…

【热门话题】常见分类算法解析

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 常见分类算法解析1. 逻辑回归&#xff08;Logistic Regression&#xff09;2. 朴…

【设计模式】聊聊观察者设计模式原理及应用

原理 观察者模式属于行为模式&#xff0c;行为模式主要解决类和对象之间交互问题。 含义&#xff1a;在对象之间定义一个一对多的依赖&#xff0c;当一个对象状态改变时&#xff0c;所有依赖的对象会自动通知。 被依赖的对象被观察者(Observable) &#xff0c;依赖的对象观察…

移动Web学习06-移动端适配Less预处理器项目案例

项目目标&#xff1a;实现在不同宽度设备中等比缩放的网页效果 Less代码 import ./base; import ./normalize;// 变量: 存储37.5 rootSize: 37.5rem; *{margin: 0;padding: 0; } body {background-color: #F0F0F0; }// 主体内容 .main {// padding-bottom: (50 / 37.5rem);pa…

缺失msvcr110.dll要怎么处理?快捷的修复msvcr110.dll方法

当你在使用电脑进行工作或娱乐时&#xff0c;可能会突然遇到一个错误提示&#xff1a;“程序无法启动&#xff0c;因为电脑中缺失msvcr110.dll”。这样的情况不仅会打断你的活动&#xff0c;还可能带来一定程度的不便。面对这个在Windows操作系统中相对常见的问题&#xff0c;其…

IDEA2023 开发环境配置

目录 1. 关闭IDEA自动更新1.2 IDEA 新版样式切换 2. Maven配置2.1本地仓库优先加载2.2 maven.config配置文件中 3. 全局配置JDK4. 配置文件编码:UTF-85. 开启自动编译&#xff08;全局配置&#xff09;6. 开启自动导包7. 开启鼠标悬浮&#xff08;提示文档信息&#xff09;8. 设…

7 个适用于 Windows 的最佳电脑分区数据恢复软件

磁盘分区对于正确存储数据以便从硬盘驱动器快速轻松地访问非常有帮助。但是&#xff0c;如果分区损坏&#xff0c;存储在其中的所有数据都会突然变得无法访问。磁盘分区损坏的原因可能有很多&#xff0c;其中最突出的是病毒攻击、突然断电、物理损坏或由于创建坏扇区。 但是&a…

gzip,bzip2,xz,tar-读书笔记(九)

gzip 将文件进行压缩 在Linux系统中&#xff0c;gzip 是一个压缩和解压文件的命令工具。它使用LZ77压缩算法及霍夫曼编码&#xff08;Huffman Coding&#xff09;来压缩文件&#xff0c;通常用来减少文件的大小&#xff0c;以节约磁盘空间或减少网络传输的时间。 gzip 命令的…

Linux gcc 6

本章开始学习工具 什么是工具&#xff1f; 本质也是指令 yum 命令 小火车 sudo yum install sl&#xff08;安装sl&#xff09; sudo yum install -y sl //直接yes就不提示了 yum list //将yum源上的软件都穷举出来 yum search sl //结果不友好&#xff0c;不推荐 yum lis…