Linux--Upd--套接字编程(单线程和多线程版本)--0215 16

news2024/9/22 18:55:37

观前提示:

本文涉及了以前博文实现的相关内容,在此贴出

线程的封装 Thread.hpp 及 日志

Liunx--线程池的实现--0208 09_Gosolo!的博客-CSDN博客

1. 网络编程相关接口

1.1 创建套接字

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);

返回值是一个套接字

参数:

domain 域,创建哪一种类型的套接字 AF_INET 表示是网络通信类型

type 通信种类

protocol 设为0值即可

1.2 绑定套接字

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);

成功返回0

sockfd 要绑定的套接字

addr 是sockaddr这个通用的结构体,我们使用网络传输时,就需要创建sockaddr_in结构体,然后将他强转为 sockaddr 结构体。

addrlen 传入的结构体的长度

1.2.1 sockaddr_in结构体

1.3 读数据

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

返回读到的字节数,失败为-1

buf 缓冲区         

len 缓冲区的大小

flags 给0时,为阻塞方式读取


除了读取发来的数据,也需要知道是谁发来的数据

src_addr  addrlen 为输出型参数 方便后续给发来数据的程序回消息

src_addr 结构体的地址

addrlen 结构体的长度        

1.4 发送消息

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

dest_addr 你要把数据发给谁

addrlen 这个谁的缓冲区有多长

 1.5 IP补充及查看网络连接指令

 ip地址

127.0.0.1 

称为本地环回,client和server发送数据只在本地协议栈中进行数据流动,不会把我们的数据发送到网络中。

注意:一个具体的公网ip在云服务器上无法绑定。


查看网络连接

netstat -anup 

1.6 网络字节序

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

h代表host,主机之意。n表示network,网络。l表示32位长整数,s表示16位短整数。

例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送

in_addr_t inet_addr(const char *cp);

将string类型转换成整数类型 然后再修改成网络字节序

char *inet_ntoa(struct in_addr in);

将以网络字节序给出的网络主机地址,转换成以点分十进制表示的字符串(如127.0.0.1)。结果作为函数返回结果返回。 

2.Upd单线程demo

创建一个服务器,首先需要设置自己的端口号和ip,以便其他人发消息给自己。

2.1 基础框架

2.1.1 服务端的框架

这里私有成员变量还有一个 int _sock; 方便我们后续使用套接字

2.1.2 服务端的运行

按照常理来讲,如果想要运行一个服务器,那就需要确定他的ip地址和端口号,但是由于云服务器的ip地址并不真实的,所以也不建议绑定一个具体的公网ip。至于不传ip如何建立相关性,我们在UpdServer() (服务端的具体构造函数中)再谈

