【Socket】Unix环境下搭建简易本地时间获取服务

这篇具有很好参考价值的文章主要介绍了【Socket】Unix环境下搭建简易本地时间获取服务。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文搭建一个Unix环境下的、局域网内的、简易的本地时间获取服务。

主要用于验证:

  1. 当TCP连接成功后,可以在两个线程中分别进行读操作、写操作动作
  2. 当客户端自行终止连接后,服务端会在写操作时收到 SIGPIPE 信号
  3. 当客户端执行shutdown写操作后,客户端会在写操作时收到 SIGPIPE 信号
  4. 当客户端执行shutdown写操作后,服务端会在读操作时得到返回值 0

服务端功能:

  1. 轮询监听Client的连接(阻塞式)
  2. 创建并缓存会话对象
  3. 开启会话对象的读操作线(阻塞式IO)、写操作线程(阻塞式IO)
  4. 当读写操作线程退出时通过回调来执行资源释放(fd,会话对象)

客户端功能:

  1. 连接成功后直接开启读操作线程(阻塞式IO)、写操作线程(阻塞式IO)
  2. 在2秒后shutdown写端
  3. 在3秒后退出工作线程

(本文对打印进行了加锁,确保输出信息看起来更清晰,否则信息会混乱交错) 

服务端源码(局域网ip、端口port 按需自行修改噢😊):

// TimeServer.cpp

#include <iostream>
#include <thread>
#include <vector>
#include <map>
#include <atomic>
#include <exception>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "TimeConn.hpp"

std::map<int, TimeConn> conn_map;

int initServer(const std::string& ip, uint16_t port) {
    int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (server == -1) {
        std::cout << "socket failed, errno: " << strerror(errno) << std::endl;
        _exit(0);
    }

    sockaddr_in addr{
        .sin_family = AF_INET,
        .sin_port = htons(port)
    };

    int success = inet_pton(AF_INET, ip.c_str(), &addr.sin_addr);
    if (success == 0) {
        std::cout << "invalid ip address, errno: " << strerror(errno) << std::endl;
        _exit(0);
    } else if (success == -1) {
        std::cout << "inet_pton error, errno: " << strerror(errno) << std::endl;
        _exit(0);
    }

    success = bind(server, reinterpret_cast<const sockaddr*>(&addr), sizeof(sockaddr_in));
    if (success == -1) {
        std::cout << "bind failed, errno: " << strerror(errno) << std::endl;
        _exit(0);
    }

    success = listen(server, 50);
    if (success == -1) {
        std::cout << "listen failed, errno: " << strerror(errno) << std::endl;
        _exit(0);
    }

    return server;
}

void handleConn(int conn) noexcept(false) {
    sockaddr client_addr;
    socklen_t len = sizeof(decltype(client_addr));

    // 读取连接建立时client的信息
    auto success = getpeername(conn, &client_addr, &len);
    if (success == 0) {
        if (client_addr.sa_family == AF_INET) {
            sockaddr_in* ipv4 = reinterpret_cast<sockaddr_in*>(&client_addr);
            std::cout << "client ip: " << std::hex << ipv4->sin_addr.s_addr << std::dec << " port: " << ipv4->sin_port << std::endl;
        }
    } else if (success == -1) {
        std::cout << "getpeername failed, errno: " << strerror(errno) << std::endl;
        close(conn);
        return;
    }

    TimeConn& timeConn = conn_map[conn];
    timeConn.initConnFd(conn);
    timeConn.startRead([&timeConn](int conn_fd){
        if (timeConn.canClose()) {
            close(conn_fd);
            conn_map.erase(conn_fd);
        }
    });
    timeConn.startWrite([&timeConn](int conn_fd){
        if (timeConn.canClose()) {
            close(conn_fd);
            conn_map.erase(conn_fd);
        }
    });
}

int main(int argc, char* argv[]) {
    std::cout << "Hello, I am server" << std::endl;
    
    std::string ip{"192.168.0.110"};

    auto server = initServer(ip, 10080);

    while (true) {
        std::cout << "Server accepting..." << std::endl;
        int conn = accept(server, nullptr, nullptr);
        if (conn == -1) {
            if (errno == EAGAIN) {
                continue;
            } else {
                std::cout << "accept failed, errno: " << strerror(errno) << std::endl;
                _exit(0);
            }
        }

        std::cout << "new connect! conn fd: " << conn << std::endl;
        
        try {
            handleConn(conn);
        } catch (std::exception& e) {
            // 
            std::cout << "handleConn exception: " << e.what() << std::endl;
        }
    }

    close(server);
    return 0;
}

服务端会话源码:

// TimeConn.hpp

#ifndef __TIMECONN_HPP__
#define __TIMECONN_HPP__

#include <unistd.h>
#include <atomic>
#include <thread>

