【网络编程】demo版UDP网络服务器实现

news2024/9/29 7:32:27

文章目录

  • 一、引入
  • 二、服务端实现
    • 2.1 创建套接字socket
    • 2.2 绑定bind
    • 2.3 启动服务器
    • 2.4 IP的绑定
    • 2.5 读取数据recvfrom
  • 三、用户端实现
    • 3.1 绑定问题
    • 3.2 发送数据sendto
  • 四、源码

一、引入

在上一章【网络编程】socket套接字中我们讲述了TCP/UDP协议,这一篇就是简单实现一个UDP协议的网络服务器。
我们也讲过其实网络通信的本质就是进程间通信。而进程间通信无非就是读和写(IO)。
所以现在我们就要写一个服务端(server)接收数据,客户端(client)发送数据。

二、服务端实现

通过上一章的介绍,要通信首先需要有IP地址,和绑定端口号。

uint16_t _port;
std::string _ip;

2.1 创建套接字socket

在通信之前要先把网卡文件打开。

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

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

RETURN VALUE
On success, a file descriptor for the new socket is returned.  
On error, -1 is returned, and errno is set appropriately.

这个函数的作用是打开一个文件,把文件和网卡关联起来。

参数介绍:

domain:一个域,标识了这个套接字的通信类型(网络或者本地)。
在这里插入图片描述
只用关注上面两个类,第一个AF_UNIX表示本地通信,而AF_INET表示网络通信。
type:套接字提供服务的类型。
在这里插入图片描述
这一章我们讲的式UDP,所以使用SOCK_DGRAM
protocol:想使用的协议,默认为0即可,因为前面的两个参数决定了,就已经决定了是TCP还是UDP协议了。

返回值:

成功则返回打开的文件描述符(指向网卡文件),失败返回-1。

而从这里我们就联想到系统中的文件操作,未来各种操作都要通过这个文件描述符,所以在服务端类中还需要一个成员变量表示文件描述符。

static const std::string defaultip = "0.0.0.0";// 默认IP

class UDPServer
{
public:
    UDPServer(const uint16_t& port, const std::string ip = defaultip)
        : _port(port)
        , _ip(ip)
        , _sockfd(-1)
    {}

    void InitServer()
    {
    	// 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd == -1)
        {
            std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
            exit(1);
        }
    }
private:
    uint16_t _port;
    std::string _ip;
    int _sockfd;
};

创建完套接字后我们还需要绑定IP和端口号。

2.2 绑定bind

#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address,
       socklen_t address_len);

RETURN VALUE
Upon successful completion, bind() shall return 0; 
otherwise, -1 shall be returned and errno set to indicate the error.

参数介绍:

socket:创建套接字的返回值。
address:通用结构体(上一章【网络编程】socket套接字有详细介绍)。
address_len:传入结构体的长度。

所以我们要先定义一个sockaddr_in结构体填充数据,在传递进去。
在这里插入图片描述

struct sockaddr_in {
    short int sin_family;           // 地址族,一般为AF_INET或PF_INET
    unsigned short int sin_port;    // 端口号,网络字节序
    struct in_addr sin_addr;        // IP地址
    unsigned char sin_zero[8];      // 用于填充,使sizeof(sockaddr_in)等于16
};

创建结构体后要先清空数据(初始化),我们可以用memset,系统也提供了接口:

#include <strings.h>

void bzero(void *s, size_t n);

填充端口号的时候要注意端口号是两个字节的数据涉及到大小端问题

大小端转化接口:

#include <arpa/inet.h>
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

对于IP,首先我们要先转成整数,再要解决大小端问题。
系统给了直接能解决这两个问题的接口:

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

int inet_aton(const char *cp, struct in_addr *inp);

in_addr_t inet_addr(const char *cp);// 点分十进制字符串

in_addr_t inet_network(const char *cp);

char *inet_ntoa(struct in_addr in);

struct in_addr inet_makeaddr(int net, int host);

in_addr_t inet_lnaof(struct in_addr in);

in_addr_t inet_netof(struct in_addr in);

这里的inet_addr就是把一个点分十进制的字符串转化成整数再进行大小端处理。

整体代码:

void InitServer()
{
    // 创建套接字
    _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(_sockfd == -1)
    {
        std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
        exit(1);
    }
    // 绑定IP与port
    struct sockaddr_in si;
    bzero(&si, sizeof si);
    si.sin_family = AF_INET;// 协议家族
    si.sin_port = htons(_port);// 端口号,注意大小端问题
    si.sin_addr.s_addr = inet_addr(_ip.c_str());// ip
    // 绑定
    int n = bind(_sockfd, (struct sockaddr*)&si, sizeof si);
    assert(n != -1);
}

2.3 启动服务器

首先要知道服务器要死循环,永远不退出,除非用户删除。站在操作系统的角度,服务器是常驻内存中的进程。

而我们启动服务器的时候要传递进去IP和端口号。

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        std::cout << "incorrect number of parameters" << std::endl;
        exit(1);
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);
    std::unique_ptr<UDPServer> ptr(new UDPServer(port, ip));
    
    return 0;
}

那么IP该怎么传递呢?

2.4 IP的绑定

在这里插入图片描述
这里的127.0.0.1叫做本地环回

它的作用就是用来做服务器代码测试的,意思就是如果我们绑定的IP是127.0.0.1的话,在应用层发送的消息不会进入物理层,也就不会发送出去。
在这里插入图片描述
当我们运行起来后想要查看网络情况就可以用指令netstat
后边也可以附带参数:

-a:显示所有连线中的Socket;
-e:显示网络其他相关信息;
-i:显示网络界面信息表单;
-l:显示监控中的服务器的Socket;
-n:直接使用ip地址(数字),而不通过域名服务器;
-p:显示正在使用Socket的程序识别码和程序名称;
-t:显示TCP传输协议的连线状况;
-u:显示UDP传输协议的连线状况;

在这里插入图片描述

在这里插入图片描述
那我们如果想要全网通信呢?该用什么IP呢?难道是云服务器上的公网IP吗?
在这里插入图片描述
但是我们发现绑定不了。
因为云服务器是虚拟化服务器(不是真实的IP),不能直接绑定公网IP。

既然公网IP邦绑定不了,那么内网IP(局域网IP)呢?
在这里插入图片描述
在这里插入图片描述
答案是可以,说明这个IP是属于这个服务器的
但是这里不是一个内网的就无法找到。

所以现在的问题是服务器启动后怎么收到信息呢?(消息已经发送到主机,现在要向上交付)
实际上,一款服务器不建议指明一个IP。因为可能服务器有很多IP,如果我们绑定了一个比如说IP1,那么其他进程发送给IP2服务器就收不到了。

在这里插入图片描述
这里的INADDR_ANY实际上就是0,这样绑定后,发送到这台主机上所有的数据,只要是访问绑定的端口(8080)的,服务器都能收到。这样就不会因为绑定了一个具体的IP而漏掉其他IP的信息

static const std::string defaultip = "0.0.0.0";// 默认IP

所以现在我们就不需要传递IP了。

2.5 读取数据recvfrom

服务端要获取到用户端发送过来的数据。

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

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

参数说明:

sockfd:从哪个套接字读。
buf:数据放入的缓冲区。
len:缓冲区长度。

flags:读取方式。 0代表阻塞式读取。
src_addraddrlen:输出型参数,返回对应的消息内容是从哪一个客户端发出的。第一个是自己定义的结构体,第二个是结构体长度。

void start()
{
    char buf[1024];
    while(1)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof peer;
        sssize_t s = recvfrom(_sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)&peer, &len);
    }
}

现在我们想要知道是谁发送过来的消息,信息都被保存到了peer结构体中,我们知道IP信息在peer.sin_addr.s_addr中,首先这是一个网络序列,要转成主机序列,其次为了方便观察,要把它转换成点分十进制
而这两个操作系统给了一个接口能够解决:

char *inet_ntoa(struct in_addr in);

同样获取端口号的时候也要由网络序列转成主机序列:

uint16_t ntohs(uint16_t netshort);

整体代码:

void start()
{
    char buf[1024];
    while(1)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof peer;
        ssize_t s = recvfrom(_sockfd, buf, sizeof buf - 1, 0, (struct sockaddr*)&peer, &len);
        if(s > 0)
        {
            std::string cip = inet_ntoa(peer.sin_addr);
            uint16_t cport = ntohs(peer.sin_port);
            std::string msg = buf;
            std::cout << " [" << cip << "@" << cport << " ]# " << msg << std::endl;
        }
    }
}