#include <iostream>
#include "udp_server.hpp"
#include <memory>
#include <cstdlib>
static void usage(std::string proc)
{
   
    std::cout<<"\nUsage:"<<proc<<"ip port\n"<<std::endl;
}
//要让服务器运行起来,需要知道 ip地址 和端口号
int main(int argc, char*argv[] )
{
    
    if(argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    // std::string ip = argv[1];
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->initServer();
    svr->Start();


    return 0;
}

2.1.3 客户端的运行

运行时需要带上 ip 和端口号 表示该客户端要像谁发送信息
问题: 那我自己的ip如何绑定呢?我给服务端发消息了 客户端怎么给我发回来呢?

之前提到过 一个进程可以对应多个端口。
client是一个客户端进程,如果显示的绑定了一个确定的端口,会导致固定使用这个端口。万一,其他的客户端提前占用了这个端口呢?
所以,客户端client一般不需要显示的bind指定port,而是让OS自动随机选择。
什么时候做的呢?当客户端首次发送信息给服务器时,OS会自动给客户端bind他的id和port

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//单线程版本的
static void usage(std::string proc)
{
    std::cout<<"\nUsage: "<<proc<<"serverIp serverPort\n"<<std::endl;
}

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    //创建 套接字
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
 
    std::string message;
    struct sockaddr_in server;//网络通信 要申请 sockaddr_in结构体
    memset(&server,0,sizeof(server));//初始化这个结构体为0
    server.sin_family=AF_INET;

    //在结构体里面 给port赋值 首先需要将输入参数的string类型 转换为整形类型
    //然后因为数据的传输是双方的 所以需要传入网络 所以需要 htons函数
    server.sin_port=htons(atoi(argv[2]));

    //接下来需要修改server中的ip 但由于嵌套的结构体封装内 接收ip地址的是一
    //个unint32_t类型.所以我们需要先将string类型转换成整数类型 然后再修改
    //这个过程有一个方便的接口
    server.sin_addr.s_addr=inet_addr(argv[1]);

    char buffer[1024];//保存读到的数据
    while(true)
    {
        std::cout<<"请输入你的信息# ";
        std::getline(std::cin,message);
        if(message=="quit") break;
        sendto(sock,message.c_str(),message.size(),0,
                (structsockaddr*)&server,sizeof(server));

        //发送消息完毕 接下来是接收消息 客户端给我返回来的消息
        struct sockaddr_in temp;//拿一个结构体来接收数据

        socklen_t len=sizeof(temp);
        ssize_t s=recvfrom(sock,buffer,sizeof buffer,0,
                    (struct sockaddr*)&temp,&len);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    close(sock);
    return 0;
}

 2.2 服务端的实现

2.2.1 构造和析构

上述说了对于服务端而言,不建议绑定确定的公网ip,我们这里直接缺省值给成空串


    UdpServer(uint16_t port,std::string ip="")
        :_port(port)
        ,_ip(ip)
    {}
    ~UdpServer()
    {
        if (_sock >= 0)
        close(_sock);
    }

 2.2.2 服务器的初始化

    bool initServer()
    {
        //初始化我的套接字 
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if (_sock < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        
        //网络通信 需要创建一个 sockaddr_in 结构体
        struct sockaddr_in local;
        //将该结构体初始化全0 也可以使用meset

        //memset(&local,0,sizeof(local));
        bzero(&local, sizeof(local));

        local.sin_family = AF_INET;//保持和domain即创建套接字的第一个参数 一致即可

        /*
        将本地端口字节序改成网络字节序
        由于构造的时候 传入的port本就是整形类型 所以直接调用 htons函数即可
        */
        local.sin_port=htons(_port);

        /*
            修改本地ip字节序为网络字节序
            1.首先 字符串类型转成 整形类型
            2. 转成网络字节序
            综上 我们使用 inet_addr()接口
            记得我们构造时,将ip给的是空串吗?不给一个ip地址是不行的,别人发消息
            就收不到了。我们这里使用了INADDR_ANY 这是一个宏 本质是一串0 他的作用
            我们后续单独来谈
        */
        local.sin_addr.s_addr=_ip.empty()?INADDR_ANY:inet_addr(_ip.c_str());

        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        return true;
    }

2.2.4 Start()接口

    void Start()
    {
        char buffer[SIZE];
        for (;;)
        {
            //  peer,纯输出型参数
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);

            //读取数据
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 
                                    0, (struct sockaddr *)&peer, &len);      
            if(s>0)
            {
                buffer[s]=0;//数据当成字符串
                uint16_t cli_port=ntohs(peer.sin_port);//从网络中来的
                /*
                   将ip由网络字节序转成主机字节序
                   记得ip地址在sockaddr_in中被封装成了一个结构体
                    使用 inet_ntoa() 接口
                */
                std::string cli_ip=inet_ntoa(peer.sin_addr);
                printf("[%s:%d]# %s\n", cli_ip.c_str(),cli_port,buffer);
            }
            
            //读取完毕 开始发送
            sendto(_sock,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);
        }

    }

2.3 测试结果


2.3.1 客户端自动绑定端口

 发现 客户端自动bind了一个端口


2.3.2 INADDR_ANY

INADDR_ANY的意义

让服务器在工作过程中 可以从任意位置获取数据 只要端口号符合就可以。

3. udp编程--多线程改进版本

只有服务端可以收到别人发来的全部消息,而客户端彼此看不到客户端的消息。我们针对于这

