【linux网络】socket编程(TCP/UDP)

网络通信(Socket/TCP/UDP) TCP/IP传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。TCP/IP传输协议对互联网中各部分进行通信的标准和方法进行了规定。并且,TCP/IP传输协议是保证网络数据信息及时、完整传输的两个重要的协议。TCP/IP传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。[2]TCP/IP协议是Internet最基本的协议,其中应用层的主要协议有TelnetFTPSMTP。 阅读详情


socket

Socket(套接字)是计算机网络编程中的一个抽象概念,用于在不同计算机之间进行通信。它是一种通信机制,允许计算机上的进程通过网络进行数据传输。Socket可以视为一种特殊的文件描述符,通过它可以进行读取和写入操作,就像操作文件一样。

socket的组成:IP + port

  • IP负责找到网络里的唯一主机。
  • port(端口号) 负责找到该主机上的唯一进程。

ip地址:标识网络里的唯一主机
记住2个特殊的ip地址:
0.0.0.0:某个套接字绑定到 0.0.0.0 地址意味着该套接字将接受来自任何本地网络接口的连接。
127.0.0.1: 是本地回环地址,通常被称为 “localhost”。这个地址通常用于在本地主机上进行网络通信。在网络编程中,使用 127.0.0.1 地址可以实现在同一台计算机上的不同进程之间进行通信。

端口号的特点

  1. 端口号和进程pid的联系与区别:
    PID是操作系统为每个进程分配的唯一标识符,用于在系统中区分不同的进程;而端口号是用于在网络通信中标识不同应用程序或服务的数字标识符。二者对于进程都具有唯一性。

  2. 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定

  3. 在计算机网络中,一些常见的端口号默认被特定的服务或进程占用。
    端口 80:HTTP服务通常使用此端口来提供Web服务。
    端口 443:HTTPS服务通常使用此端口来提供加密的Web服务。
    端口 21:FTP(文件传输协议)服务器通常使用此端口来进行文件传输。
    端口 22:SSH(安全外壳协议)服务器通常使用此端口来进行安全的远程访问。
    端口 25:SMTP(简单邮件传输协议)服务器使用此端口来传输电子邮件。
    ……

预备知识

TCP和UDP 协议

在网络编程中,常用的协议(如TCP/IP、UDP等)都基于Socket来实现
下面简单介绍一下

TCP

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

UDP

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

简单的理解:
TCP相当于打电话,先要建立连接,保证传输安全,再进行通信。
UDP相当于发邮箱,不用先建立连接,直接发,至于数据是否丢失、出错等,UDP并不关心。如果出问题,直接重发。

UDP的不可靠并不是贬义,它仅仅是对UDP特点的描述。连接的可靠性是有代价的,需要维护可靠性,这意味TCP更复杂,而不可靠意味UDP很简单。根据不同的应用场景,选择合适的协议的。

网络字节序

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

大端:低字节序放在高地址端,高字节序放在低地址端
小端:低字节序放在低地址端,高字节序放在高地址端
简记:小小小
在这里插入图片描述

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;

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

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

不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

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

上述接口,会发现一个结构体sockaddr,这个结构体是什么?

sockaddr

sockaddr(socket address)结构体是用于表示套接字地址的数据结构。网络底层协议不同,套接字结构体也不同。

套接字编程的种类:

  • 域间套接字:同一个机器通信
  • 原始套接字:绕过传输层,直接访问网络层
  • 网络套接字:网络通信

不同的结构体导致使用难度上升,于是设计者设计了一套统一的接口sockaddr。
在这里插入图片描述
sockaddr_in和sockaddr_un是具体我们使用的套接字结构体。当我们将它们的指针作为参数传给socket接口函数,强制转换为sockaddr*。
如下:

struct sockaddr_in local;
int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));

在函数内部,函数会解析local的前2个字节,如果是AF_INET就表示使用IPv4的网络通信,如果AF_UNIX就表示使用本地通信。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.

那为什么不像很多系统接口一样,使用void * 来作为参数类型呢?
因为那时候c语言还没有void*

下面介绍一下sockaddr_in
sockaddr_in 是在 Linux 中用于表示 IPv4 地址和端口的结构体
官方的定义如下:
在这里插入图片描述
简化一下:

struct sockaddr_in {
    unsigned short   sin_family;   // 地址族 (AF_INET)
    unsigned short   sin_port;     // 端口号
    struct in_addr  sin_addr;     // IPv4 地址
    char             sin_zero[8];  // 填充 0 的字节以使结构与 sockaddr 结构的大小相同
};
  • sin_family 字段表示地址族,对于 IPv4 地址来说,它的值通常是 AF_INET。
  • sin_port 字段表示端口号,以网络字节序存储 (大端字节序)。
  • sin_addr 是一个 struct in_addr 类型的结构体,用于存储 IPv4 地址。
  • sin_zero 是用于填充的额外字段,以使 sockaddr_in 结构体的大小与 sockaddr 结构体的大小相同。

