【Linux】网络编程套接字

简介: 【Linux】网络编程套接字

网络编程套接字

1. 认识TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

2. 认识UDP协议

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据包

3. 网络字节序

不管这台主机是大端还是小端,就需要先将数据转换为大端字节序

h表示本地,n 表示 network, l表示32为长整数, s表示16位短整数

4. socket编程接口

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,
 socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
 socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);

4.1 sockaddr 结构

5. 简单的UDP网络程序

void InitServer()
{
    _sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (_sock < 0)
    {
        std::cout << "create sock error" << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }
    std::cout << "create sock success" << std::endl;
    struct sockaddr_in local;
    sizeof (local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = inet_addr(_ip.c_str());
    if (bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        std::cout << "bind socket error" << strerror(errno) << std::endl;
        exit(BIND_ERR);
    }
    std::cout << "bind socket success" << std::endl;
}

下面是实现的简单群聊服务器:

  1. 环形队列
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
static const int N = 50;
template <class T>
class RingQueue
{
private:
    void P(sem_t &s)
    {
        sem_wait(&s);
    }
    void V(sem_t &s)
    {
        sem_post(&s);
    }
    void Lock(pthread_mtuex_t &m)
    {
        pthread_mutex_lock(&m);
    }
    void Unlock(pthread_mutex_t &m)
    {
        pthread_mutex_unlock(&m);
    }
public:
    RingQueue(int num = N) : _ring(num), _cap(num)
    {
        sem_init(&_data_sem, 0, 0);
        sem_init(&_space_sem, 0, num);
        _c_step = _p_step = 0;
        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }
    void push(const T &in)
    {
        P(_space_sem);
        Lock(_p_mutex);
        _ring[_p_step++] = in;
        _p_step %= _cap;
        Unlock(_p_mutex);
        V(_data_sem);
    }
    void pop(T *out)
    {
        P(_data_sem);
        Lock(_c_mutex);
        *out = _ring[_c_step++];
        _c_step %= _cap;
        Unlock(_c_mutex);
        V(_space_sem);
    }
    ~RingQueue()
    {
        sem_destroy(&_data_sem);
        sem_destroy(&_space_sem);
        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }
private:
    std::vector<T> _ring;
    int _cap; // 环形队列的大小
    sem_t _data_sem; // 消费者关心
    sem_t _space_sem;  // 生产者关心
    int _c_step; // 消费位置
    int _p_step; // 生产位置
    pthread_mutex_t _c_mutex;
    pthread_mutex_t _p_mutex;
};

下面是实现的简单群聊服务器:

  1. 环形队列
#pragma once
#include <iostream>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
static const int N = 50;
template <class T>
class RingQueue
{
private:
    void P(sem_t &s)
    {
        sem_wait(&s);
    }
    void V(sem_t &s)
    {
        sem_post(&s);
    }
    void Lock(pthread_mtuex_t &m)
    {
        pthread_mutex_lock(&m);
    }
    void Unlock(pthread_mutex_t &m)
    {
        pthread_mutex_unlock(&m);
    }
public:
    RingQueue(int num = N) : _ring(num), _cap(num)
    {
        sem_init(&_data_sem, 0, 0);
        sem_init(&_space_sem, 0, num);
        _c_step = _p_step = 0;
        pthread_mutex_init(&_c_mutex, nullptr);
        pthread_mutex_init(&_p_mutex, nullptr);
    }
    void push(const T &in)
    {
        P(_space_sem);
        Lock(_p_mutex);
        _ring[_p_step++] = in;
        _p_step %= _cap;
        Unlock(_p_mutex);
        V(_data_sem);
    }
    void pop(T *out)
    {
        P(_data_sem);
        Lock(_c_mutex);
        *out = _ring[_c_step++];
        _c_step %= _cap;
        Unlock(_c_mutex);
        V(_space_sem);
    }
    ~RingQueue()
    {
        sem_destroy(&_data_sem);
        sem_destroy(&_space_sem);
        pthread_mutex_destroy(&_p_mutex);
        pthread_mutex_destroy(&_c_mutex);
    }
private:
    std::vector<T> _ring;
    int _cap; // 环形队列的大小
    sem_t _data_sem; // 消费者关心
    sem_t _space_sem;  // 生产者关心
    int _c_step; // 消费位置
    int _p_step; // 生产位置
    pthread_mutex_t _c_mutex;
    pthread_mutex_t _p_mutex;
};

线程包装

#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <pthread.h>
#include <functional>
class Thread
{
public:
    typedef enum
    {
        NEW = 0, 
        RUNNING, 
        EXITED
    } ThreadStatus;
    using func_t = std::function<void ()>;
public:
    Thread(int num, func_t func) : _tid(0), _status(NEW), _func(func)
    {
        char name[128];
        snprintf(name, sizeof(name), "thread-%d", num);
        _name = name;   
    }
    int status() 
    {
        return _status;
    }
    std::string threadname()
    {
        return _name;
    }
    pthread_t pthreadid()
    {
        if (_status == RUNNING)
        {
            return _tid;
        }
        else
        {
            return 0;
        }
    }
    void operator()() // 仿函数
    {
        if (_func != nullptr) _func();
    }
    static void *runHelper(void *args)
    {
        Thread* ts = (Thread*)args;
        (*ts)();
        return nullptr;
    }
    void run()
    {
        int n = pthread_create(&_tid, nullptr, runHelper, this);
        if (n != 0) exit(1);
        _status = RUNNING;
    }
    void join()
    {
        int n = pthread_join(_tid, nullptr);
        if (n != 0)
        {
            std::cout << "man thread join thread" << _name << " error" << std::endl;
            return;
        }
        _status = EXITED;
    }
    ~Thread()
    {}
private:
    pthread_t _tid;
    std::string _name;
    func_t _func;
    ThreadStatus _status;
};

锁包装

#pragma once
#include <iostream>
#include <pthread.h>
class Mutex // 自己不维护锁,外部传入
{
private:
    pthread_mutex_t *_pmutex;
public:
    Mutex(pthread_mutex_t *mutex) : _pmutex(mutex)
    {}
    void lock()
    {
        pthread_mutex_lock(_pmutex);
    }
    void unlock()
    {
        pthread_mutex_unlock(_pmutex);
    }
    ~Mutex()
    {}
};
class lockGuard
{
private:
    Mutex _mutex;
public:
    lockGuard(pthread_mutex_t *mutex) : _mutex(mutex)
    {
        _mutex.lock();
    }
    ~lockGuard()
    {
        _mutex.unlock();
    }
};

服务器

#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unordered_map>
#include "err.hpp"
#include "RingQueue.hpp"
#include "lockGuard.hpp"
#include "Thread.hpp"
const static uint16_t port = 8888;
using func_t = std::function<std::string(std::string)>;
class UdpServer
{
private:
    uint16_t _port;
    int _sock;
    std::unordered_map<std::string, struct sockaddr_in> _onlineuser;
    pthread_mutex_t _lock;
    RingQueue<std::string> _rq;
    Thread *c;
    Thread *p;
public:
    UdpServer(uint16_t port = port) : _port(port)
    {
        std::cout << "server addr " << _port << std::endl;
        pthread_mutex_init(&_lock, nullptr);
        p = new Thread(1, std::bind(&UdpServer::Recv, this));
        c = new Thread(1, std::bind(&UdpServer::Broadcast, this));
    }
    void start()
    {
        _sock = socket(AF_INET, SOCK_DGRAM, 0); // 创建套接字
        if (_sock < 0)
        {
            std::cout << "create _sock error" << std::endl;
            exit(SOCKET_ERR);
        }
        std::cout << "create _sock success" << std::endl;
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_port = htons(_port);
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0) // 绑定套接字
        {
            std::cout << "bind socket error" << std::endl;
            exit(BIND_ERR);
        }
        std::cout << "bind socket success" << std::endl;
        p->run();
        c->run();
    }
    void addUser(const std::string &name, const struct sockaddr_in &peer)
    {
        lockGuard guard(&_lock);
        auto it = _onlineuser.find(name);
        if (it != _onlineuser.end())
        {
            return;
        }
        _onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, peer));
    }
    void Recv()
    {
        char buf[2056];
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int n = recvfrom(_sock, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buf[n] = '\0';
            }
            else
                continue;
            std::cout << "recv done" << std::endl;
            std::string clientip = inet_ntoa(peer.sin_addr);
            uint16_t clientport = ntohs(peer.sin_port);
            std::cout << clientip << "-" << clientport << "#" << buf << std::endl;
            std::string name = clientip;
            name += "-";
            name += std::to_string(clientport);
            addUser(name, peer);
            _rq.push(buf);
        }
    }
    void Broadcast()
    {
        while (true)
        {
            std::string sendstring;
            _rq.pop(&sendstring);
            std::vector<struct sockaddr_in> v;
            {
                lockGuard guard(&_lock);
                for (auto user : _onlineuser)
                {
                    v.push_back(user.second);
                }
            }
            for (auto user : v)
            {
                sendto(_sock, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr*)&(user), sizeof(user));
                std::cout << "send done" << sendstring << std::endl;
            }
        }
    }
    ~UdpServer()
    {
        pthread_mutex_destroy(&_lock);
        c->join();
        p->join();
        delete p, c;
    }
};

