Linux 网络编程套接字

news2024/10/4 13:22:08

目录

一.网络知识

1.网络通信

2.端口号

(1)介绍

(2)端口号和进程ID

3.TCP协议

4.UDP协议

5.网络字节序

二. socket编程接口

1.socket常见API

2.sockaddr结构

(1)sockaddr结构

(2)sockaddr_in结构

(3)in_addr结构

三.实现一个UDP网络程序

1.Makefile

2.Log.hpp

3.udpServer.cc

4.udpClient.cc

四.地址转换函数和inet_ntoa

1.地址转换函数

2.inet_ntoa

五.守护进程

六.实现一个TCP网络程序(守护进程化)

1.Lock.hpp

2.Task.hpp

3.ThreadPool.hpp

4.log.hpp

5.util.hpp

6.clientTcp.cc

7.serverTcp.cc

8.daemonize.hpp

七.TCP协议通讯流程

1.服务器初始化

2.建立连接的过程

3.数据传输的过程

4.断开连接的过程


前言:从这一篇开始,细致的介绍网络,学会使用网络编程

一.网络知识

1.网络通信

        主机间通信的目的本质是:在各自的主机上的两个进程在互相交互数据。

        IP地址可以完成主机和主机的通信,而主机上各自的通信进程,才是发送和接受数据的一方。

        在进行通信的时候,不仅仅要考虑两台主机间互相交互数据。

        本质上讲,进行数据交互的时候,是用户和用户在进行交互。用户的身份,通常是用程序体现的。程序一定是在运行中的 ---- 进程。

        IP ---- 确保主机的唯一性

        端口号(port):确保该主机上的进程的唯一性

        IP:PORT = 标识互联网中唯一的一个进程。 ---- socket

        网络通信的本质:也是进程间通信。

2.端口号

(1)介绍

① 端口号是一个2字节16位的整数;

② 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;

③ IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;

④ 一个端口号只能被一个进程占用

(2)端口号和进程ID

        进程pid是可以表示唯一一个进程的,此处端口号也是唯一表示一个进程。那么为什么不用进程ID表示,而用端口号呢?

        使用端口号,可以解耦,并且一个进程可能会绑定多个端口号,但是一个进程只有一个进程ID。看到端口号就可以知道这个是属于网络的。(举个例子:就比如学生都有身份证号,并且是唯一的,但是学校中又使用学号,也是唯一的,但是如果身份证不用了,也不会影响学校,这就完成了解耦,同时学校使用学号,会更方便的对学生进行管理,同时如果有学号就说明是属于这个学校的,但是身份证号是无法区分是不是这个学校的。)

3.TCP协议

① 传输层协议

② 有连接

③ 可靠传输

④ 面向字节流

4.UDP协议

① 传输层协议

② 无连接

③ 不可靠传输

④ 面向数据报

5.网络字节序

        我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

① 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;

② 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;

③ 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.

④ TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.

⑤ 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;

⑥ 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可

        为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

① 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。

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

③ 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

④ 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

二. socket编程接口

1.socket常见API

// 创建 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);

2.sockaddr结构

        socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、 IPv6,以及后面要说的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同

① IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址

② IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容

③ socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数

(1)sockaddr结构

(2)sockaddr_in结构

        虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址

(3)in_addr结构

        in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数

三.实现一个UDP网络程序

1.Makefile

.PHONY:all
all:udpClient udpServer

udpClient: udpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
udpServer:udpServer.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udpClient udpServer

2.Log.hpp

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);

    va_end(ap); // ap = NULL

    FILE *out = (level == FATAL) ? stderr : stdout;

    fprintf(out, "%s | %u | %s | %s\n",
            log_level[level],
            (unsigned int)time(nullptr),
            name == nullptr ? "unknow" : name,
            logInfo);
}

3.udpServer.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <unordered_map>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Log.hpp"

static void Usage(const std::string porc)
{
    std::cout << "Usage:\n\t" << porc << " port [ip]" << std::endl;
}

