当前位置: 首页 > article >正文

Linux —— Socket编程(二)

一、本篇重点

对于上一篇实现的简单udp客户端/服务器进一步的补充改造,继续了解与Socket api的相关接口

二、upd服务器(第一版)

上一篇我们通过一边实现一个简单udp,一边学习Socket编程的相关接口,本篇打算进一步对上一篇的代码进行补充和添加功能,上一篇为了了解接口,只是简单的让client端能够发送消息到server端,然后server端获取消息并能够发送回来即可,这里先提供完整的代码。

1. udp_server.hpp

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include "errno.hpp"
#include <cstring>

namespace chk
{
    const static uint16_t default_port = 8080;
    class UdpServer
    {
    public:
        // 对成员变量完成初始化
        UdpServer(uint16_t port = default_port) : _port(port)
        {
            std::cout << "server port: " << _port << std::endl;
        }

        void Init() // 创建出套接字,并绑定端口号和ip
        {
            // 1. 创建套接字
            _sock = socket(AF_INET, SOCK_DGRAM, 0);
            if (_sock < 0)
            {
                std::cerr << "create socket error: " << strerror(errno) << std::endl;
                exit(SOCKET_ERR);
            }
            std::cout << "bind socket success: " << _sock << std::endl; // 3

            // 2. 构建struct sockaddr_in结构体
            struct sockaddr_in local;
            bzero(&local, sizeof(local));       // 初始化
            local.sin_family = AF_INET;         // IPv4
            local.sin_port = htons(_port);      // 端口号
            local.sin_addr.s_addr = INADDR_ANY; // 服务器下的ip

            // 3. 绑定套接字和sockaddr_in
            int n = bind(_sock, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                std::cerr << "bind error: " << strerror(errno) << std::endl;
                exit(BIND_ERR);
            }
            std::cout << "bind socket success: " << _sock << std::endl;
        }
        void Start() // 时刻读取套接字中的信息数据,并将其返回
        {
            char buffer[1024];
            while(true)
            {
                // 收数据
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int n = recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
                if(n>0) buffer[n] = '\0';
                else continue;

                //收到信息后打印出来:对方ip+端口号+内容
                std::cout << inet_ntoa(peer.sin_addr) << " - " << ntohs(peer.sin_port) << " : " << buffer << std::endl;

                // 发回去
                sendto(_sock,buffer,strlen(buffer),0,(struct sockaddr*)&peer,sizeof(peer));
            }
        }
        ~UdpServer() // 析构
        {
        }

    private:
        int _sock;
        uint16_t _port;
    };
}

2. udp_server.cc

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


using namespace std;
using namespace chk;

// 我们最终希望以 ./udp_server port 的形式去启动
static void usage(string proc)//使用手册
{
    std::cout << "Usage:\n\t" << proc << " port\n" << std::endl;
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<UdpServer> usvr(new UdpServer(port));
    usvr->Init();
    usvr->Start();

    return 0;
}

3. udp_client.hpp

#pragma once

#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<cstring>
#include<string>
#include"errno.hpp"

4. udp_client.cc

#include"udp_client.hpp"//基本要用到的头文件
using namespace std;


static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n" << std::endl; 
}

// ./udp_client server_ip server_port
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        exit(USAGE_ERR);
    }
    string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    //1. 创建套接字
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    if(sock < 0)
    {
        cerr << "client : create socket error" << endl;
        exit(SOCKET_ERR);
    }

    //2. 创建server端的struct sockaddr
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));//初始化方案2
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    server.sin_port = htons(server_port);

    //3. 客户端测试
    while(true)
    {
        // 用户发送消息
        string messages;
        cout << "client : " ;
        cin >> messages;
        // 发送到sock
        sendto(sock,messages.c_str(),messages.size(),0,(struct sockaddr*)&server,sizeof(server));

        // 接受返回的信息
        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int n = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer << endl;
        }
        
    }
    return 0;
}

5. errno.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};

6. makefile

.PHONY:all
all: udp_client udp_server

udp_client:udp_client.cc
	g++ -o $@ $^ -std=c++11
udp_server:udp_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udp_client udp_server

三、udp服务器(第二版)

1. 添加回调函数

第一版的udp服务器,我们只要求能够收发信息,但我们实际应用中,服务端接受到信息以后,还需要对信息进行后续的处理,而具体的处理方法,我们这里简单设计几个例子。

所以我们应该对udp_server.hpp的设计进行补充和改进,我们对类的设计可以多添加一个表示具体执行方法的成员,由外部传入具体的方法,类内只需要拿到方法后,以回调的方式去处理接受到的信息,并且将处理好的信息发送回给客户端。

———————————————————————————————————————————

知识点一

在c语言中,通常是用typedef的方式去定义一个函数指针类型,而在C++中,例如我想定义一个类型string fun_t(string msg) 这样一个类型的函数指针,我们通常使用以下方式:

#include <functional>

using func_t = std::function<std::string(std::string)>;

ps:这里用到的方法细究的话,知识点有点多,可以先简单的认为,这就是定义一个函数指针的方法,实际用处更加广泛,这个本质也不是函数指针。

———————————————————————————————————————————

基于上述在知识点,我们定义函数指针,添加函数指针变量的类成员对象,然后调整下构造,选择让外部传入具体方法,这样就实现了方法处理和网络传输的解耦,然后再对于之前的逻辑进行调整,我们拿到数据后,先将数据交给回调函数,然后将处理好后的信息再发生给客户端

2. 信息处理函数

这里我们简单的写几个功能进行测试即可