udpserver和udpclient

udpserver
在这里插入图片描述

std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8888;
class udpserver
{
public:
	 udpserver(uint16_t port = defaultport) :_port(port)
    {}
    ~udpserver()
    {
        close(_sockfd);
    }
    void init()
    {
    	//初始化服务器
	}
	void run()
	{
		//运行服务器
	}
private:
    int _sockfd; //socket 文件描述符
    std::string _ip = defaultip; //点分十进制的ip地址
    uint16_t _port; //端口号
};

初始化udp服务器

void init()
    {
    	// 1 create sockfd   
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        // 2 init sockadd_in
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清0
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); /
        // 3 bin socket
        bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
    }
  1. create sockfd

int socket(int domain, int type, int protocol);// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
参数:

  • domain: 指定通信协议族。常见的包括 AF_INET(IPv4),AF_INET6(IPv6),AF_UNIX(Unix 域套接字),等等。
  • type: 指定套接字的类型,常见的包括 SOCK_STREAM(字节流式套接字,用于面向连接的通信,如 TCP),SOCK_DGRAM(数据报套接字,用于无连接的通信,如 UDP),以及其他类型。
  • protocol: 指定协议。在大多数情况下,可以指定为 0,表示使用默认协议。对于某些特定的协议族和类型组合,需要指定具体的协议。

我们创建的是ipv4的udpserver, 因此使用如下:
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);

  1. init sockadd_in
    分别对3个成员进行初始化,注意细节:
  • port:网络通信采用的是大端字节序,如果你使用的小端存储的机器,需要将port改为大端字节序。但你怎么知道自己使用的机器是大端还是小端?难道自己还有判断一下?有辅助接口可以解决这个麻烦。
    在这里插入图片描述
  • ip:其一、地址不是直接赋给sin_addr, sin_addr类型定义如下:
struct in_addr sin_addr;

typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

sin_addr的类型是in_addr的结构体,地址保存在in_addr的成员s_addr中,使用时要注意。
其二、由于用户使用的是点分十进制风格的ip地址(字符串),因此有辅助接口帮助我们进行转换。
在这里插入图片描述
使用如下:

local.sin_addr.s_addr = inet_addr(_ip.c_str());
 inet_aton(_ip.c_str(), &local.sin_addr);
  1. bind socket
    现在有了socket结构体和sock文件描述符,我们还需要绑定二者。
    在这里插入图片描述
    bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));

由此udpserver的核心工作就完成了,为什么?因为udp是无连接的,不用关心消息传输的问题。

运行udp服务器

udpserver简易收发消息代码:

void run()
    {
        char buffer[SIZE]; //缓存
        while(_isRunning)
        {
            buffer[0] = 0;
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&client, &len);
            buffer[n] = 0;
            //处理服务器收到的数据报
            std::string client_message = buffer;
            client_message += " Server has received";

            //发送数据报
            sendto(_sockfd, client_message.c_str(), client_message.size(), 0, (struct sockaddr* )&client, len);
        }
    }

recvfrom和sendto是udp收发数据报的函数。

recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  • sockfd:指定待接收数据的套接字描述符。
  • buf:指向用于存放接收数据的缓冲区。
  • len:指定接收数据缓冲区的大小。
  • flags:一组标志,通常设置为0。
  • src_addr:指向用于存放发送端地址信息的结构体指针。
  • addrlen:指向一个整数,用于指定 src_addr 结构体的大小。
  • 返回接收到的字节数

sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd:指定发送数据的套接字描述符。
  • buf:指向待发送数据的缓冲区。
  • len:指定待发送数据的长度。
  • flags:一组标志,通常设置为0。
  • dest_addr:指向包含目标地址信息的结构体指针。
  • addrlen:指定 dest_addr 结构体的大小。
  • 返回实际发送的字节数。

完整的服务器代码,这里的log.hpp是我写的一个简易日志。

#include <iostream>
#include <string>
#include <memory>

#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.hpp"
extern logger log;

std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8080;

int size = 1024;