/// @brief  我们想写一个简单的udpSever
/// 云服务器有一些特殊情况:
/// 1. 禁止你bind云服务器上的任何确定IP, 只能使用INADDR_ANY,如果你是虚拟机,随意
class UdpServer
{
public:
    UdpServer(int port, std::string ip = "") : port_((uint16_t)port), ip_(ip), sockfd_(-1)
    {
    }
    ~UdpServer()
    {
    }

public:
    void init()
    {
        // 1. 创建socket套接字
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 就是打开了一个文件
        if (sockfd_ < 0)
        {
            logMessage(FATAL, "socket:%s:%d", strerror(errno), sockfd_);
            exit(1);
        }
        logMessage(DEBUG, "socket create success: %d", sockfd_);
        // 2. 绑定网络信息,指明ip+port
        // 2.1 先填充基本信息到 struct sockaddr_in
        struct sockaddr_in local;     // local在哪里开辟的空间? 用户栈 -> 临时变量 -> 写入内核中
        bzero(&local, sizeof(local)); // memset
        // 填充协议家族,域
        local.sin_family = AF_INET;
        // 填充服务器对应的端口号信息,一定是会发给对方的,port_一定会到网络中
        local.sin_port = htons(port_);
        // 服务器都必须具有IP地址,"xx.yy.zz.aaa",字符串风格点分十进制 -> 4字节IP -> uint32_t ip
        // INADDR_ANY(0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法
        // inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n
        local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());
        // 2.2 bind 网络信息
        if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1)
        {
            logMessage(FATAL, "bind: %s:%d", strerror(errno), sockfd_);
            exit(2);
        }
        logMessage(DEBUG, "socket bind success: %d", sockfd_);
        // done
    }

    void start()
    {
        // 服务器设计的时候,服务器都是死循环
        char inbuffer[1024];  //将来读取到的数据,都放在这里
        char outbuffer[1024]; //将来发送的数据,都放在这里
        while (true)
        {
            struct sockaddr_in peer;      //输出型参数
            socklen_t len = sizeof(peer); //输入输出型参数

            // demo2
            //  UDP无连接的
            //  对方给你发了消息,你想不想给对方回消息?要的!后面的两个参数是输出型参数
            ssize_t s = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0,
                                 (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                inbuffer[s] = 0; //当做字符串
            }
            else if (s == -1)
            {
                logMessage(WARINING, "recvfrom: %s:%d", strerror(errno), sockfd_);
                continue;
            }
            // 读取成功的,除了读取到对方的数据,你还要读取到对方的网络地址[ip:port]
            std::string peerIp = inet_ntoa(peer.sin_addr);       //拿到了对方的IP
            uint32_t peerPort = ntohs(peer.sin_port); // 拿到了对方的port

            checkOnlineUser(peerIp, peerPort, peer); //如果存在,什么都不做,如果不存在,就添加

            // 打印出来客户端给服务器发送过来的消息
            logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer);

            // for(int i = 0; i < strlen(inbuffer); i++)
            // {
            //     if(isalpha(inbuffer[i]) && islower(inbuffer[i])) outbuffer[i] = toupper(inbuffer[i]);
            //     else outbuffer[i] = toupper(inbuffer[i]);
            // }
            messageRoute(peerIp, peerPort,inbuffer); //消息路由

            // 线程池!

            // sendto(sockfd_, outbuffer, strlen(outbuffer), 0, (struct sockaddr*)&peer, len);

            // demo1
            // logMessage(NOTICE, "server 提供 service 中....");
            // sleep(1);
        }
    }

    void checkOnlineUser(std::string &ip, uint32_t port, struct sockaddr_in &peer)
    {
        std::string key = ip;
        key += ":";
        key += std::to_string(port);
        auto iter = users.find(key);
        if(iter == users.end())
        {
            users.insert({key, peer});
        }
        else
        {
            // iter->first, iter->second->
            // do nothing
        }
    }

    void messageRoute(std::string ip, uint32_t port, std::string info)
    {

        std::string message = "[";
        message += ip;
        message += ":";
        message += std::to_string(port);
        message += "]# ";
        message += info;

        for(auto &user : users)
        {
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)&(user.second), sizeof(user.second));
        }
    }
            