一点进行改进。

  • 首先,在服务端中 我们需要一个数据结构,里面保存了所有客户端发来的消息和他的ip port等信息,可以采用哈希表的方式。

unordered_map<std::string, struct sockaddr_in>

  • 其次,按照单线程逻辑,当一方接收到消息时,一定有这样几个过程。1.接收消息 2.处理信息 3.发回消息。由于发送消息需要使用getline()接口,不输入消息就会阻塞在这个地方。后面的接收消息等代码不会被执行。要想收到别人的消息,需要自己先发一条消息,这不合理。
  • 如果需要让服务端看到别人的消息,就需要服务端中的读写消息的功能同时进行,即使用多线程帮助。

3.1 多线程版本的服务端的实现

在Udp_Server类中新增一个unorder_map<string,struct sockaddr_in>类型

key作为关键字,提示消息是哪个用户发送的,我们采用的是ip+port构成key

哈希表仅用于客户端向用户端发送消息,没有涉及其他模块,我们只需要修改

Udp_Server::Start()

 void Start()
    {
        char buffer[SIZE];
        for (;;)
        {
            struct sockaddr_in peer;
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);

            // 读取数据
            ssize_t s = recvfrom(_sock, buffer, sizeof(buffer) - 1, 
                            0, (struct sockaddr *)&peer, &len);
            //给一个关键字 用于放入哈希表
            char key[64];
            if(s>0)
            {
                buffer[s]=0;//数据当成字符串

                uint16_t cli_port=ntohs(peer.sin_port);
                std::string cli_ip=inet_ntoa(peer.sin_addr);
                    
                //利用ip 和 port构建一个名字 127.0.0.1-8080
                snprintf(key, sizeof(key), "%s-%u", cli_ip.c_str(), cli_port);

                logMessage(NORMAL, "key: %s", key);

                auto it = _users.find(key);
                if (it == _users.end())
                {
                    //将他纳入哈希表
                    logMessage(NORMAL, "add new user : %s", key);
                    _users.insert({key, peer});
                }
            }
            for (auto &iter : _users)
            {
                std::string sendMessage = key;
                sendMessage += "# ";
                sendMessage += buffer; // 127.0.0.1-1234# 你好
                logMessage(NORMAL, "push message to %s", iter.first.c_str());
                sendto(_sock, sendMessage.c_str(), sendMessage.size(), 0,
                   (struct sockaddr *)&(iter.second), sizeof(iter.second));
            }
        }
    }

3.2 多线程版本的客户端

引入线程的封装文件,#include "Thread.hpp"

3.2.1 主函数

/*
下面这两个全部变量 可以像ThreadData一样进行进一步封装
这里没有实现
*/
uint16_t serverport = 0;
std::string serverip;

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    //创建 套接字
    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
    serverport = atoi(argv[2]);
    serverip = argv[1];
    //创建两个线程
    std::unique_ptr<Thread> sender(new Thread(1,udpSend,(void*)&sock));
    std::unique_ptr<Thread> recver(new Thread(2,udpRecv,(void*)&sock));
    sender->start();
    recver->start();

    sender->join();
    recver->join();

    close(sock);
    return 0;
}

3.2.2 回调函数

static void *udpSend(void *args)
{
    int sock = *(int *)((ThreadData *)args)->args_;
    std::string name = ((ThreadData *)args)->name_;

    std::string message;
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    while (true)
    {
        std::cerr << "请输入你的信息# "; //标准错误 2打印
        std::getline(std::cin, message);
        if (message == "quit")
            break;
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof server);
    }

    return nullptr;
}
static void* udpRecv(void* args)
{
    int sock=*(int*)((ThreadData*)args)->args_;
    std::string name=((ThreadData*)args)->name_;
    char buffer[1024];
    while(true)
    {
        memset(buffer,0,sizeof(buffer));
        struct sockaddr_in temp;
        socklen_t len=sizeof(temp);
        ssize_t s=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
        if(s>0)
        {
            buffer[s];
            std::cout  << buffer << std::endl;
        }
    }
}

3.3 多线程版本测试结果