现在只需要等待用户端发送数据即可。

三、用户端实现

首先我们要发送数据,就得知道客户端的IP和port
而这里的IP就必须指明

uint16_t _serverport;
std::string _serverip;
int _sockfd;

这里的IP和port指的是要发送给谁。

创建套接字就跟前面的一样:

// 创建套接字
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(_sockfd == -1)
{
    std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
    exit(1);
}

3.1 绑定问题

这里的客户端必须绑定IP和端口,来表示主机唯一性和进程唯一性。
但是不需要显示的bind

那么为什么前面服务端必须显示的绑定port呢?

因为服务器的端口号是众所周知的,不能改变,如果变了就找不到服务器了。

而客户端只需要有就可以,只用标识唯一性即可。
举个例子:

我们手机上有很多的app,而每个服务端是一家公司写的,但是客户端却是多个公司写的。如果我们绑定了特定的端口,万一两个公司都用了同一个端口号呢?这样就直接冲突了。

所以操作系统会自动形成端口进行绑定。(在发送数据的时候自动绑定)
所以创建客户端我们只用创建套接字即可。

void InitClient()
{
    // 创建套接字
    _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(_sockfd == -1)
    {
        std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
        exit(1);
    }
}

3.2 发送数据sendto

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

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

这里的参数和上面的recvfrom差不多,而这里的结构体内部我们要自己填充目的IP和目的端口号。

void start()
{
    struct sockaddr_in si;
    bzero(&si, sizeof(si));
    si.sin_family = AF_INET;
    si.sin_addr.s_addr = inet_addr(_serverip.c_str());
    si.sin_port = htons(_serverport);
    std::string msg;
    while(1)
    {
        std::cout << "Please input: ";
        std::cin >> msg;
        sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&si, sizeof si);
    }
}

在这里插入图片描述
在这里插入图片描述
当然这里是同一台主机之间测试,如果是不同的机器,我们传递参数的时候就要传递公网IP,例如我们这台云服务器的公网IP是:
在这里插入图片描述
我们在运行的时候:

./UDPClient 43.143.106.44 8080

四、源码

// UDPServer.hpp
#pragma once 
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <string.h>
#include <cassert>
#include <functional>

static const std::string defaultip = "0.0.0.0";// 默认IP

class UDPServer
{
public:
    UDPServer(const uint16_t& port, const std::string ip = defaultip)
        : _port(port)
        , _ip(ip)
        , _sockfd(-1)
    {}

    void InitServer()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd == -1)
        {
            std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
            exit(1);
        }
        // 绑定IP与port
        struct sockaddr_in si;
        bzero(&si, sizeof si);
        si.sin_family = AF_INET;// 协议家族
        si.sin_port = htons(_port);// 端口号,注意大小端问题
        // si.sin_addr.s_addr = inet_addr(_ip.c_str());// ip
        si.sin_addr.s_addr = INADDR_ANY;
        // 绑定
        int n = bind(_sockfd, (struct sockaddr*)&si, sizeof si);
        assert(n != -1);
    }

    void start()
    {
        char buf[1024];
        while(1)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof peer;
            ssize_t s = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                buf[s] = 0;// 结尾
                std::string cip = inet_ntoa(peer.sin_addr);
                uint16_t cport = ntohs(peer.sin_port);
                std::string msg = buf;
                std::cout << "[" << cip << "@" << cport << "]# " << msg << std::endl;
            }
        }
    }
private:
    uint16_t _port;
    std::string _ip;
    int _sockfd;
};

// UDPServer.cc
#include "UDPServer.hpp"
#include <memory>

int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cout << "incorrect number of parameters" << std::endl;
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UDPServer> ptr(new UDPServer(port));
    ptr->InitServer();
    ptr->start();
    return 0;
}

// UDPClient.hpp
#pragma once 
#include <iostream>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <netinet/in.h>
#include <string.h>
#include <cassert>

class UDPClient
{
public:
    UDPClient(const std::string& serverip, const uint16_t& port)
        : _serverip(serverip)
        , _serverport(port)
        , _sockfd(-1)
    {}