class TimeConn
{
public:
    TimeConn(int conn = -1) : mConnFd{conn}, isReading{false}, isWriting{false}
    {
        // ...
    };

    virtual ~TimeConn()
    {
        close(mConnFd);
    };

    // constexpr TimeConn &operator=(const TimeConn &);

public:
    void initConnFd(int conn);

    void startRead(std::function<void(int)> callback);

    void startWrite(std::function<void(int)> callback);

    void stopRead();

    void stopWrite();

    bool canClose();

private:
    int mConnFd;

    std::atomic_bool isReading;

    std::atomic_bool isWriting;
};

#endif
// TimeConn.cpp

#include <chrono>
#include <iostream>
#include <mutex>
#include <sstream>
#include <sys/socket.h>
#include <signal.h>

#include "TimeConn.hpp"

// constexpr TimeConn &TimeConn::operator=(const TimeConn & other) {
//     this->mConnFd = other.mConnFd;
//     return *this;
// }

static std::mutex m;

static void print_log(const std::stringstream& ss) {
    std::lock_guard<std::mutex> lock(m);

    std::cout << ss.str() << std::endl;
}

void TimeConn::initConnFd(int conn) {
    this->mConnFd = conn;
}

void TimeConn::startRead(std::function<void(int)> callback) {
    using namespace std::literals;

    isReading = true;
    std::thread([this, callback]{
        std::stringstream ss;
        ss << "conn fd: " << this->mConnFd << " start read";
        print_log(ss);

        while (this->isReading) {
            char buffer[512];
            ssize_t res = recv(this->mConnFd, &buffer, sizeof(buffer), 0);
            if (res == 0) {
                std::stringstream ss1;
                ss1 << "no data or remote end";
                print_log(ss1);
                break;
            } else if (res == -1) {
                // error
                std::stringstream ss2;
                ss2 << "conn fd:" << this->mConnFd <<  " recv failed: " << strerror(errno);
                print_log(ss2);
                break;
            } else {
                std::stringstream ss3;
                ss3 << "recv success, count: " << res << " data: " << buffer;
                print_log(ss3);
            }
        }

        this->isReading = false;
        callback(this->mConnFd);

        // 注意!
        // 在经过callback后,若map进行了erase操作,则该TimeConn obj内存被清除,this->mConnFd值是不确定的,大概率是0,但也可能已被其它值占用
        std::stringstream ss4;
        ss4 << "conn fd " << this->mConnFd << " Reading finish";
        print_log(ss4);
    }).detach();
}

void TimeConn::startWrite(std::function<void(int)> callback) {
    using namespace std::literals;

    isWriting = true;
    std::thread([this, callback]{
        std::stringstream ss;
        ss << "conn fd: " << this->mConnFd << " start write";
        print_log(ss);

        // send 时若该连接已关闭,则会产生SIGPIPE信号,程序默认执行动作是“退出进程”
        // 解决方案一 使用signal忽略SIGPIPE
        // signal(SIGPIPE, SIG_IGN);

        while (this->isWriting) {
            const auto now = std::chrono::system_clock::now();
            const std::time_t t_c = std::chrono::system_clock::to_time_t(now);
            const auto* t = std::ctime(&t_c);
            ssize_t res = -1;

            // 发送数据
            // send 时若该连接已关闭,则会产生SIGPIPE信号,程序默认执行动作是“退出进程”
            // 解决方案二(操作系统受限) 若操作系统支持,可以加上flag MSG_NOSIGNAL
            res = send(this->mConnFd, t, strlen(t) + sizeof('\0'), MSG_DONTROUTE | MSG_NOSIGNAL);
            if (res == -1) {
                // error
                std::stringstream ss1;
                ss1 << "conn fd:" << this->mConnFd <<  " send failed: " << strerror(errno);
                print_log(ss1);
                break;
            } else {
                std::stringstream ss2;
                ss2 << "send success, count: " << res << " data: " << t;
                print_log(ss2);
            }

            // std::this_thread::sleep_for(1s);
        }

        this->isWriting = false;
        callback(this->mConnFd);

        // 注意!
        // 在经过callback后,若map进行了erase操作,则该TimeConn obj内存被清除,this->mConnFd值是不确定的,大概率是0,但也可能已被其它值占用
        std::stringstream ss3;
        ss3 << "conn fd " << this->mConnFd << " Writing finish";
        print_log(ss3);
    }).detach();
}

void TimeConn::stopRead() {
    isReading = false;
}

void TimeConn::stopWrite() {
    isWriting = false;
}

bool TimeConn::canClose() {
    // 鉴于该示例启动线程的时机与退出线程的时机比较简单,所以无需加锁
    return !isReading && !isWriting;
}