private:
    // 服务器必须得有端口号信息
    uint16_t port_;
    // 服务器必须得有ip地址
    std::string ip_;
    // 服务器的socket fd信息
    int sockfd_;
    // onlineuser
    std::unordered_map<std::string, struct sockaddr_in> users;
};

// struct client{
//     struct sockaddr_in peer;
//     uint64_t when; //peer如果在when之前没有再给我发消息,我就删除这用户
// }

// ./udpServer port [ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3) //反面:argc == 2 || argc == 3
    {
        Usage(argv[0]);
        exit(3);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    UdpServer svr(port, ip);
    svr.init();
    svr.start();

    return 0;
}

4.udpClient.cc

#include <iostream>
#include <string>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>

struct sockaddr_in server;

static void Usage(std::string name)
{
    std::cout << "Usage:\n\t" << name << " server_ip server_port" << std::endl;
}

void *recverAndPrint(void *args)
{
    while (true)
    {
        int sockfd = *(int *)args;
        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
}

// ./udpClient server_ip server_port
// 如果一个客户端要连接server必须知道server对应的ip和port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 1. 根据命令行,设置要访问的服务器IP
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    // 2. 创建客户端
    // 2.1 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);

    // 2.2 client 需不需要bind??? 需要bind,但是不需要用户自己bind,而是os自动给你bind
    // 所谓的"不需要",指的是: 不需要用户自己bind端口信息!因为OS会自动给你绑定
    // 如果我非要自己bind呢?可以!但是严重不推荐!
    // 所有的客户端软件 <-> 服务器 通信的时候,必须得有 client[ip:port] <-> server[ip:port]
    // 为什么呢??client很多,不能给客户端bind指定的port,port可能被别的client使用了,你的client就无法启动了
    // 那么server凭什么要bind呢??server提供的服务,必须被所有人知道!server不能随便改变!
    // 2.2 填写服务器对应的信息

    bzero(&server, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    pthread_t t;
    pthread_create(&t, nullptr, recverAndPrint, (void *)&sockfd);
    // 3. 通讯过程
    std::string buffer;
    while (true)
    {
        std::cerr << "Please Enter# ";
        std::getline(std::cin, buffer);
        // 发送消息给server
        sendto(sockfd, buffer.c_str(), buffer.size(), 0,
               (const struct sockaddr *)&server, sizeof(server)); // 首次调用sendto函数的时候,我们的client会自动bind自己的ip和port
    }

    close(sockfd);

    return 0;
}

本地通信测试:

        这个也是可以远程通信的,通过sz udpClient,然后发布该文件,别人再rz -e该文件,因为默认情况是没有可执行权限的,再chmod +x udpClient,这时再找到自己云服务器的公网ip,别人./udpClient 公网ip 8080,就可以在自己的服务器接收到别人发的信息了。

        也可以在Window中创建一个可以链接上Linux的可执行程序,那么在Window中输入的信息,就可以显式在Linux中。

        还可以把广播过来的信息放入一个fifo里,这个fifo文件就是一个聊天室。

         这里可以看到,输入在udpClient中的信息都显示在了fifo的管道文件中。同时也有对应输出信息那个人的ip和端口号。

四.地址转换函数和inet_ntoa

1.地址转换函数

        本篇只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换。

        字符串转in_addr的函数:

         in_addr转字符串的函数:

        其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr

        示例:

2.inet_ntoa

        inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?

         man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.

        那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:

运行结果如下:

        因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.

思考: 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?

① 在APUE中, 明确提出inet_ntoa不是线程安全的函数;

② 但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;

③ 在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

        多线程调用inet_ntoa代码示例如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *Func1(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr1: %s\n", ptr);
    }
    return NULL;
}

void *Func2(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr2: %s\n", ptr);
    }
    return NULL;
}