地址转换函数

inet_ntoa 是把返回结果放到了静态区,这个时候不需要手动释放。如果多次调用,会覆盖掉上一次的值

6. 简单的TCP网络程序

TCP由于是全双工的,所以初始化工作一共有五步

  1. socket
  2. bind
  3. listen
  4. accept
  5. connect

listen 声明socket fd处

  1. 于监听状态,并且允许多个客户端来连接等待状态
    accept 三次握手完成后,调用服务器连接,

connect 客户端需要调用这个函数连接服务器

TCP 简单服务器:

#pragma once
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "err.hpp"
static const int defaultport = 8888;
static const int backlog = 32;
using func_t = std::function<std::string(const std::string &)>;
class TcpServer;
class ThreadData
{
public:
    int _sock;
    std::string _clientip;
    uint16_t _port;
    TcpServer *_current;
public: 
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *ts)
    : _sock(fd), _clientip(ip), _port(port), _current(ts)
    {}
};
class TcpServer
{
public:
    TcpServer(func_t func, uint16_t port = defaultport) : _port(port), _func(func)
    {
    }
    void initServer()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            std::cout << "create socket error" << std::endl;
            exit(SOCKET_ERR);
        }
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = htonl(INADDR_ANY);
        local.sin_port = htons(_port);
        if (bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            std::cout << "create bind error" << std::endl;
            exit(BIND_ERR);
        }
        if (listen(_listensock, backlog) < 0)
        {
            std::cout << "create listen error" << std::endl;
            exit(LISTEN_ERR);
        }
    }
    static void* threadRuntinue(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->_current->server(td->_sock, td->_clientip, td->_port);
        delete td;
        return nullptr;
    }
    void start()
    {
        _quit = false;
        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sock = accept(_listensock, (struct sockaddr*)&client, &len);                                                  
            if (sock < 0)
            {
                std::cout << "create accept error" << std::endl;
                continue;
            }
            std::string clientip = inet_ntoa(client.sin_addr);
            uint16_t clientport = ntohs(client.sin_port);
            std::cout << "accept success" << clientip << " " << clientport << std::endl;
            // server(sock, clientip, clientport); 第一个版本
            // 多线程版本
            pthread_t tid;
            ThreadData *threadDate = new ThreadData(sock, clientip, clientport, this);
            pthread_create(&tid, nullptr, threadRuntinue, threadDate);
        }
    }
    void server(int sock, const std::string ip, uint16_t port)
    {
        std::string who = ip + "-" + std::to_string(port);
        char buffer[1024];
        while (true)
        {
            size_t s = read(sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0;
                std::string res = _func(buffer); // 回调传进来的函数
                std::cout << who << ">>>" << res << std::endl;
                write(sock, res.c_str(), res.size());
            }
            else if (s == 0)
            {
                close(sock);
                std::cout << who << "quit, me too" << std::endl;
                break;
            }
            else
            {
                close(sock);
                std::cout << "read error" << std::endl;
                break;
            }
        }
    }
    ~TcpServer() {}