class udpserver
{
public:
    udpserver(uint16_t port = defaultport) :_port(port)
    {}
    void init()
    {
        
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            //log(Fatal, "socket creat fail. socket : %d", _sockfd);
            exit(1);
        }
        //log(Info, "socket creat success. socket: %d", _sockfd);
        // 2 create sockadd_in
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清0
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); // uint32_t 需要将字符串 --> uint32_t, 使用函数 inet_addr
        // 或者s_addr = INADDR_ANY
        // 3 bin socket
        int n = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            //log(Fatal, "bind error: %d, err string: %s", errno, strerror(errno));
            exit(1);
        }
        //log(Info, "bind success, n = %d", n);
    }

    void run()
    {
        _isRunning = true;
        char buffer[size];
        while(_isRunning)
        {
            buffer[0] = 0;

            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                //log(Warning, "recvfrom error, errno:%d, err string: %s", errno, buffer);
                exit(1);
            }
            buffer[n] = 0;
            //log(Info, "receive success");
            //处理服务器收到的报文
            std::string client_message = buffer;
            client_message += " Server has received";

            //发送数据包
            sendto(_sockfd, client_message.c_str(), client_message.size(), 0, (struct sockaddr* )&client, len);
        }
    }

    ~udpserver()
    {
        close(_sockfd);
    }
private:
    int _sockfd;
    std::string _ip = defaultip;
    uint16_t _port;
    bool _isRunning = false;
};

udp客户端

在这里插入图片描述

客服端代码:客户端通常不用手动bind套接字和sockfd
当客户端调用 socket() 函数创建套接字时,操作系统会为该套接字分配一个未使用的端口号(通常是一个临时端口号),并在后续的通信过程中,该端口号会被动态地绑定到套接字上。这样客户端就可以与服务器端建立连接并进行通信,而无需手动指定端口号。
相反,服务器端通常需要绑定一个固定的端口号,以便客户端可以知道在哪个端口监听服务器的连接请求。因此,服务器端通常需要调用 bind() 函数来指定要监听的端口号和 IP 地址

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

#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        cout << "Usage: ./client [serverip] [serverport]" << endl;
        return 0;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;

    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    std::string message;
    char buffer[1024];
    while(true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);
        
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr* )&server, len);

        buffer[0] = 0;
        struct sockaddr_in tmp;
        socklen_t tmp_len = sizeof(tmp);
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0,(struct sockaddr*)&tmp, &tmp_len);

        buffer[n] = 0;
        cout << buffer << endl;

    }

    close(sockfd);
    return 0;
}

问题:ip,port需要主机序列转网络序列,那网络通信的内容需要吗?需要,但不需要手动完成,recfrom和sendto会自动转换。

tcpserver和tcpclient

在这里插入图片描述

class tcpserver
{
public:
    tcpserver(uint16_t port = defaultport) : port_(port)
    {
    }
    ~tcpserver()
    {
        close(listensockfd_);
    }
    void init(); 
    void run();
private:
    int listensockfd_;
    std::string ip_ = defaultip;
    uint16_t port_;
};

初始化服务器

void init()
    {
        //1. 创建监听描述符
        listensockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensockfd_ < 0)
        {
            sfw_log(Fatal, "socket creat fail. socket : %d", listensockfd_);
            exit(1);
        }
        sfw_log(Info, "socket creat success. socket: %d", listensockfd_);
        // 2 create sockadd_in
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清0
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &local.sin_addr);
        // 3 bin listensockfd
        if (bind(listensockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
        	//自己写的一个日志
            //sfw_log(Fatal, "bind error: %d, err string: %s", errno, strerror(errno));
            exit(1);
        }
        sfw_log(Info, "bind success");
        if (listen(listensockfd_, backlog))
        {
            //sfw_log(Fatal, "listen error");
            exit(1);
        }
        sfw_log(Info, "listen success, sockf:%d", listensockfd_);
    }

和udp的区别,再udp中只有一种套接字:通信套接字。
而在tcp中有两种套接字:监听套接字和通信套接字

  • 监听套接字:监听是否有客户端进行连接
  • 通信套件字:与客户端传输数据
int listen(int sockfd, int backlog);
  • backlog(连接队列的长度):这是一个整数参数,指定在连接队列中等待接受处理的连接请求的最大数量。如果连接队列已满,新的连接请求将会被拒绝或丢弃。具体的实现限制可能有所不同,但通常情况下这个参数的取值范围在 1 到 SOMAXCONN 之间,SOMAXCONN 是系统定义的连接队列的默认最大长度(128)
  • 返回值:0成功,-1失败

运行服务器

    void run()
    {
        //sfw_log(Info, "topserver is running……");
        while (1)
        {
            // 1. 获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensockfd_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                //sfw_log(Warning, "accept error, errno : %d, errstring: %s", errno, strerror(errno));
                continue;
            }
            uint16_t client_port = ntohs(client.sin_port);
            char client_ip[32] = {0};
            inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof(client_ip));
            //sfw_log(Info, "get a new link, sockfd:%d, client [%s] [%d]", sockfd, client_ip, client_port);
			
            // 2 通过新连接进行通信
            //方法很多
            //不要忘记close(sockfd)
        }
    }