int main()
{
    pthread_t tid1 = 0;
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    pthread_create(&tid1, NULL, Func1, &addr1);
    pthread_t tid2 = 0;
    pthread_create(&tid2, NULL, Func2, &addr2);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

五.守护进程

        一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭,否则,一直会在运行。

        构建一个会话,必须要有一个前台进程组(而且任何时刻,只能有一个前台进程组),给我们加载bash,有0个或者多个后台进程组。

        加载的bash构建了一个会话,自己是会话前台进程组,后续如果我们自己再新启动进程(启动进程组),依旧属于bash自己的会话。

        因此在命令行中启动一个进程,现在就可以叫做在会话中启动一个进程组,来完成某中任务。

        所有会话内的进程fork创建子进程,一般而言依旧属于当前会话。

        在登录的状态时,新起了一个网络服务器,创建好之后,再派生的子进程也属于当前会话,所以我们就不能让这个网络服务器属于这个会话内容,要不然它会受到用户的登录和注销的影响。

        所以当我们有个网络服务的时候,应该脱离这个会话,让它独立的在计算机里自成进程组,自成新会话。这样在两个用户同时登录的时候,形成的两个会话是独立的,在操作各自的bash不会互相影响。

        像这种自成进程组,自成新会话,而且周而复始进行的进程称为守护进程(精灵进程)。

如何自己形成呢?

        必须调用一个函数setsid():将调用进程设置称为独立的会话

        进程组的组长不应调用setsid()

        那么如何让'我'不成为组长呢?可以让'我'成为进程组内的第二个进程。常规做法:fork()子进程,子进程就不是组长进程了,它就可以成功调用setsid()。

        

        server端一直在写,client端关闭了,server端就会被终止(server会收到SIGPIPE信号终止)

因此,必做的:

if(fork() > 0) exit(0)

setsid();

选做的:

忽略SIGPIPE信号

更改进程的工作目录,通过chdir更改

方法:

1.close(0, 1, 2) 【很少有这么做的,不推荐】

2.打开 /dev/null ,并且进行对0, 1, 2重定向。 (/dev/null 类似于Linux下的一个"垃圾桶 or 文件黑洞",凡是从/dev/null里面读写一概被丢弃) 

六.实现一个TCP网络程序(守护进程化)

1.Lock.hpp

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock_, nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&lock_);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock_);
    }

private:
    pthread_mutex_t lock_;
};

class LockGuard
{
public:
    LockGuard(Mutex *mutex) : mutex_(mutex)
    {
        mutex_->lock();
        std::cout << "加锁成功..." << std::endl;
    }

    ~LockGuard()
    {
        mutex_->unlock();
        std::cout << "解锁成功...." << std::endl;
    }

private:
    Mutex *mutex_;
};

2.Task.hpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "log.hpp"

class Task
{
public:
    //等价于
    // typedef std::function<void (int, std::string, uint16_t)> callback_t;
    using callback_t = std::function<void (int, std::string, uint16_t)>;
private:
    int sock_; // 给用户提供IO服务的sock
    uint16_t port_;  // client port
    std::string ip_; // client ip
    callback_t func_;  // 回调方法
public:
    Task():sock_(-1), port_(-1)
    {}
    Task(int sock, std::string ip, uint16_t port, callback_t func)
    : sock_(sock), ip_(ip), port_(port), func_(func)
    {}
    void operator () ()
    {
        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 开始啦...",\
            pthread_self(), ip_.c_str(), port_);

        func_(sock_, ip_, port_);

        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 结束啦...",\
            pthread_self(), ip_.c_str(), port_);
    }
    ~Task()
    {}
};

3.ThreadPool.hpp

#pragma once

#include <iostream>
#include <cassert>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "Lock.hpp"

using namespace std;

int gThreadNum = 15;