private:
    uint16_t _port;
    int _listensock;
    bool _quit;
    func_t _func;
};

TCP 简单的客户端

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "err.hpp"
int main(int argc, char *argv[])
{
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    // 要不要bind? 要
    // 要不要自己bind? 不要,因为client要让OS自动给用户进行bind
    // 要不要listen?不要 要不要accept?不需要
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_aton(serverip.c_str(), &(server.sin_addr));
    int cnt = 5;
    while (connect(sock, (struct sockaddr*)&server, sizeof(server)) != 0)
    {
        sleep(1);
        std::cout << "正在尝试重新连接" << std::endl;
        cnt--;
        if (cnt < 0) break;
    }
    if (cnt <= 0) {
        std::cout << "连接失败" << std::endl;
        exit(CONNECT_ERR);
    }
    char buffer[1024];
    while (true)
    {
        std::string line;
        std::getline(std::cin, line);
        write(sock, line.c_str(), line.size());
        size_t s = read(sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo" << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "server quit" << std::endl;
            break;
        } else {
            std::cout << "read error" << std::endl;
            break;
        }
    }
    close(sock);
    return 0;
}

6.1 TCP socket的封装

#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "Err.hpp"
static const int gbacklog = 32;
static const int defaultfd = -1;
class Sock
{
public:
    Sock() : _sock(defaultfd) {}
    void Socket()
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            logMessage(Fatal, "socket error, code : %d", errno);
            exit(SOCKET_ERR);
        }
    }
    void Bind(const uint16_t &port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
    }
    void Listen()
    {
        if (listen(_sock, gbacklog) < 0)
        {
            logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
            exit(LISTEN_ERR);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        int sock = accept(_sock, (struct sockaddr *)&temp, &len);
        if (sock < 0)
        {
            logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
        }
        else
        {
            *clientip = inet_ntoa(temp.sin_addr);
            *clientport = ntohs(temp.sin_port);
        }
        return sock;
    }
    int Connect(const std::string &serverip, const uint16_t &serverport)
    {
        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());
        return connect(_sock, (struct sockaddr *)&server, sizeof(server));
    }
    int Fd()
    {
        return _sock;
    }
    ~Sock()
    {
        if (_sock != defaultfd)
            close(_sock);
    }