accept函数

int accept(int listensockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 返回值:sockfd, 用来与客户端通信。

tcp服务器不会一直与客户端进行连接,不然如果客户端连接了,但什么也不做,会浪费服务器资源。因此服务器一般会对连接设定时间,或者完成客户端的请求后,立即断开连接。

tcp与客户端进行通信的方式有很多种:

  1. 单进程:直接让主执行流执行。但是这将导致tcp服务器一次只能为一个连接服务
// 2 通过新连接进行通信
// 单进程
service(sockfd, client_ip, client_port);//服务
close(sockfd);
  1. 多进程:让孙子进程执行。可以一次服务多个连接,但创建进程的代价较大,效率低
    为什么要让孙子进程执行?如果让子进程执行,父进程还需要等待子进程,
多进程
             pid_t id = fork();
             if(id == 0)
             {
                 //son
                 close(listensockfd_);
                 if(fork() > 0) exit(0);
                 //孙子进程,会由系统领养
                 service(sockfd, client_ip, client_port);
                 close(sockfd);
                 exit(0);
             }
             //father
             close(sockfd);
             //阻塞等待,但是阻塞等待,会导致父进程卡在这,不会重新accept
             //让孙子进程操作
             pid_t rid = waitpid(id, nullptr, 0);
             (void)rid;
  1. 多线程:让其他执行流执行。可以一次服务多个连接,效率较高。但每次都要新建线程
			ThreadData *td = new ThreadData(sockfd, client_ip, client_port);
            pthread_t tid;
            pthread_create(&tid, nullptr, service_routine, (void *)td);
  1. 线程池:多线程的升级,提前创建好几个线程,有连接就交给线程池。
    这里的TcpTask和ThreadPool自定义
            TcpTask t(sockfd, client_ip, client_port);
            ThreadPool<TcpTask>::GetInstance()->push(t);

实现一个线程池版本:

#pragma once

#include <iostream>
#include <string>
#include <memory>
#include <functional>

#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <pthread.h>

#include <string.h>


#include <netinet/in.h>
#include <arpa/inet.h>

#include "threadpool.hpp"//线程池

std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8888;

int size = 1024;
int backlog = 128;

struct ThreadData
{
    ThreadData(int sd, std::string i, uint16_t p) : sockfd(sd), ip(i), port(p)
    {
    }
    ~ThreadData()
    {
        close(sockfd);
    }
    int sockfd;
    std::string ip;
    uint16_t port;
};


class tcpserver
{
public:
    tcpserver(uint16_t port = defaultport) : port_(port)
    {
    }
    ~tcpserver()
    {
        close(listensockfd_);
    }
    void init()
    {
        // 1. 转换为守护进程
        Daemon();
        listensockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listensockfd_ < 0)
        {
            //sfw_log(Fatal, "socket creat fail. socket : %d", listensockfd_);
            exit(1);
        }
        //sfw_log(Info, "socket creat success. socket: %d", listensockfd_);
        // 2 create sockadd_in
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 清0
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(), &local.sin_addr);
        // 3 bin socket
        if (bind(listensockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            //sfw_log(Fatal, "bind error: %d, err string: %s", errno, strerror(errno));
            exit(1);
        }
        //sfw_log(Info, "bind success");
        if (listen(listensockfd_, backlog))
        {
            //sfw_log(Fatal, "listen error");
            exit(1);
        }
        //sfw_log(Info, "listen success, sockf:%d", listensockfd_);
    }
    void run()
    {
        //sfw_log(Info, "topserver is running……");
        while (1)
        {
            // 1. 获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensockfd_, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                //sfw_log(Warning, "accept error, errno : %d, errstring: %s", errno, strerror(errno));
                continue;
            }
            uint16_t client_port = ntohs(client.sin_port);
            char client_ip[32] = {0};
            inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof(client_ip));
            //sfw_log(Info, "get a new link, sockfd:%d, client [%s] [%d]", sockfd, client_ip, client_port);

            // 2 通过新链接进行通信
            TcpTask t(sockfd, client_ip, client_port);
            ThreadPool<TcpTask>::GetInstance()->push(t);         
        }
    }

private:
    int listensockfd_;
    std::string ip_ = defaultip;
    uint16_t port_;
};

线程池

#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>
#include <vector>
#include <string>

#include "TcpTask.hpp"

/*
设计思路:

一、线程池的成员:
1. 线程表 -- vector<ThreadInfo> threads_
2. 任务表 -- queue<T> tasks_
3. 最大任务数 -- maxcap_
4. 互斥锁和条件变量 -- mutex_, p_cond_, c_cond_
二、线程池工作流程
    创建一批线程,这批线程循环、竞争、搜查任务表,有任务工作,无任务阻塞



*/
struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