    void InitClient()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_sockfd == -1)
        {
            std::cerr << "socket error" << errno << " : " << strerror(errno) << std::endl;
            exit(1);
        }
    }

    void start()
    {
        struct sockaddr_in si;
        bzero(&si, sizeof(si));
        si.sin_family = AF_INET;
        si.sin_addr.s_addr = inet_addr(_serverip.c_str());
        si.sin_port = htons(_serverport);
        std::string msg;
        while(1)
        {
            std::cout << "Please input: ";
            std::cin >> msg;
            sendto(_sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr*)&si, sizeof si);
        }
    }
private:
    uint16_t _serverport;
    std::string _serverip;
    int _sockfd;
};

// UDPClient.cc
#include "UDPClient.hpp"
#include <memory>

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        std::cout << "incorrect number of parameters" << std::endl;
        exit(1);
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);
    std::unique_ptr<UDPClient> ptr(new UDPClient(ip, port));
    ptr->InitClient();
    ptr->start();
    return 0;
}


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保护移动设备免受恶意软件侵害优秀方法

几天前&#xff0c;移动恶意软件攻击增加了500%显然&#xff0c;我们大多数人都不知道不能很好地保护我们的手机下面小编揭秘有效保护移动设备免受恶意软件侵害的最佳方法。 1、使用移动反恶意软件 恶意软件很容易感染智能手机和平板电脑&#xff0c;因此在设备上安装可靠的…

douyin 之xgorgon0404参数

如果我们想要看抖音的数据&#xff0c;在抓包的时候&#xff0c;会发现有一个xgorgon参数,在请求接口的时候&#xff0c;只需要在请求头携带xgorgon参数&#xff0c;就能获得响应后的数据。 目前抖音的xgorgon0404算法已经还原了&#xff0c; 下面是一个请求的demo。代码如下:…

计算机网络可靠传输的三种基本实现机制

可靠传输的means 在数据链路层会给上层网络层提供可靠传输或者不可靠传输&#xff0c;不可靠传输是指检测到传输差错的时候只是丢弃该分组而不进行任何处理&#xff0c;而可靠传输会给发送者一个信号进行重发该分组。 以下给出的三种基本实现机制是提出一些问题&#xff0c;慢…

截图的背景色如何去除?这里介绍一个小工具

屏幕截图&#xff0c;是方便常用的功能。例如从网页或者视频中截图。但是有时候想去除截图中的背景颜色&#xff0c;怎么办&#xff1f;下面这个案例介绍如何去掉截图中的蓝色背景色。 这个小工具就能帮你方便实现。Web端的便捷小工具链接: http://www.artvily.com/renderCase…

深度学习框架发展趋势

深度学习方法的发展是推动深度学习框架进步的最大动力&#xff0c;因此深度学习框架的功能和设计应顺应 算法和模型的发展趋势&#xff1a; 第一&#xff0c;易用性。深度学习领域仍处于快速发展期&#xff0c;参与者和学习者不断增加&#xff0c;新模型大量提出。因 此&#…

深入篇【C++】类与对象:拷贝构造函数详解

深入篇【C】类与对象&#xff1a;拷贝构造函数详解 ①.拷贝构造函数Ⅰ.概念Ⅱ.特征1.重载形式之一2.参数唯一3.形参必须传引用4.编译器的拷贝函数5.典型调用场景 ②.总结&#xff1a; ①.拷贝构造函数 Ⅰ.概念 在创建对象时&#xff0c;能否创建一个与已存在对象一模一样的新…

[架构之路-186]-《软考-系统分析师》-5-数据库系统 - 关系型数据库、操作型数据库、数据集市、数据仓库的区别?

目录 总体架构&#xff1a; 一、数据库 二、关系型数据库 三、SQL与数据库 四、 分布式数据库 五、数据仓库 1. 概述 2. 架构 3、数据仓库的主要特征 3、1 面向主题性 3、2 集成性 3、3 非易失性 3、4 时变性 总体架构&#xff1a; 一、数据库 数据库是“按照数据…

【链表OJ题 1】反转链表

目录 题目来源&#xff1a; 代码实现 1、方法一 1.1分析 2、方法二 2.1 分析 题目来源&#xff1a; 力扣 题目描述&#xff1a; 代码实现 1、方法一 struct ListNode* reverseList(struct ListNode* head) {struct ListNode* prev NULL, * cur head;while (cur){st…

基础IO+文件