2.1 大小写转换

先简单实现一个,将对方发送过来的字符串信息中,关于小写的字母转换成大写返回

———————————————————————————————————————————

知识点一

这里是整理了一下要实现这个函数的一些接口,不算新的知识点

字符检查相关接口

#include<ctype.h>
   
int isalpha(int c); // 检查是否为字母
int islower(int c); // 检查是否为小写字母
int isupper(int c); // 检查是否为大写字母

英文字符大小写转化的相关接口

#include <ctype.h>

int toupper(int c); //小写转化成大写
int tolower(int c); //大写转化成小写

———————————————————————————————————————————

代码参考

string transacationString(string msg)
{
    string res;
    char tmp;
    for(auto& c:msg)
    {
        if(islower(c))
        {
            tmp = toupper(c);
            res.push_back(tmp);
        }
        else
        {
            res.push_back(c);
        }
    }
    return res;
}

2.2 远程指令控制

这里要做一个让远程的客户端传指令给服务器,服务器处理并将结果返回,我们可以像之前一样,去封装一个命令行解释器,调用创建线程去处理并将结果返回,这里不选择自己造轮子,而是通过已有的接口去直接实现这个命令的处理,并返回相对应的处理结果。

———————————————————————————————————————————

知识点一:命令行执行接口

   FILE *popen(const char *command, const char *type);

作用:创建一个子进程去执行command命令,并且创建管道连接这个子进程,通过type指定去连接到这个指令的标准输出或者标准输入

  • command:要执行的 shell 命令字符串。
  • type:指定管道的方向,可以是 "r"(读)或 "w"(写)。如果是 "r",则创建的管道连接到命令的标准输出,可以从这个管道读取命令的输出。如果是 "w",则连接到命令的标准输入,可以向这个管道写入数据作为命令的输入。
  • 如果成功,返回一个指向FILE类型的指针,可以使用标准 I/O 函数(如freadfwritefgets等)来与管道进行交互。
  • 如果失败,返回NULL,并设置errno来指示错误原因。

   int pclose(FILE *stream);

作用:pclose函数用于关闭由popen函数创建的管道,并等待与该管道关联的进程结束。

  • stream:由popen函数返回的指向管道的FILE指针。

———————————————————————————————————————————

参考代码

// 远程指令
std::string excuteCommand(std::string command)
{
    //1. 安全检查:避免对方输入一些你不愿意提供的指令,例如rm等等
    //这部分我们只是简单做测试,就不进行安全检查了

    //2. 业务处理逻辑
    FILE* fp = popen(command.c_str(),"r");
    if(fp == nullptr) return "Invalid instruction";

    //3. 获取结果
    char line[1024];
    std::string res;
    while(fgets(line,sizeof(line),fp))
    {
        res += line;
    }
    pclose(fp);

    return res;
}

四、udp服务器(第三版)

服务端除了这种对信息的处理然后返回,我们还可以简单的做一个群聊功能玩玩。

群聊的要求就是,我们多个客户端向服务器发送消息,服务器首先需要每个都拿到,并且将受到的消息,广播给每一个在线用户,要实现这些,首先要解决的就是,我需要将消息发送到每一个客户端上,我就要有一个存放每一个在线用户信息的一个容器,这里做的稍微简单一点,我们认为只要给我发送了消息,我就将你的客户端信息记录起来,认为你当前处于“在线状态”,然后将你的信息向每一个用户进行发送,此时如果有其他用户也给服务端发送信息怎么办?这就涉及到多线程并发访问的问题,我们利用前面学习到的生产消费模型去处理,多个线程发送消息到循环队列中,而服务器每次往循环队列拿信息,并且广播。而为了测试时观赏性更强一点,我们还可以利用管道文件,将内容输入重定向到管道文件中,再另外开一个窗口,把管道文件的内容打印出来。

参考代码

Linux —— udp实现群聊代码-CSDN博客

总结

本篇重点是延续上一篇,进一步改造了udp服务端,简单的补充和添加了几个功能,并且提供了代码进行参考。


http://www.kler.cn/a/324304.html

相关文章:

  • Unity 2022 Nav Mesh 自动寻路入门
  • 从零开始学习 sg200x 多核开发之 uboot saveenv 功能使能
  • Cartographer激光雷达slam -20241116
  • onlyoffice Command service(命令服务)使用示例
  • 树状数组+概率论,ABC380G - Another Shuffle Window
  • CentOS7.9 源码编译 FreeSWITCH 1.10.12
  • NetworkPolicy访问控制
  • Windows 开发工具使用技巧
  • PAT甲级1003Emergency
  • 【分布式微服务云原生】10分钟揭秘Dubbo负载均衡:如何让服务调用更智能?
  • 发明专利实用新型专利外观设计专利
  • List几种遍历方法速度
  • 【GUI设计】基于图像分割的GUI系统(3),matlab实现
  • leetcode91. 解码方法,动态规划
  • uniapp设置从右上角到左下角的三种渐变颜色
  • 滚雪球学MySQL[2.1讲]:基础SQL操作
  • 如何使用 Go 获取你的 IP 地址
  • MMD模型及动作一键完美导入UE5-IVP5U插件方案(二)
  • Vue3中的30个高频重点面试题
  • 金镐开源组织成立,增加最新KIT技术,望能为开源添一把火
  • 加法器以及标志位
  • Qt学习笔记
  • HTTP请求过程 part-1
  • 高通Android 12 音量API设置相关代码
  • (undone) MIT6.824 Lecture1 笔记
  • OpenGL ES 绘制一个三角形(2)