本文中使用的recv、send函数都是用阻塞式IO,所以相应的返回值处理都是按照阻塞式时的错误来进行处理的。若采用非阻塞式IO,则处理方式并不是如此的。文章来源地址https://www.toymoban.com/news/detail-758968.html

到了这里,关于【Socket】Unix环境下搭建简易本地时间获取服务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • unix网络编程-简易服务器与客户端程序解析

    a -- address f -- file        eg: fputs() -- file put stream fd -- file descriptor h - host(主机) in/inet -- internet        eg: sockaddr_in; inet_aton n -- network(网络字节序)/numeric(数值) p -- protocol(协议)/presentation(表达/呈现形式) s -- socket        eg: sin -- socket internet t -- type,用于指定某种

    2024年01月16日
    浏览(69)
  • 内网环境的NTP服务搭建和应用(实现各服务器时间同步)

    NTP,“网络时间协议”(Network Time Protocol),它是一种用于在网络中同步各个设备时钟的协议。NTP通过在网络中的一组时间服务器之间传递时间信息来实现时间同步,从而确保网络中的各个设备具有相似的时间。 在内网环境中想要保持各个服务器时间一致,就需要搭建NTP服务

    2024年02月03日
    浏览(55)
  • windows环境(本地端以及华为云服务器)搭建HTTP服务器

    最近在调试一款中移物联网推出的NB-IOT物联网模组,模组有个功能是需要实现固件在线下载,那么模组更新固件的时候可以通过服务器端通过HTTP协议进行下载,因此首先需要搭建一个HTTP服务器。 本篇文章从本地电脑端以及华为云服务器端分别进行了HTTP服务器的搭建,并实现

    2024年02月15日
    浏览(46)
  • 二:原神本地服务器(sifu)搭建环境配置教程第二篇

    安装jdk 双击msi安装文件,一路到底就欧克,傻瓜式安装。  安装数据库 也是一路yes,后面在安装界面会有一个询问你是否安装最新版,可选可不选,不选安装会快些 中间提示服务启动不成功也无所谓,可以忽略。后面会教你怎么开  安装代理器  双击开始安装一路yes就行

    2024年02月13日
    浏览(54)
  • 在vscode中安装使用live Server(前端搭建自己的本地服务器环境)

    一、在扩展中搜索live Server,找到并下载,下载完后点击设置图标,点击扩展设置  二、点击在settings.json中编辑,在JSON中修改添加下列代码 端口号可配,注意不要与其他服务冲突; AdvanceCustomBrowserCmdLine填写自己实际chrome的路径;  三、保存重启vscode,点击右下角的go live 就可

    2024年02月16日
    浏览(62)
  • 搭建一个简易的 PMML 模型测试环境

    PMML ,全称为 Predictive Model Markup Language ,是一种标准化的模型描述和交换格式。它允许从不同的数据挖掘和机器学习软件中导出模型,并在其他系统中进行部署,无需重新编写代码。PMML 通过定义一套统一的规则来描述模型,包括数据预处理、模型参数以及输入输出格式等。

    2024年04月22日
    浏览(30)
  • Unix 网络编程:Socket 状态图&编程参数

        Flags (9 bits) (aka Control bits) . Contains 9 1-bit flags NS (1 bit): ECN-nonce - concealment protection (experimental: see RFC 3540). CWR (1 bit): Congestion Window Reduced (CWR) flag is set by the sending host to indicate that it received a TCP segment with the ECE flag set and had responded in congestion control mechanism (added to header by RFC 31

    2024年02月02日
    浏览(44)
  • macOS跨进程通信: Unix Domain Socket 创建实例

    macOS跨进程通信: Unix Domain Socket 创建实例 Socket 是 网络传输的抽象概念。 一般我们常用的有 Tcp Socket 和 UDP Scoket , 和类Unix 系统(包括Mac)独有的 Unix Domain Socket (UDX)。 Tcp Socket 能够跨电脑进行通信,即使是在同一个电脑下的多进程间通信,也会通过网卡进行数据传输,如

    2024年01月24日
    浏览(43)
  • UNIX网络编程:socket实现client/server通信

    阅读 UNIX网络编程 卷1:套接字联网API 第3版 的前4个章节,觉得有必要对书籍上的源码案例进行复现,并推敲TCP的C/S通信过程。 📌 测试环境:CentOS7.6 x64 编译server.c 和 client.c gcc server.c -g -std=gnu99 -o server 和 gcc client.c -g -std=gnu99 -o client 运行测试: 📌 server.c仅仅实现对单个客户

    2024年02月06日
    浏览(49)
  • nodejs使用 Unix domain socket进行IPC通讯

    Unix domain socket 又叫 IPC(inter-process communication 进程间通信) socket,用于实现同一主机上的进程间通信。 socket 原本是为网络通讯设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.

    2024年01月21日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包