基础IO 回顾文件回顾文件操作库函数调用接口写文件-w读文件-r 系统调用打开文件-open写文件-write 文件操作本质文件描述符fd文件描述符的分配规则重定向 FILE缓冲区模拟实现缓冲区总结 理解文件系统磁盘物理结构存储结构逻辑结构 文件系统软硬链接 动静态库动态库和静态库生成…

Kafka上的优化经验

1. 平滑扩容 Motivation kafka扩容⼀台新机器的流程 假如集群有 3 个 broker &#xff0c;⼀共有 4 个 TP &#xff0c;每个 3 副本&#xff0c;均匀分布。现在要扩容⼀台机器&#xff0c; 新 broker 加⼊集群后需要通过⼯具进⾏ TP 的迁移。⼀共迁移 3 个 TP 的副…

JavaScript高级程序设计(第2版)——读书笔记

文章目录 第1章 JavaScript简介第2章 在HTML中使用JavaScript第3章 基本概念第4章 变量、作用域、内存问题第5章 引用类型第6章 面向对象的程序设计第7章 匿名函数第8章 BOM第9章 客户端检测第10章 DOM第11章 DOM2和DOM3第12章 事件第13章 表单脚本第14章 错误处理与调试第15章…

java 的参数传递

一、疑惑引入 首先&#xff0c;我们从一个例子来引出这个问题&#xff1a; public static void main(String[] args) throws IOException {List<String> mockList Lists.newArrayList("a", "b");System.out.println("1: " mockList);L…

【GAMES101】04 Viewing Transformation

1.View/Camera Transformation&#xff08;视图变换&#xff09; 1、将准备拍摄的对象移动到场景中指定位置。&#xff08;模型变换&#xff0c;Model Transform&#xff09; - 模型导入场景中从模型坐标系转换成世界坐标系 2、将相机移动到准备拍摄的位置&#xff0c;将它对准…

【网络】-- TCP协议

其中TCP就属于传输层&#xff0c;并且端口号也是在传输层起作用。 目录 TCP协议报头 可靠性 32位序号 16位窗口大小 六个标记位 三次握手四次挥手 RST PSH URG 16位紧急指针 FIN socksetopt 可靠性机制 确认应答(ACK)机制 超时重传机制 连接管理机制 三大机…

03-角色维护 尚筹网

一、分页操作 目标 将角色数据进行分页显示 思路 点击后台主页面的权限管理->角色维护&#xff0c;通过view-controller进入角色分页显示的页面&#xff0c;浏览器加载页面的数据并初始化一些数据&#xff08;页码、页大小、关键词等&#xff09;&#xff0c;调用分页函…

基于Open3D的点云处理3-可视化

可视化接口 API open3d.visualization.draw_geometries(*args, **kwargs)重载函数1 draw_geometries(geometry_list, window_name’Open3D’, width1920, height1080, left50, top50, point_show_normalFalse, mesh_show_wireframeFalse, mesh_show_back_faceFalse)geometry…

B树

文章目录 B树的定义和性质为什么需要B树B树的定义 B树的模拟实现节点的数据结构B树的插入B树的删除 B树的模拟实现 B树的定义和性质 我们之前已经对 平衡搜索二叉树有了一定的了解&#xff0c;学习了两种树——AVL树 和 红黑树&#xff0c;下面介绍一下B树 为什么需要B树 数…

Nacos 服务网格⽣态

博主介绍&#xff1a;✌全网粉丝4W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战、定制、远程&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面…

为一加七Pro(LineageOs17.1 4.14内核版本)编译KernelSu

编译内核 因为一加七的内核版本是4.14&#xff0c;所以想使用kernelsu&#xff0c;需要自己将kernelsu编译到内核里。 我使用的系统是&#xff1a;LineageOS17.1&#xff0c;对于之后的lineage版本同样适用&#xff0c;只是拉取的源代码不一样。刷机教程请看&#xff1a;wsl2…

vue diff算法与虚拟dom知识整理(3) 了解h函数和虚拟节点概念 实现虚拟节点上dom树

虚拟dom之前我们也有了基本的了解 简单说 就是用js数据结构来描述html的dom结构树 首先 为什么要用虚拟dom啊&#xff1f; 官方给出的回答是 diff最小量精细化算法是发生在虚拟dom上的 也就是 我们之前说的 节点与节点比较 并不是发生在html元素上的 而是发生在js中的虚拟dom上…