private:
    int _sock;
};

6.2 TCP协议通讯流程

服务器初始化:

  • 调用socket,创建文件描述符
  • 调用bind,将当前的文件描述符和ip / port 绑定在一起,如果这个端口被占用了,就会bind失败
  • 调用listen,声明这个文件描述符是服务器的文件描述符,为后面的accept做好准备
  • 调用accept并阻塞,等待客户端连接

建立连接的过程:

  • 调用socket,创建文件描述符
  • 调用connect,向服务器发起连接请求
  • connect会发出SYN并阻塞等待服务器应答
  • 服务器收到客户端的SYN,会应答一个SYN-ACK,表示同意建立连接
  • 客户端收到SYN-ACK后会从connect()返回,同时应答一个ACK
    这个建立过程称为三次握手

TCPVSUDP

  • 可靠传输 VS 不可靠传输
  • 有连接 VS 无连接
  • 字节流 VS 数据报

7. 守护进程

// 1. setsid();
// 2. setsid(), 调用进程,不能是组长!我们怎么保证自己不是组长呢?
// 3. 守护进程a. 忽略异常信号 b. 0,1,2要做特殊处理 c. 进程的工作路径可能要更改 /
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "log.hpp"
#include "err.hpp"
//守护进程的本质:是孤儿进程的一种!
void Daemon()
{
    // 1. 忽略信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    // 2. 让自己不要成为组长
    if (fork() > 0)
        exit(0);
    // 3. 新建会话,自己成为会话的话首进程
    pid_t ret = setsid();
    if ((int)ret == -1)
    {
        logMessage(Fatal, "deamon error, code: %d, string: %s", errno, strerror(errno));
        exit(SETSID_ERR);
    }
    // 4. 可选:可以更改守护进程的工作路径
    // chdir("/")
    // 5. 处理后续的对于0,1,2的问题
    int fd = open("/dev/null", O_RDWR);
    if (fd < 0)
    {
        logMessage(Fatal, "open error, code: %d, string: %s", errno, strerror(errno));
        exit(OPEN_ERR);
    }
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}
相关文章
|
2月前
|
安全 Linux 网络安全
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
100 0
Nipper 3.9.0 for Windows & Linux - 网络设备漏洞评估
|
3月前
|
运维 Linux 开发者
Linux系统中使用Python的ping3库进行网络连通性测试
以上步骤展示了如何利用 Python 的 `ping3` 库来检测网络连通性,并且提供了基本错误处理方法以确保程序能够优雅地处理各种意外情形。通过简洁明快、易读易懂、实操性强等特点使得该方法非常适合开发者或系统管理员快速集成至自动化工具链之内进行日常运维任务之需求满足。
234 18
|
3月前
|
网络协议 关系型数据库 Linux
【App Service Linux】在Linux App Service中安装 tcpdump 并抓取网络包
在App Service for Linux环境中,无法像Windows一样直接使用网络排查工具抓包。本文介绍了如何通过TCPDUMP在Linux环境下抓取网络包,包括SSH进入容器、安装tcpdump、执行抓包命令及下载分析文件的完整操作步骤。
202 5
|
4月前
|
Web App开发 网络协议 Linux
【Linux】网络基础
TCP/IP五层模型是网络通信的基础框架,将复杂的数据传输过程分为物理层、数据链路层、网络层、传输层和应用层,每层各司其职,协同完成远程通信。该模型确保了不同设备和网络之间的互联互通,是现代互联网运行的核心机制。
315 5
|
4月前
|
网络协议 Linux 开发者
深入Linux中UDP网络通信机制编程探索
以上步骤概述了Linux中UDP网络通信的编程机制。在实现时,因关注细节和上下文环境可能有所调整,但大致流程是一致的。这些知识片段旨在帮助开发者快速上手Linux下的UDP编程,并提供可靠的信息作为编程的基础。在编程实践中,应结合实际业务需求,设计合适的数据传输协议,确保数据的正确性和实时性。
130 0
|
6月前
|
安全 网络协议 Linux
Linux网络应用层协议展示:HTTP与HTTPS
此外,必须注意,从HTTP迁移到HTTPS是一项重要且必要的任务,因为这不仅关乎用户信息的安全,也有利于你的网站评级和粉丝的信心。在网络世界中,信息的安全就是一切,选择HTTPS,让您的网站更加安全,使您的用户满意,也使您感到满意。
196 18
|
6月前
|
Linux 数据安全/隐私保护
使用Linux命令行接入无线网络Wi-Fi的示例。
现在,你已经使用命令行成功地连接到 Wi-Fi 网络了。这两个示例涵盖了用 `nmcli` 和 `wpa_supplicant` 连接无线网络的常见场景,让你能够不依赖图形化界面来完成这个任务。在日常使用中熟练掌握这些基本操作能增强你对 Linux 系统的理解,帮助你更有效地处理各种问题。
454 12
|
6月前
|
安全 Ubuntu Linux
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
221 0
Nipper 3.8.0 for Windows & Linux - 网络设备漏洞评估
|
8月前
|
Ubuntu Linux
Linux系统管理:服务器时间与网络时间同步技巧。
以上就是在Linux服务器上设置时间同步的方式。然而,要正确运用这些知识,需要理解其背后的工作原理:服务器根据网络中的其他机器的时间进行校对,逐步地精确自己的系统时间,就像一只犹豫不决的啮齿动物,通过观察其他啮齿动物的行为,逐渐确定自己的行为逻辑,既简单,又有趣。最后希望这个过程既能给你带来乐趣,也能提高你作为系统管理员的专业素养。
1289 20
|
8月前
|
JSON 运维 Ubuntu
Linux下如何使用Curl进行网络请求
希望这篇文章能帮助您在Linux下更好地使用Curl进行网络请求。如有疑问,请随时提问!
433 10

热门文章

最新文章