template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum) : threadNum_(threadNum), isStart_(false)
    {
        assert(threadNum_ > 0);
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T>&) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance) //仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }

        return instance;
    }
    //类内成员, 成员函数,都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        // prctl(PR_SET_NAME, "follower"); // 更改线程名称
        while (1)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            //这个任务就被拿到了线程的上下文中
            T t = tp->pop();
            tp->unlockQueue();
            t(); // 让指定的先处理这个任务
        }
    }
    void start()
    {
        assert(!isStart_);
        for (int i = 0; i < threadNum_; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);
        }
        isStart_ = true;
    }
    void push(const T &in)
    {
        lockQueue();
        taskQueue_.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    int threadNum()
    {
        return threadNum_;
    }

private:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool haveTask() { return !taskQueue_.empty(); }
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
    T pop()
    {
        T temp = taskQueue_.front();
        taskQueue_.pop();
        return temp;
    }

private:
    bool isStart_;
    int threadNum_;
    queue<T> taskQueue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *instance;
    // const static int a = 100;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

4.log.hpp

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};

#define LOGFILE "serverTcp.log"

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);

    va_end(ap); // ap = NULL

    // 每次打开太麻烦
    umask(0);
    int fd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);
    assert(fd >= 0);

    FILE *out = (level == FATAL) ? stderr : stdout;

    dup2(fd, 1);
    dup2(fd, 2);

    fprintf(out, "%s | %u | %s | %s\n",
            log_level[level],
            (unsigned int)time(nullptr),
            name == nullptr ? "unknow" : name,
            logInfo);

    fflush(out); // 将C缓冲区中的数据刷新到OS
    fsync(fd);   // 将OS中的数据尽快刷盘 

    close(fd);
    // char *s = format;
    // while(s){
    //     case '%':
    //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
    //     break;
    // }
}

5.util.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3
#define USAGE_ERR  4
#define CONN_ERR   5

#define BUFFER_SIZE 1024

6.clientTcp.cc

#include "util.hpp"
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1. 创建socket SOCK_STREAM
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    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);
    // 2.2 发起请求,connect 会自动帮我们进行bind!
    if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(CONN_ERR);
    }
    std::cout << "info : connect success: " << sock << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入你的消息>>> ";
        std::getline(std::cin, message); // 结尾不会有\n
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

        ssize_t s = write(sock, message.c_str(), message.size());
        if (s > 0)
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)(message.c_str()), 1024);
            if (s > 0)
                message[s] = 0;
            std::cout << "Server Echo>>> " << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

7.serverTcp.cc

#include "util.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include "daemonize.hpp"

#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

class ServerTcp; // 申明一下ServerTcp