template <class T>
class ThreadPool
{
private:
    void lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void psleep()
    {
        pthread_cond_wait(&p_cond_, &mutex_);
    }
    void csleep()
    {
        pthread_cond_wait(&c_cond_, &mutex_);
    }
    void pwake()
    {
        pthread_cond_signal(&p_cond_);
    }
    void cwake()
    {
        pthread_cond_signal(&c_cond_);
    }
    std::string GetThreadName(pthread_t tid)
    {
        for (auto &e : threads_)
        {
            if (e.tid == tid)
            {
                return e.name;
            }
        }
        return "wu";
    }

private:
    ThreadPool(int threadsnum = 5, int maxcap = 10)
        : maxcap_(maxcap), threads_(threadsnum)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&p_cond_, nullptr);
        pthread_cond_init(&c_cond_, nullptr);

        for (int i = 0; i < threads_.size(); i++)
        {
            threads_[i].name = "thread-" + std::to_string(i);
            pthread_create(&(threads_[i].tid), nullptr, SolveTask, (void *)this);
        }
    }
    ThreadPool(const ThreadPool<T> &e) = delete;
    const ThreadPool &operator=(const ThreadPool &e) = delete;
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);
    }

public:
    static void *SolveTask(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool *tp = static_cast<ThreadPool *>(args);
        while (true)
        {
            tp->lock();
            while (tp->tasks_.empty())
            {
                tp->csleep();
            }
            T out = tp->tasks_.front();
            tp->tasks_.pop();
            tp->pwake();
            tp->unlock();

            // 处理任务
            out.run();
            //
        }
    }

    void push(const T &in)
    {
        lock();
        while (tasks_.size() == maxcap_)
        {
            psleep();
        }
        tasks_.push(in);
        cwake();
        unlock();
    }

    static ThreadPool<T> *GetInstance()
    {
        
        if (nullptr == tp_)
        {
            pthread_mutex_lock(&lock_);  
            if (tp_ == nullptr)
            {
                tp_ = new ThreadPool;
                return tp_;
            }
            pthread_mutex_unlock(&lock_);
        }
        return tp_;
    }

private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;
    int maxcap_;

    pthread_mutex_t mutex_;
    pthread_cond_t c_cond_;
    pthread_cond_t p_cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>

class TcpTask
{
public:
    TcpTask(int sockfd, std::string ip, uint16_t port)
        : sockfd_(sockfd), ip_(ip), port_(port)
    {
    }
    ~TcpTask()
    {
    }
    void run()
    {
        char buffer[1024];
        ssize_t n = read(sockfd_, buffer, sizeof(buffer));
        if (n > 0)
        {
            sfw_log(Info, "server receive a message");
            buffer[n] = 0;
            std::string mes;
            mes += "server receive: ";
            mes += buffer;
            n = write(sockfd_, mes.c_str(), mes.size());
            if(n < 0)
            {
                //sfw_log(Warning, "server write fail");
            }
        }
        else if (n == 0)
        {
            //sfw_log(Info, "client quie");
        }
        else
        {
            //sfw_log(Fatal, "read error, client_ip: %s, client_port: %d", ip_.c_str(), port_);
        }
        close(sockfd_);
    }
private:
        int sockfd_;
        std::string ip_;
        uint16_t port_;
};

Tcp客户端

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

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    while (true)
    {
        int cnt = 5;
        int isreconnect = false;
        int sockfd = 0;
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            std::cerr << "socket error" << std::endl;
            return 1;
        }
        do
        {
            // 客户端发起connect的时候,系统自动bind
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                isreconnect = true;
                cnt--;
                std::cerr << "connect error..., reconnect: " << cnt << std::endl;
                sleep(2);
            }
            else
            {
                break;
            }
        } while (cnt && isreconnect);

        if (cnt == 0)
        {
        //连接超时
            std::cerr << "user offline..." << std::endl;
            break;
        }

        std::string message;
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);
        if(message.size() == 0) continue;
        
        int n = write(sockfd, message.c_str(), message.size());
        if (n < 0)
        {
            std::cerr << "write error..." << std::endl;
            continue;
        }

        char inbuffer[4096];
        n = read(sockfd, inbuffer, sizeof(inbuffer));
        if (n > 0)
        {
            inbuffer[n] = 0;
            std::cout << inbuffer << std::endl;
        }
        else if (n == 0)
        {
            std::cout << "server quit……" << std::endl;
        }
        close(sockfd);
    }

    return 0;
}

connect函数

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)

守护进程

一般服务器开启后,便不会随便停止。因此我们可以把服务器进程转换为守护进程。