如果直接运行程序,客户端会显示自己发送的消息,客户端发回的消息,以及其他人的消息杂成一团,所以我们利用管道文件,将收到的消息重定向到该管道文件

mkfifo clientA 

 创建管道文件 clientA

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

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

相关文章

磁盘调度算法

磁盘调度算法 为了减少对文件的访问时间&#xff0c;应采用一种最佳的磁盘调度算法&#xff0c;以使各进程对磁盘的平均访问时间最少。由于在访问磁盘时主要是寻道时间。因此&#xff0c;磁盘调度的目标是使磁盘的平均寻道时间最少。 1.先来先服务&#xff08;FCFS&#xff09…

Java Character 类,超详细整理,适合新手入门

目录 一、什么是Java Character 类&#xff1f; 二、Character类有哪些常用的静态方法&#xff1f; 1、将一个字符分别转换为大写字母和小写字母 2、如何判断一个字符是否是数字&#xff1f; 3、如何将一个字符转换为数字&#xff1f; 4、如何将一个字符串转换为字符数组…

【c++学习】入门c++(中)

目录一. 前言二. 函数重载1. 概念2.函数名修饰规则三 .引用&#xff08;&&#xff09;1. 概念2. 引用特性3.应用1.做参数2. 做返回值3. 传值、传引用效率比较4.引用和指针的区别四 . 结语一. 前言 小伙伴们大家好&#xff0c;今天我们继续学习c入门知识&#xff0c;今天的…

SQL性能优化的47个小技巧,你了解多少?

收录于热门专栏Java基础教程系列&#xff08;进阶篇&#xff09; 1、先了解MySQL的执行过程 了解了MySQL的执行过程&#xff0c;我们才知道如何进行sql优化。 客户端发送一条查询语句到服务器&#xff1b;服务器先查询缓存&#xff0c;如果命中缓存&#xff0c;则立即返回存…

platform 总线

驱动的分离与分层思想 分离&#xff1a;硬件信息分离&#xff1b; 在编写硬件驱动的时候&#xff0c;需要操作许多硬件寄存器。比如gpio 驱动&#xff0c;你需要知道gpio控制器 寄存器的地址&#xff0c;你想要哪个gpio输出&#xff1f;或是输入? 这些操作最终都是靠设置寄存…

19 pandas 分层索引与计算