// 大小写转化服务
// TCP && UDP: 支持全双工
void transService(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char inbuffer[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为我们读到的都是字符串
        if (s > 0)
        {
            // read success
            inbuffer[s] = '\0';
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
            // 可以进行大小写转化了
            for (int i = 0; i < s; i++)
            {
                if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                    inbuffer[i] = toupper(inbuffer[i]);
            }
            logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);

            write(sock, inbuffer, strlen(inbuffer));
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

void execCommand(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char command[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, command, sizeof(command) - 1); //我们认为我们读到的都是字符串
        if (s > 0)
        {
            command[s] = '\0';
            logMessage(DEBUG, "[%s:%d] exec [%s]", clientIp.c_str(), clientPort, command);
            // 考虑安全
            std::string safe = command;
            if((std::string::npos != safe.find("rm")) || (std::string::npos != safe.find("unlink")))
            {
                break;
            }
            // 我们是以r方式打开的文件,没有写入
            // 所以我们无法通过dup的方式得到对应的结果
            FILE *fp = popen(command, "r");
            if(fp == nullptr)
            {
                logMessage(WARINING, "exec %s failed, beacuse: %s", command, strerror(errno));
                break;
            }
            char line[1024];
            while(fgets(line, sizeof(line)-1, fp) != nullptr)
            {
                write(sock, line, strlen(line));
            }
            // dup2(fd, 1);
            // dup2(sock, fp->_fileno);
            // fflush(fp);
            pclose(fp);
            logMessage(DEBUG, "[%s:%d] exec [%s] ... done", clientIp.c_str(), clientPort, command);
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;

public:
    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {
    }
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "")
        : port_(port),
          ip_(ip),
          listenSock_(-1),
          tp_(nullptr)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 运行别人来连接你了

        // 4. 加载线程池
        tp_ = ThreadPool<Task>::getInstance();
    }
    // static void *threadRoutine(void *args)
    // {
    //     pthread_detach(pthread_self()); //设置线程分离
    //     ThreadData *td = static_cast<ThreadData *>(args);
    //     td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
    //     delete td;
    //     return nullptr;
    // }
    void loop()
    {
        // signal(SIGCHLD, SIG_IGN); // only Linux
        tp_->start();
        logMessage(DEBUG, "thread pool start success, thread num: %d", tp_->threadNum());
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接, accept 的返回值是一个新的socket fd ??
            // 4.1 listenSock_: 监听 && 获取新的链接-> sock
            // 4.2 serviceSock: 给用户提供新的socket服务
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            // transService(serviceSock, peerIp, peerPort);

            // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     close(listenSock_); //建议
            //     //子进程
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0); // 进入僵尸
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!

            // 5.1 v1.1 版本 -- 多进程版本  -- 也是可以的
            // 爷爷进程
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // 爸爸进程
            //     close(listenSock_);//建议
            //     // 又进行了一次fork,让 爸爸进程
            //     if(fork() > 0) exit(0);
            //     // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0);
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!
            // // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
            // pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
            // assert(ret > 0);
            // (void)ret;

            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗??不需要啦
            // 多线程是会共享文件描述符表的!
            // ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, threadRoutine, (void*)td);

            // 5.3 v3 版本 --- 线程池版本
            // 5.3.1 构建任务
            // 5.3 v3.1
            // Task t(serviceSock, peerIp, peerPort, std::bind(&ServerTcp::transService, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            // tp_->push(t);

            // 5.3 v3.2
            // Task t(serviceSock, peerIp, peerPort, transService);
            // tp_->push(t);
            // 5.3 v3.3
            Task t(serviceSock, peerIp, peerPort, execCommand);
            tp_->push(t);

            // waitpid(); 默认是阻塞等待!WNOHANG
            // 方案1

            // logMessage(DEBUG, "server 提供 service start ...");
            // sleep(1);
        }
    }

private:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
    // 引入线程池
    ThreadPool<Task> *tp_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n"
              << std::endl;
}

// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];
    
    daemonize(); // 我们的进程就会成为守护进程

    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

8.daemonize.hpp

#pragma once

#include <cstdio>
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void daemonize()
{
    int fd = 0;
    // 1. 忽略SIGPIPE
    signal(SIGPIPE, SIG_IGN);
    // 2. 更改进程的工作目录
    // chdir();
    // 3. 让自己不要成为进程组组长
    if (fork() > 0)
        exit(1);
    // 4. 设置自己是一个独立的会话
    setsid();
    // 5. 重定向0,1,2
    if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. 关闭掉不需要的fd
        if(fd > STDERR_FILENO) close(fd);
    }
}

七.TCP协议通讯流程

① tcp是面向链接的,client connect &&server accept

② tcp在建立链接的时候,采用的是三次握手,在断开链接的时候,采用的是四次挥手

③ connect发起三次挥手(client),close() client && closer() server -> close() 执行4次挥手中的2次

1.服务器初始化

① 调用socket, 创建文件描述符

② 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败

③ 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备

④ 调用accecpt, 并阻塞, 等待客户端连接过来

2.建立连接的过程

① 调用socket, 创建文件描述符;

② 调用connect, 向服务器发起连接请求;

③ connect会发出SYN段并阻塞等待服务器应答; (第一次)

④ 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)

⑤ 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)

        这个建立连接的过程, 通常称为 三次握手

3.数据传输的过程

① 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据

② 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待

③ 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答

④ 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求

⑤ 客户端收到后从read()返回, 发送下一条请求,如此循环下去

4.断开连接的过程

① 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次)

② 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次)

③ read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)

④ 客户端收到FIN, 再返回一个ACK给服务器(第四次)

        这个断开连接的过程, 通常称为 四次挥手

在学习socket API时要注意应用程序和TCP协议层是如何交互的:

① 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段

② 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段

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

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

相关文章