守护进程(Daemon Process)是在计算机操作系统中以服务方式运行的后台进程。它们通常在系统启动时启动,并在系统关闭时终止,而不与任何用户交互。守护进程通常在后台默默地执行任务,如监控、维护系统状态、处理特定的请求等

#pragma once

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <string>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
std::string nullfile = "/dev/null";
void Daemon(const std::string &cwd = "")
{
    // 1. 忽略常见的信号
    signal(SIGCLD, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    // 2. 变成独立会话 --- 组长(一般是第一个进程)不能变成独立会话,因此要先变成子进程(变为第二个进程)
    if (fork() > 0)
        exit(0);
    setsid();

    // 3. 更改当前调用进程的目录
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4. 关闭012, /dev/null 垃圾桶
    int fd = open(nullfile.c_str(), O_RDWR);
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}

调用一下Daemon函数即可。

当然linux也提供了系统接口daemon, 不过一般都是自己手写。
在这里插入图片描述

TCP/IP协议(1)——基础知识(协议与接口) 简单介绍TCP/IP模型中关于协议和接口的含义,为后面讲述做准备 阅读详情

相关推荐

Socket 通信机制详解

Socket 通信机制详解

喵酱 1万+

socket接口详解

socket接口详解socket概述接口详解socket()   socket概述 socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层负责复杂的操作抽象为几个简单的接口供应用层调用已以实现进程在网络中的通信。 socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种“打开——读/写——关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通信结束时关闭文件。   接口详解 sock

L607_607的博客 9124

Linux C/C++网络编程实战:从Socket基础到TCP/UDP并发服务器实现

网络编程是计算机系统编程的核心领域之一,它涉及进程间通信和数据交换的基础原理。从概念上讲,网络编程基于客户端-服务器模型,通过套接字(Socket)接口实现不同主机间的数据通信。其技术原理围绕TCP/IP协议栈展开,TCP提供面向连接的可靠字节流传输,而UDP则提供无连接的不可靠数据报服务。在工程实践中,网络编程的价值在于构建分布式系统、实现实时通信和支持高并发服务。常见的应用场景包括Web服务器、即时通讯软件、在线游戏和物联网设备通信等。本文通过热门的Linux C/C++开发环境,详细解析Socket

weixin_30650039的博客 389

【计算机网络】什么是socket编程?以及相关接口详解

我们使用一张图片很形象的可以看出socket所处的位置。从上图看出,Socket处于应用层与传输层的中间软件抽象层,它是一组接口。并且前面我们也学过,运输层和网络层其实属于操作系统内层面,我们只给用户提供使用的接口即可,不可能让用户能直接访问到内核部分。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。linux下一切皆文件!!!所以本质来说Socket即是一种。

luhaoran814的博客 3332

Linux网络编程入门:Socket、TCP/UDP与字节序详解

网络编程是计算机通信的基础,其核心在于实现不同主机间进程的数据交换。其原理基于TCP/IP协议栈,通过Socket API抽象底层网络细节,提供类似文件读写的操作接口。这一技术的价值在于构建稳定、高效的分布式系统,是后台服务、实时通信等应用的基石。在Linux开发中,理解Socket创建、TCP与UDP的本质差异以及网络字节序处理,是掌握高并发、高性能网络编程的前提。本文聚焦于Linux C语言环境,通过解析Socket API、对比TCP的可靠连接与UDP的无连接传输,并详解网络字节序转换,为后续学习IO

weixin_30613433的博客 441

Linux网络编程核心:Socket、字节序与TCP/UDP实战解析

网络编程是计算机通信的基础,其核心在于实现不同主机或进程间的数据交换。其基本原理是通过操作系统提供的Socket接口,建立通信端点,实现数据的发送与接收。从技术价值看,掌握网络编程能构建分布式系统、实现服务间通信,是后端开发、云计算、物联网等领域的必备技能。应用场景广泛,包括Web服务器、即时通讯、流媒体传输等。本文聚焦Linux环境,深入解析Socket编程中的关键概念,如字节序转换、地址处理,并对比TCP流Socket与UDP数据报Socket的差异,通过实战示例展示如何构建健壮的客户端与服务器程序,帮

weixin_33749131的博客 253

Linux:socket网络编程TCP/UDP

本文介绍了Socket网络编程的核心概念和实现方法。主要内容包括:1)Socket基础概念,区分TCP(可靠传输)和UDP(高效传输)的特点;2)TCP三次握手和四次挥手的过程;3)关键函数如socket()、bind()、listen()等的作用;4)完整的TCP和UDP通信代码实例,包含服务器和客户端的实现步骤。通过示例代码展示了如何建立连接、收发数据及关闭Socket等关键操作。文章为网络编程初学者提供了清晰的实践指导。

