【linux】网络 - 传输层 -- socket(TCP/UDP)

文章目录

  • socket
  • 预备知识
    • TCP和UDP 协议
    • 网络字节序
  • socket接口及辅助接口
    • sockaddr
    • udpserver和udpclient
      • 初始化udp服务器
      • 运行udp服务器
      • udp客户端
    • tcpserver和tcpclient
      • 初始化服务器
      • 运行服务器
      • Tcp客户端
  • 守护进程


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_instruct sockaddr_in local;bzero(&local, sizeof(local)); // 清0local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str()); /// 3 bin socketbind(_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_instruct sockaddr_in local;bzero(&local, sizeof(local)); // 清0local.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 socketint 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_instruct sockaddr_in local;bzero(&local, sizeof(local)); // 清0local.sin_family = AF_INET;local.sin_port = htons(port_);inet_aton(ip_.c_str(), &local.sin_addr);// 3 bin listensockfdif (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){//sonclose(listensockfd_);if(fork() > 0) exit(0);//孙子进程,会由系统领养service(sockfd, client_ip, client_port);close(sockfd);exit(0);}//fatherclose(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_instruct sockaddr_in local;bzero(&local, sizeof(local)); // 清0local.sin_family = AF_INET;local.sin_port = htons(port_);inet_aton(ip_.c_str(), &local.sin_addr);// 3 bin socketif (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的时候,系统自动bindint 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, 不过一般都是自己手写。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV C++学习笔记

1.图像的读取与显示 1.1 加载并显示一张图片 #include<opencv2/opencv.hpp> #include<iostream>using namespace cv; using namespace std; int main(int argc,char** argv){Mat srcimread("sonar.jpg");//读取图像if(src.empty()){printf("Could…

Presto Player 2.0 – 引人入胜的视频播放列表

Presto Player 2.0 引入了一项令人惊叹的新功能&#xff1a;视频播放列表。 将其与类似 Netflix 的新体验相结合&#xff0c;您将发现一款流畅的视频播放器&#xff0c;其功能在市场上任何其他工具中都找不到。 让我们看看 Presto Player 2.0 如何将您的内容提升到新的参与度…

使用 HBuilderX自动上传Uniapp 微信小程序代码

HBuilderX内置相关环境&#xff0c;开箱即用&#xff0c;无需配置nodejs。本文只介绍发布微信小程序的步骤。 1.下载和安装 HBuilderX hbuilder首页&#xff1a;https://www.dcloud.io/hbuilderx.html 下载hbuilder编辑器,选择对应的系统,Windows和mac正式版即可,下载后免安…

代码随想录算法训练营DAY25|C++回溯算法Part.2|216. 组合总和III、17.电话号码的字母组合

文章目录 216. 组合总和III题意理解树形结构伪代码实现剪枝操作CPP代码实现 17.电话号码的字母组合解题思路树形结构伪代码实现隐藏回溯CPP代码 216. 组合总和III 力扣题目链接 文章讲解&#xff1a;216. 组合总和III 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算…

Vue-B站学习笔记

1. 路由配置 B站视频之Vue route文件下的index.js app.vue

几分钟!你的PDF文件就可以具有仿真翻页的效果!

你是否曾经在阅读PDF文件时感到困扰&#xff0c;因为它的页面是静态的&#xff0c;无法模拟真实的翻页效果&#xff1f;现在&#xff0c;我教你一个很实用的方法&#xff1a;几分钟&#xff01;你的PDF文件就可以具有仿真翻页的效果&#xff01; 工具&#xff1a;FLBOOK在线制作…

叉车载货出入库AI检测算法介绍及应用

随着物流行业的快速发展&#xff0c;叉车作为物流运输的重要设备&#xff0c;其安全性和效率性越来越受到人们的关注。然而&#xff0c;在实际操作中&#xff0c;由于人为因素和操作环境的复杂性&#xff0c;叉车事故时有发生&#xff0c;给企业和个人带来了巨大的损失。为了提…

langchain 文本向量化存储,并检索相似 topK

目录 chroma 检索 faiss 检索 检索器 相似性 最大相关性mmr 相似数阈值 txt 有多行&#xff0c;我的这份数据有 67 行&#xff0c;样例如下&#xff1a; 字段1\t值1\n 字段2\t值2\n ... chroma 检索 pip install langchain-chroma 在本地下载了 embedding 模型&…

kali工具----网络映射器(Network Mapper)

识别活跃的主机 尝试渗透测试之前&#xff0c;必须先识别在这个目标网络内活跃的主机。在一个目标网络内&#xff0c;最简单的方法将是执行ping命令。当然&#xff0c;它可能被一个主机拒绝&#xff0c;也可能被接收。本节将介绍使用Nmap工具识别活跃的主机。 1、网络映射器工具…

突破编程_前端_SVG(rect 矩形)

1 rect 元素的基本属性和用法 在SVG中&#xff0c;<rect> 元素用于创建矩形。 <rect> 元素有一些基本的属性&#xff0c;可以用来定义矩形的形状、位置、颜色等。以下是这些属性的详细解释&#xff1a; x 和 y &#xff1a;这两个属性定义矩形左上角的位置。 x …

智慧公厕:忙时不顶用,闲时没啥用的玩意。

智慧公厕是一种利用智能科技和物联网技术来提升公厕管理和用户体验的新型公厕形式。它通过集成各种智能设备和传感器&#xff0c;实现自动感应、自动清、自动消毒等功能&#xff0c;以提供更便捷、卫生、舒适的使用体验。 在忙时&#xff0c;智慧公厕能够提供更高效的服务。例如…

计算机视觉 | 基于 ORB 特征检测器和描述符的全景图像拼接算法

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本项目实现了基于 ORB 特征检测器和描述符的全景图像拼接算法&#xff0c;能够将两张部分重叠的图像拼接成一张无缝连接的全景图像。 文章目录 一、随机抽样一致算法二、功能实现三、代码解析四、效果展示五、完整代码 一、随机…