JavaScript 语句

文章目录JavaScript 语句JavaScript 语句分号 ;JavaScript 代码JavaScript 代码块JavaScript 语句标识符JavaScript 语句 JavaScript 语句向浏览器发出的命令。语句的作用是告诉浏览器该做什么。 JavaScript 语句 JavaScript 语句是发给浏览器的命令。 这些命令的作用是告诉浏…

顶象入选信通院“数字政府建设赋能计划”成员单位

为进一步推动数字政府建设提质增效&#xff0c;由中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;联合数字政府相关企业、科研机构共同成立“数字政府建设赋能计划”&#xff0c;旨在凝聚各方力量&#xff0c;整合优质资源&#xff0c;开展技术攻关&#xf…

FlinkSQL基本语法和概念

Flink Sql1、简介2、网址3、SQL客户端4、Queries5、Create6、Drop7、Alter8、Insert9、ANALYZE10、Describe11、Explain12、Use13、Show14、Load15、Unload16、Set17、Reset18、Jar19、Windowing TVF19.1、TUMBLE&#xff08;滚动窗口&#xff09;19.2、HOP&#xff08;滑动窗口…

rabbitmq+netcore6 【2】Work Queues:一个生产者两个消费者

文章目录1&#xff09;准备工作2&#xff09;新建消费者13&#xff09;新建消费者24&#xff09;生产者5&#xff09;知识点解读1、autoAck: true2、重复声明/前后不一致3、Message durability 消息持久化4、Fair Dispatch 公平调度5、综合以上知识点的代码&#xff1a;官网参考…

Linux的运行级别

Linux的运行级别: Linux系统有7种运行级别(runlevel): 运行级别 0&#xff1a;系统停机状态&#xff0c;系统默认运行级别不能设为0&#xff0c;否则不能正常启动运行运行级别 1&#xff1a;单用户工作状态&#xff0c;root权限&#xff0c;用于系统维护&#xff0c;找回丢失…

少儿Python每日一题(9):约瑟夫环

原题解答 本次的题目如下所示(原题出处:蓝桥杯) 【编程实现】 有n个人围成一个圈,按顺序排好号。然后从第一个人开始报数(从1到3 报数),报到3的人退出圈子,然后继续从1到3报数,直到最后留下一个 人游戏结束,问最后留下的是原来第几号。 输入描述:输入一个正整数n 输…

国际手机号码检查纠正 API 接口

国际手机号码检查纠正 API 接口 有效性检查及智能纠正&#xff0c;遵循 E.164 标准&#xff0c;智能统一格式。 1. 产品功能 智能检测国际手机号码有效性&#xff1b;可根据提供的国家编码参数&#xff0c;判断提供的手机号码是否为该国家有效手机号码&#xff1b;智能纠正提…

场景题:假设10W人突访,你的系统如何做到不 雪崩?

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

头歌:Ping服务端创建UDP套接字(底部附全关完整答案)

头歌实践教学平台 (educoder.net)在 Ping 的 服务程序中 创建一个使用 UDP 协议的 套接字数据包套接字类型套接字三种类型:流式套接字(SOCK_STREAM)&#xff0c;数据包套接字(SOCK_DGRAM)及原始套接字&#xff08;SOCK_RAW&#xff09;数据包格式套接字&#xff08;Datagram So…

JavaSE---多用户网络通信系统

目录 项目开发流程 多用户网络通信系统的架构设计 客户端 界面层 服务层 管理层 服务端 服务层 功能层 管理层 总结 项目开发流程 多用户网络通信系统的架构设计 整体 作为一个可供多个用户使用的通信系统&#xff0c;那么每个用户和其他用户之间的连接必定不是直接…

电脑宽带连接提示错误代码769怎么办?

有用户反映&#xff0c;在使用宽带连接网络时&#xff0c;出现错误代码769&#xff0c;无法连接到指定目标怎么办&#xff1f;这里整理了错误代码769的可能原因和修复方法&#xff0c;带大家顺利连接网络。错误代码769的原因&#xff1a;连接线松动或损坏网卡被禁用网卡驱动过时…