nbwoshi的博客 538

如何理解界面陷阱电荷呢(interface trapped charge)和费米钉扎效应?

如何理解界面陷阱电荷呢?(interface trapped charge) 我们在学半导体物理的时候经常对各种界面态和陷阱分不清楚,然而这却是半导体中最为关键的知识点之一。我这路主要介绍一下如何比较直观地理解界面陷阱电荷,应该也有叫界面态,快态之类的。 在介绍界面陷阱电荷之前,我们可以回顾一下施主(donor)和受主(acceptor)的概念。在si中如果掺杂了n型杂质P,那么P由于最外层电子有5个,有一个电子比较容易跑走,成为自由移动的导电电子,在能带中可以表现为电子从禁带中的能级跃迁到导带,这种情况我

Wang_P_P的博客 3万+

C/C++ Linux Socket网络编程 TCP 与 UDP

典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。

m0_54901781的博客 382

86 arduino 电机yl_(八)arduino入门:直流电机控制

一、本节介绍在本实验中,您将学习如何使用驱动芯片L293D和SunFounder Uno板控制小型直流(DC)电机的方向和速度。为了使初学者更容易,我们将让直流电机左右旋转,自动加速和减速。二、材料准备1 *小型直流电机1 * L293D1 * Arduino Uno板1 *面包板1 * USB数据线杜邦线三、原理讲解L293D这是一个非常实用的芯片,可以独立控制两台直流电机。在这个实验中,只有一...

weixin_29163857的博客 3567

InterSystems IRIS 数据库数据平台安装

下载地址:安装包:IRIS_Community-2024.1.0.267.2-win_x64.exe。

yuguang的博客 2369

【Linux/C++网络篇(一) 】网络编程入门:一文搞懂 TCP/UDP 编程模型与 Socket 网络编程

本文带你从零入门 C/C++ 网络编程,深入解析 Socket 核心机制。文章首先厘清字节序(大小端)、IP 地址及端口号等基础概念,详解 htonl/inet_addr 等关键转换函数。随后,通过完整的代码实战,分别演示了基于 TCP 的面向连接通信与基于 UDP 的无连接通信流程,对比了两者在 C/S 模型下的实现差异。无论是理解网络底层原理,还是手写第一个 Echo 服务器,本文都将为你打下坚实的后端开发基石,助你迈向高并发服务器开发之路。

2501_93095927的博客 5091

STM32F4的sct文件理解

原文地址http://blog.sina.com.cn/s/blog_898f36590100ya2l.html ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...

weixin_34008805的博客 995

Linux 网络编程基础:从 TCP/UDP 到 Socket 编程

地址转换工具inet_aton(字符串 IP→网络字节序)和inet_ntoa(网络字节序→字符串 IP)简化 IP 处理。协议选择:需可靠性选 TCP,需实时性选 UDP;UDP 需自行处理丢包和重传。错误处理:所有 Socket 函数需检查返回值,处理errno错误码(如EAGAIN表示资源暂时不可用)。Linux 网络编程的核心是理解 TCP/UDP 的差异、合理使用端口号、处理字节序转换,并掌握 Socket 编程的基本流程。

HJACNP的博客 1947

STM32控制舵机SG-5010转动,HAL库

首先设置时钟为72Mhz,预分频系数为72-1,自动重装载计数器的值为5000-1,这样的话就可以得到一个5ms的脉冲宽度,5000的计数值可以让舵机的旋转角度变得更加易控且相对精准,计算旋转角度的时候也可以更加精确。所以我们5ms脉宽,period是5000脉宽-------------角度----------pulse0.5ms-------------0度----------500;1.0ms------------45度---------1000;

qq_73783264的博客 1292

Unity零基础到入门 ☀️| 轻松学会 Unity界面布局和简单实例——入门级!(^_−)☆

入门级Unity知识,一起加油呀!

努力前行,总会成为自己心中的那道光 1万+

《Linux网络编程》2.Socket编程(UDP/TCP)

本文介绍了基于C++的UDP和TCP网络编程实现。主要内容包括:1)UDP编程的核心接口(socket、bind、send/recv)及C++封装实现UDPServer类;2)TCP编程的关键接口(listen、accept、connect)及TCPServer类的封装,包含InetAddr地址处理类;3)分别给出了UDP和TCP的客户端与服务器实现示例代码。文章重点阐述了两种协议的网络编程实现差异,并通过面向对象方式对底层系统调用进行封装,提升了代码复用性和可维护性。最后指出当前TCPServer实现存在

qq_49648783的博客 1499

SPSS 多重共线性问题:编程解决方案