文章目录分层设置与查询数据index 为有序index 为无序(中文&#xff09;查看数据示例多层索引的创建方式&#xff08;行&#xff09;1、from_arrays 方法2、from_tuples 方法3、from_product 方法多层索引的创建方式&#xff08;列&#xff09;分层索引计算MultiIndex 参数表分…

Mybatis笔记整理

1. 相关文档地址 中文文档 https://mybatis.org/mybatis-3/zh/index.htmlMybatis可以配置成适应多种环境&#xff0c;不过每个SqlSessionFactory实例只能选择一种环境。Mybatis默认事务管理器是JDBC&#xff0c;连接池&#xff1a;POOLEDMaven仓库:下载地址<dependency>…

KVM-1、Linux 操作系统及虚拟化

1. 前言 一台计算机是由一堆硬件设备组合而成,在硬件之上是操作系统,操作系统与计算机硬件密不可分,操作系统用来管理所有的硬件资源提供服务,各个硬件设备是通过 总线 进行连接起来的: 在操作系统之上,需要一个人机交互接口,我们才能使用计算机对其发送指令,这个人机…

C语言【动态内存管理 后篇】

动态内存管理 后篇&#x1fac5;经典例题&#x1f926;‍♂️题目1&#x1f926;‍♂️题目2&#x1f926;‍♂️题目3&#x1f926;‍♂️题目4&#x1fac5;C/C程序的内存开辟前面的一篇文章动态内存管理 前篇&#xff0c;我们已经了解过了动态内存管理的相关信息&#xff0c…

数据库管理-第五十七期 多灾多难(20230218)

数据库管理 2023-02-18第五十七期 多灾多难1 网络震荡2 挂一大片3 恢复虚拟机总结第五十七期 多灾多难 2月第三周&#xff0c;怎么说呢&#xff0c;多灾多难的一周&#xff0c;一周两次严重故障&#xff0c;而且事情还都发生在24小时之内&#xff0c; 1 网络震荡 本周四一大…

不要让GPT成为你通向“学业作弊”的捷径——使用GPT检测工具来帮助你保持正确的方向

不要让GPT成为你通向“学业作弊”的捷径——使用GPT检测工具来帮助你保持正确的方向 最近&#xff0c;多所美国高校以及香港大学等都明确禁止在校使用ChatGPT等智能文本生成工具。GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种自然语言处理技术&#x…

04 C++提高编程

文件基本上是黑马程序员的文档&#xff0c;部分添加自己需要的内容&#xff0c;仅用于自己学习&#xff01;链接&#xff1a;黑马程序视频课程GitHub:源代码 C提高编程 本阶段主要针对C泛型编程和STL技术做详细讲解&#xff0c;探讨C更深层的使用 1 模板 1.1 模板的概念 模…

spring的注解

Spring的常用注解常用注解EnableWebMvcConfigurationBeanSpringBootApplication && MapperScanControllerResponseBodyRestControllerRequestMapping("robot")ResourceRequestMappingService常用注解 EnableWebMvc 在配置类中开启Web MVC的配置支持。 Con…

力扣62.不同路径

文章目录力扣62.不同路径题目描述方法1&#xff1a;暴力深搜(超时未通过)方法2&#xff1a;动态规划力扣62.不同路径 题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器…

[安装] Dell电脑安装系统时看不到固态硬盘的解决方案

前言如图&#xff0c;配备NVME固态硬盘的机器在重新安装时候没有看到固态硬盘。这其实是由于安装镜像缺少IRST驱动导致的。1.硬盘模式设置为AHCI大多数戴尔机器出厂BIOS默认硬盘模式为Raid On而非AHCI&#xff0c;WIN10纯净版镜像中自带NVME驱动&#xff0c;可以识别AHCI模式下…

台积电后悔莫及,美国没有将它当成自己人,大陆市场重要性凸显

台积电对于赴美设厂可谓变了再变&#xff0c;日前台积电创始人张忠谋就发声指美国“美国认为他可以通钱来快速进入芯片市场&#xff0c;这太天真了”&#xff0c;这是在台积电获得美国的补贴少得可怜之后的表态&#xff0c;凸显出对美国的不满。台积电对于赴美设厂一开始出现截…

postman教程

一、前言 1、postman 是什么 postman 是一款 HTTP 客户端工具&#xff0c;它可以用来调试和测试接口。通过 HTTP 协议&#xff0c;将请求数据发送到服务端&#xff0c;并从服务端获取响应数据。 2、为什么要使用 postman 后端开发者写的代码在大多数情况下是要给到前端开发…

day46【代码随想录】动态规划之打家劫舍 III、买卖股票的最佳时机、买卖股票的最佳时机II

文章目录前言一、打家劫舍 III&#xff08;力扣337&#xff09;【较难】二、买卖股票的最佳时机&#xff08;力扣121&#xff09;三、买卖股票的最佳时机II&#xff08;力扣122&#xff09;前言 1、打家劫舍 III 2、买卖股票的最佳时机 3、买卖股票的最佳时机II 一、打家劫舍 …

C++——二叉树进阶oj题

目录二叉树创建字符串二叉树的分层遍历1二叉树的分层遍历2给定一个二叉树, 找到该树中两个指定节点的最近公共祖先二叉树搜索树转换成排序双向链表。根据一棵树的中序遍历与后序遍历构造二叉树根据一棵树的前序遍历与中序遍历构造二叉树二叉树创建字符串 题目链接 思路&#x…

Python 之 Matplotlib 散点图、箱线图和词云图

文章目录一、散点图1. scatter() 函数2. 设置图标大小3. 自定义点的颜色和透明度4. 可以选择不同的颜色条&#xff0c;配合 cmap 参数5. cmap 的分类5.1 Sequential colormaps&#xff1a;连续化色图5.2 Diverging colormaps&#xff1a;两端发散的色图 .5.3 Qualitative color…