【信息论与编码 沈连丰】第四章:离散信源的信源编码

【信息论与编码 沈连丰】第四章&#xff1a;离散信源的信源编码第四章 离散信源的信源编码4.1 信源编码的模型4.2 信息传输速率和编码效率4.3 单义可译定理4.4 无失真信源编码定理4.5 几种典型的信源编码方法4.6 汉字编码方法及其讨论4.7 图像的信源编码4.8 误码对信源译码的影…

openFeign远程调用返回页面404 ,对应配置文件不生效,排除数据源等问题

在使用上架商品功能时&#xff0c;在debug时候&#xff0c;发现在将数据发送给ES保存时&#xff0c;无法远程调用es的服务&#xff0c;报错404找不到接口&#xff0c;如下图&#xff1a; 一开始以为是openFeign的问题&#xff0c;经过检查&#xff0c;各种接口、注解都没问题&…

2022尚硅谷SSM框架跟学(一)MyBatis基础一

2022尚硅谷SSM框架跟学 一MyBatisSSM框架整合课程优势课程体系框架图MyBatis1、MyBatis简介1.1MyBatis历史1.2MyBatis特性1.3MyBatis下载1.4和其它持久化层技术对比JDBCHibernate 和 JPAMyBatis2.搭建MyBatis2.1开发环境2.2创建maven工程(1)打包方式&#xff1a;jar(2)引入依赖…

【UE4 第一人称射击游戏】20-添加瞄准十字线

上一篇&#xff1a;【UE4 第一人称射击游戏】19-修复冲刺或换弹时可以进行射击的bug本篇效果&#xff1a;步骤&#xff1a;先下载一个瞄准的十字线图片&#xff0c;可以从阿里巴巴矢量图库下载&#xff1a;https://www.iconfont.cn/search/index?searchTypeicon&q%E7%9E%8…

反射Reflection

目录1. 反射快速入门1. 需求2. 运用反射2. 反射原理图2.1 反射相关的主要类2.1 反射优点和缺点2.1.1 反射调用优化-关闭访问检查4. Class类分析4.1 Class类常用方法4.2 获取Class类对象【六种】4.3 哪些类型有class对象4.4 动态和静态加载4.5 类加载流程图5. 获取类结构信息5.1…

RabbitMQ、Kafka、RocketMQ三种消息中间件对比总结

文章目录前言侧重点架构模型消息通讯其他对比总结参考文档前言 不论Kafka还是RabbitMQ和RocketMQ&#xff0c;作为消息中间件&#xff0c;其作用为应用解耦、异步通讯、流量削峰填谷等。 拿我之前参加的一个电商项目来说&#xff0c;订单消息通过MQ从订单系统到支付系统、库存…

【国科大模式识别】第一次作业

【题目一】设 ωmax⁡\omega_{\max }ωmax​ 为类别状态, 此时对所有的 i(i1,…,c)i(i1, \ldots, c)i(i1,…,c), 有 P(ωmax⁡∣x)≥P\left(\omega_{\max } \mid \boldsymbol{x}\right) \geqP(ωmax​∣x)≥ P(ωi∣x)P\left(\omega_i \mid \boldsymbol{x}\right)P(ωi​∣x) …

理解 mysql 之 count(*)的性能问题

一、 count(*) 为什么性能差 在Mysql中&#xff0c;count()的作用是统计表中记录的总行数。而count()的性能跟存储引擎有直接关系&#xff0c;并非所有的存储引擎&#xff0c;count(*)的性能都很差。在Mysql中使用最多的存储引擎是&#xff1a;innodb 和 myisam 。 在 myisam…

手写RPC框架-整合注册中心模块设计与实现

源码地址&#xff1a;https://github.com/lhj502819/IRpc/tree/v2 思考 如果同一个服务有10台不同的机器进行提供&#xff0c;那么客户端该从哪获取这10台目标机器的ip地址信息呢&#xff1f;随着调用方的增加&#xff0c;如何对服务调用者的数据进行监控呢&#xff1f;服务提…