多重共线性会导致回归模型的不稳定性,使得估计的回归系数失去解释力,降低模型的准确性和可靠性。通过执行这些代码,你将能够计算和查看VIF和CI的结果,并确定是否存在多重共线性问题。上述代码执行了一个回归分析,其中A是我们感兴趣的因变量,B、C、D和E是我们要检查的自变量。接下来,我们将执行多重共线性的检测。通过执行上述代码,你将能够看到每个自变量的VIF和CI值,以及预先设置的阈值。上述代码将计算VIF和CI的值,并将结果写入指定路径的结果文件中。接下来,我们可以使用以下代码来计算和显示VIF和CI的值。

CyberGenius的博客 1426

Linux网络编程核心实践:TCP/UDP socket与epoll高并发服务器构建

本篇博文是Linux网络编程的全面深度指南,带领读者从零开始构建一个完整、坚实的知识体系。文章分为三大板块:首先,基础理论篇系统地梳理了TCP/IP五层模型,通过命令行工具验证了从数据链路层到应用层的核心协议(ARP, IP, TCP/UDP)与数据封装过程;其次,实战编程篇详细讲解了socket编程的核心API,通过编写TCP和UDP的回射服务器,让读者亲手实践连接建立、数据交换和地址管理的完整流程,并最终通过一个epoll版的TCP服务器实战,展示了如何构建高并发网络应用。本文是理论与实践紧密结合的典范

weixin_52342399的博客 1099

分享Charles抓包 显示<unknown> 解决方案 !

首先电脑上需要安装charles,然后需要设置手机上的WiFi设置,修改配置中的代理设置,把服务器的主机名设置成你电脑当前的ip地址(需要注意的是,你的代理服务器主机名的ip地址要和你当前连的ip地址的网段需要一致)。像 Charles 这样的工具,虽然功能强大,但仍然需要用户具备一定的配置和排障能力,以应对各种复杂的网络环境。通过将 Charles 的 SSL 证书正确安装到移动设备中,并在设置中启用“允许不安全的内容”选项,最终成功解决了这一问题,并准确抓取到所有请求数据。Charles 显示。

朱雀随云记的博客 8105

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

嵌入式Linux网络编程实战:从Socket基础到TCP/UDP应用开发

本文详细介绍了嵌入式Linux网络编程实战,从Socket基础概念到TCP/UDP应用开发。重点解析了Socket创建、地址绑定、连接管理等核心函数,并提供了在资源受限的嵌入式环境中进行高效、稳定网络通信的开发技巧和最佳实践。

o5p6q的博客 202

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

linux下,socket网络编程TCP/UDP连接

自己封装了一个函数 int connectsock(char* server_ip, int server_port, int type, int protocolType){ int sock_fd = socket(AF_INET, type, protocolType); if(-1 == sock_fd){ printf(“create socket error\n”); return -...

小小英 257

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Linux网络编程 | socket介绍、网络字节序与主机字节序概念与两者的转换、TCP/UDP 连接中常用的 socket 接口

文章目录套接字sockaddr 和 sockaddr_in 套接字 为什么要用到套接字? 我们知道,数据链路层、网络层、传输层协议是在内核中实现的。而 socket 就是 操作系统 提供给 应用程序 通过 系统调用 访问这些 协议服务 的一组 API 。socket 不但可以访问内核中 TCP/IP 协议栈,而且访问其他网络协议栈。 socket 定义的 API 提供哪些功能? 将 应用程序数据 从 用户缓冲区 中复制到 TCP/UDP 内核发送缓冲区 ,将发送数据交付内核。 从 TCP/UDP

Jormungand_V的博客 793

【Linux网络-Socket编程预备】认识IP和端口号 + UDP/TCP编程接口

PID是操作系统分配给每个进程的唯一标识符,用于在系统内部标识和追踪进程。

2202_75331338的博客 1288

【Linux】探索网络编程:TCP/UDP协议解析与Socket应用实例

在现代信息技术飞速发展的今天,网络通信已经成为我们日常生活和工作中不可或缺的一部分。无论是通过电子邮件、社交媒体还是在线会议,网络通信都扮演着至关重要的角色。而在这背后,是复杂的网络协议和编程技术支撑着这一切的运行。本文旨在深入探讨网络编程的基础知识,特别是UDP和TCP这两种常用的传输层协议,以及它们在socket编程中的应用。通过本文,读者将能够理解源IP地址、目的IP地址、端口号等概念,并学习如何使用socket编程接口来创建网络应用程序。

Colorful___的博客 1639
上一篇: 【linux系统】线程(线程TCB+线程同步和互斥+线程安全)
下一篇: 【linux系统】进程(进程PCB + 进程地址空间+进程控制)
ProcedureStone
博客等级 码龄4年 254粉丝 64原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值