当前位置: 首页 > news >正文

Linux 网络套接字解析:实现网络通信

在这里插入图片描述

目录

  • 一.网络基础
    • 1.协议
    • 2.OSI与TCP/IP模型
    • 3.网络通信流程
    • 4.IP与Mac地址
  • 二.网络编程套接字
    • 1.端口号
    • 2.网络字节序
    • 3.tcp、udp协议
    • 4.socket编程
    • 5.sockaddr结构解析
    • 6.实现Udp_socket
    • 7.实现Windows与Linux通信
    • 8.Linux下远程执行指令
    • 9.实现tcp_socket
    • 10.守护进程

一.网络基础

1.协议

计算机相关的硬件软件很多,这时就需要制定一个标准保障数据的传输,所以网络协议就是是计算机网络中通信双方遵循的约定。本质上来说:协议是通过结构体表达出来的特定的双方都认识的结构体对象

2.OSI与TCP/IP模型

为了更好的将网络维护起来,OSI将网络模型分成了七层的理论模型,TCP/IP为互联网的实际模型通常为五层(四层)。
TCP/IP模型

  1. 物理层: 负责光/电信号的传递方式. 比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同轴电缆(现在主要用于有线电视)、光纤, 现在的wifi无线网使用电磁波等都属于物理层的概念
  2. 数据链路层:负责处理硬件层面的通信细节,包括如何在本地网络上发送和接收数据。它涵盖了物理层和数据链路层的功能,管理数据帧的传输、错误检测、以及链路控制。
  3. 网络层:主要负责数据包的路由和转发,确保数据能够在不同网络之间传输。这一层定义了IP地址的结构和数据包的传输路径。IP协议(IPv4和IPv6)是这一层的核心协议。
  4. 传输层:提供端到端的数据传输服务,确保数据的完整性和顺序性。传输层负责数据的分段、传输、重组,并根据需要提供错误检测和流量控制。TCP和UDP协议在这一层运行,分别提供可靠的连接型和高效的无连接型数据传输服务。
  5. 应用层:直接为用户提供网络服务,负责数据的格式化、加密以及应用程序之间的通信。这一层涵盖了OSI模型的应用层、表示层和会话层功能,是用户与网络交互的接口。

网络协议栈示意图:
在这里插入图片描述

3.网络通信流程

两主机通过TCP/IP通信过程所示
在这里插入图片描述
发送数据在不断的对数据加上报头,发送数据即封装的过程,接受数据是在不断的解包和分用的过程。分用:决定将自己的有效在和交付给上层的哪个协议的能力(就是分用)。传输层的叫数据段,网络层的叫叫数据报,数据链路层叫数据帧,通信的的过程本质就是不断封装和解包的过程,报文 = 报头 + 有效载荷
局域网通信原理是基于碰撞域和碰撞检测的,交换机会划分碰撞域,局域网可以看成多台主机所共享的临界资源,所以是要保证互斥的。

4.IP与Mac地址

在计算机网络中,IP地址和MAC地址是两种用于标识设备的重要地址类型。
IP地址
IP地址(是分配给网络中每个设备的逻辑地址,用于在网络层进行通信。IP地址有两种版本:IPv4和IPv6。

  • IPv4地址: 由32位二进制数构成,通常表示为四个十进制数(例如,192.168.1.1),每个数值代表8位(一个字节)。
  • IPv6地址: 由于IPv4地址枯竭问题,IPv6应运而生,它使用128位地址空间,表示为8组16进制数(例如,2001:0db8:85a3:0000:0000:8a2e:0370:7334)。

Mac地址
MAC地址网络接口卡(网卡)的唯一标识符,存在于数据链路层。每个网络设备出厂时都会分配一个唯一的MAC地址,通常由硬件制造商确定。MAC地址由48位二进制数构成,6字节
MAC地址用于在同一局域网(LAN)内的设备之间进行通信。因为它是硬件地址,所以在设备移动到不同网络时保持不变。交换机等网络设备利用MAC地址来学习和识别局域网中设备的位置,并相应地转发数据帧。

在网络通信中,当一个设备需要与另一个设备通信时,通常需要知道对方的IP地址。然而,数据帧(数据链路层)的实际传输依赖于MAC地址。这时,ARP(地址解析协议)发挥作用。

二.网络编程套接字

1.端口号

端口号为2字节16位的整数,范围为在0到65535之间,它与IP地址一起构成一个套接字(socket),唯一标识网络中的某个服务。用来标识唯一进程
那么端口号和进程pid有什么区别呢?,端口号是用来标识网络服务需要用到的进程,不是所有进程都要通信,但是所有进程都必须有pid标识自己。一个端口号可以被多个进程绑定,一个端口号不能绑定多个进程,在公网上:IP地址能表示唯一的一台主机,端口号port,用来标识该主机上的唯一的一个进程 ,所以用IP和端口号可以标识全网唯一的一个进程。

2.网络字节序

在计算机网络中,数据传输可能会在不同计算机系统上。由于不同系统可能使用不同的字节序),为了确保数据在网络中传输时的正确性,需要统一使用一种标准的字节序。所以诞生了网络字节序:TCP/IP协议规定网络数据流应该采用大端字节序。所以如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
接着主机序列转网络序列的库函数应运而生:

#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);

h代表主机序列转网络序列,n代表网络字节序列转主机序列,其中如果机器是小端字节序会做相应的转换。

3.tcp、udp协议

在Linux网络编程中,TCP(传输控制协议)和UDP(用户数据报协议)是两种最常用的传输层协议。
UDP协议(用户数据报协议)

  • 无连接:UDP是无连接的协议。数据传输之前不需要建立连接,因此可以更快地发送数据,但不保证数据的可靠性。
  • 不可靠传输:UDP不保证数据包的到达顺序,也不提供数据包的确认或重传机制。如果数据包丢失,UDP不会重发。
  • 面向数据报:在计算机网络中,**数据报(Datagram)**是指独立传输的、具有独立完整意义的数据单元。
  • 传输层协议

TCP协议(传输控制协议)

  • 有连接:TCP是一种面向连接的协议。在数据传输之前,客户端和服务器需要通过三次握手建立连接,确保双方准备好进行通信。
  • 面向字节流:TCP中,数据被看作是一个连续的字节流。发送方可以将任意数量的字节发送到接收方,而接收方会以流的形式接收到这些字节。这意味着发送方发送的每一段数据不会被当作一个独立的数据单元,TCP不会将数据拆分成数据报。接收方接收到的数据流是发送方数据流的一个完整视图。
  • 可靠传输:TCP提供可靠的数据传输,通过数据包的确认和重传机制来确保数据完整无误。数据包的顺序也会被正确维护。
  • 传输层协议

4.socket编程

socket是一种通信机制socket 是应用层与传输层之间的一个接口,通过它,应用程序可以利用底层网络协议来发送和接收数据。网络协议栈中的Socket通常被用来实现TCP/IP协议通信
socket编程系列函数:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int socket(int domain, int type, int protocol);

socket用来创建一个套接字:

  • domain指定协议族,如 AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地通信)。
  • type:指定Socket类型,如 SOCK_STREAM(流式套接字,TCP)、SOCK_DGRAM(数据报套接字,UDP)。
  • protocol:指定协议,一般设置为 0,让系统选择合适的协议。
  • 成功时返回一个Socket描述符(非负整数),失败时返回 -1 并设置 errno。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

bind函数在网络编程中用于将一个套接字(socket)绑定到一个特定的地址和端口struct sockaddr结构体)。

  • sockfd:socket函数返回的Socket描述符。
  • addr:指向struct sockaddr结构的指针,其中包含要绑定的地址信息。
  • addrlen:addr结构的大小。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int listen(int sockfd, int backlog);

listen 函数在网络编程中用于将一个已经绑定到地址和端口的套接字转换为监听状态,以便接受客户端的连接请求。这个函数主要用于服务器端

  • sockfd:socket函数返回的Socket描述符。
  • backlog:指定连接请求队列的最大长度。
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept 函数在网络编程中用于接受客户端的连接请求。它主要用于服务器端,在一个已经处于监听状态的套接字上调用,用于从等待队列中提取一个连接请求,并返回一个新的套接字描述符用于与客户端进行数据交换

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

函数在网络编程中用于发起与远程主机的连接。它主要用于客户端程序,试图连接到指定的服务器地址和端口

  • addr:说明:指向一个 struct sockaddr 结构体的指针,包含了远程主机的地址和端口信息。
#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

recvfrom 函数在网络编程中用于从一个套接字接收数据,它特别适用于面向数据报的协议(如 UDP),但也可以用于其他协议。

  • sockfd:由 socket 函数创建的套接字描述符。这个套接字应该是用来接收数据的,并且可以是任何支持数据接收的协议(如 UDP)。
  • buf:指向一个缓冲区的指针,用于存储接收到的数据。这个缓冲区应该足够大,以容纳预期的数据量。
  • len:缓冲区 buf 的大小(以字节为单位)。接收到的数据量不能超过这个大小
  • flags:接收操作的标志。通常使用 0,但也可以使用以下标志之一:MSG_OOB:接收带外数据(如果有)。MSG_PEEK:窥视数据,不从队列中移除。MSG_DONTWAIT:非阻塞模式(适用于非阻塞套接字)。
  • src_addr:指向一个 struct sockaddr 结构体的指针,用于存储数据源的地址信息。对于 IPv4 通信,通常使用 struct sockaddr_in;对于 IPv6 通信,通常使用 struct sockaddr_in6。这个参数可以为 NULL,如果不需要源地址信息。
  • addrlen:指向 socklen_t 类型变量的指针,表示 src_addr 结构体的大小。在调用前应设置为 src_addr 的大小;调用后,它将包含实际的地址长度。可以为 NULL,如果不需要源地址信息。

5.sockaddr结构解析

在网络编程中,sockaddr 结构体是用于表示网络地址的基础结构体。常见变体有sockaddr_in 和 sockaddr_un 的详细解析:
在这里插入图片描述

  • sockaddr 结构:是一个通用的地址结构体,用于表示网络地址。具体地址信息存储在其派生结构体中。
  • sockaddr_in:用于表示 IPv4 地址,包含地址族、端口号和 IPv4 地址。适用于使用 TCP/UDP 协议的通信。
  • sockaddr_un:用于表示UNIX 域套接字的本地通信地址,包含地址族和路径名。适用于本地进程间通信。
    套接字分为三种:域间套接字(同一机器内)、原始套接字(网络工具)、网络套接字(用户间网络通信)

6.实现Udp_socket

首先我们分别要实现客户端和服务器逻辑,让服务器接受客户端的数据,并且返回给客户端。
首先我们创建服务器类:

class UdpServer
{
private:int _socketid;//网络文件描述符string _ip;//字符串形式ip地址uint16_t _port;//服务器进程的端口号
};

构造函数:

UdpServer(uint16_t port = default_port, string ip = default_ip):_socketid(0),_ip(ip),_port(port){}

绑定ip缺省为0.0.0.0代表允许接收来自任何IP地址的连接请求
创建套接字和绑定sockaddr_in结构体:

void Init(){   //1.创建udp socket _socketid = socket(AF_INET,SOCK_DGRAM,0);if(_socketid <0){   exit(Socket_err);}//2.bind  //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);struct sockaddr_in _obj;bzero(&_obj,sizeof(_obj));_obj.sin_family = AF_INET;_obj.sin_port = htons(_port);_obj.sin_addr.s_addr = inet_addr(_ip.c_str());int ret = bind(_socketid,(struct sockaddr*)&_obj,sizeof(_obj));if(ret<0){exit(Bind_err);}}

其中AF_INET代表IPV4协议,SOCK_DGRAM面向数据报的套接字,htons用于将主机字节序列的端口号转为网络序列,inet_addr将char*类型转换成in_addr_t类型,bind将套接字与存储好协议和ip与端口号的struct sockaddr_in绑定起来。bzero将指定内存块清空,常用于使用之前初始化结构体
接着进行客户端数据收发逻辑:

void run(){char Buffer[1024];string temp;while(true){//ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,//struct sockaddr *src_addr, socklen_t *addrlen);struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t Re_ret = recvfrom(_socketid,Buffer,sizeof(Buffer)-1,0,//接收(struct sockaddr*)&client,&len);if(Re_ret<0){lg(Fatal,"bind create is error : %d",Re_ret);continue;}Buffer[Re_ret] = 0;//看成字符串temp = Buffer;cout<<temp<<endl;//ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,//const struct sockaddr *dest_addr, socklen_t addrlen);sendto(_socketid,temp.c_str(),temp.size(),0,(struct sockaddr*)&client,len);//发送}}

客户端逻辑:

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;void Usage(string proc)
{cout << "Usage: " << proc << " serverip serverport"<< endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(0);}string Ser_ip = argv[1];uint16_t Ser_proc = stoi(argv[2]);//1.创建struct sockaddr struct sockaddr_in Server;bzero(&Server,sizeof(Server));Server.sin_family=AF_INET;Server.sin_port = htons(Ser_proc);Server.sin_addr.s_addr= inet_addr(Ser_ip.c_str());int _sockeid = socket(AF_INET,SOCK_DGRAM,0);if(_sockeid <0){cout<<"Client is error "<<endl;exit(1);}char Buffer[1024];string message;socklen_t len = sizeof(Server);while(true){cout<<"Please enter :";getline(cin,message);sendto(_sockeid,message.c_str(),sizeof(message)-1,0,(struct sockaddr*)&Server,len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(_sockeid, Buffer, sizeof(Buffer)-1, 0, (struct sockaddr*)&temp, &len);//接受&if(s > 0){Buffer[s] = 0;cout << Buffer << endl;}}close(_sockeid);return 0;
}

客户端也是需要bind的但是是由OS在客户端发送数据的时候自动做。不需要显示调用bind首先客户端想要与服务器通信,要知道服务器的ip和端口号,这里用到了可变参数,让用户的运行的时候告诉进程服务器的ip和端口号。之后的逻辑乏善可陈,即是发送和接受数据。
我们再设置服务器端口号时,要知道0到1023:为系统内定的端口号,一般都要有固定的。
在这里插入图片描述
如此以来我们就封装出了udpsocket实现简单的cs通信。

7.实现Windows与Linux通信

将Linux机器作为服务器,代码逻辑与上相同:

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "Log.hpp"using namespace std;
Log lg;enum{SOCKET_ERR=1,BIND_ERR
};uint16_t defaultport = 8080;
string defaultip = "0.0.0.0";
const int size = 1024;class UdpServer{
public:UdpServer(const uint16_t &port = defaultport, const string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false){}void Init(){sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INETif(sockfd_ < 0){lg(Fatal, "socket create error, sockfd: %d", sockfd_);exit(SOCKET_ERR);}lg(Info, "socket create success, sockfd: %d", sockfd_);struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_); //需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_addr.s_addr = inet_addr(ip_.c_str()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??// local.sin_addr.s_addr = htonl(INADDR_ANY);if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}void Run() // 对代码进行分层{isrunning_ = true;char inbuffer[size];string temp;while(isrunning_){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = 0;//看成字符串temp = inbuffer;cout<<temp<<endl;//ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,//const struct sockaddr *dest_addr, socklen_t addrlen);sendto(sockfd_,temp.c_str(),temp.size(),0,(struct sockaddr*)&client,len);//发送}}~UdpServer(){if(sockfd_>0) close(sockfd_);}
private:int sockfd_;     // 网路文件描述符string ip_; // 任意地址bind 0uint16_t port_;  // 表明服务器进程的端口号bool isrunning_;};

Windows下vs2022代码:

#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>#include<iostream>
#include<string>#pragma comment(lib,"ws2_32.lib") //引入库文件int main()
{//初始化网络环境WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){printf("WSAStartup failed\n");return -1;}//建立一个udp的socketSOCKET socked = socket(AF_INET, SOCK_DGRAM, 0);if (socked == INVALID_SOCKET){printf("create socket failed\n");return -1;}int port = 8080;std::string ip = "";//服务器ip地址//创建结构体sockaddr_in addr = { 0 };addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());std::string info;char buffer[1024];memset(buffer, 0, sizeof(buffer));//收发数据while (true) {std::cout << "Please enter:";std::getline(std::cin, info);//发送数据int n = sendto(socked, info.c_str(), info.size(), 0, (SOCKADDR*)&addr, sizeof(SOCKADDR));if (n == 0){printf("send failed\n");return -1;}sockaddr_in t = { 0 };int len = sizeof(sockaddr_in);// 接收数据n = recvfrom(socked, buffer, sizeof(buffer) - 1, 0, (SOCKADDR*)&t, &len);buffer[n] = 0;std::cout << buffer << std::endl;memset(buffer, 0, sizeof(buffer));}//关闭SOCKET连接closesocket(socked);//清理网络环境WSACleanup();return 0;
}

在这里插入图片描述
即可完成通信

8.Linux下远程执行指令

popen 函数允许程序通过一个文件流与外部命令进行交互:

FILE *popen(const char *command, const char *type);
  • command:一个指向字符串的指针,表示要执行的命令。这可以是任何可以在命令行执行的命令,包括路径、可执行文件名以及命令的参数。
  • type:一个指向字符串的指针,用于指定文件流的类型,可以是 “r” 或 “w”:“r”:表示读取(即从命令的标准输出读取数据)。“w”:表示写入(即将数据写入到命令的标准输入)。
  • 成功时,返回一个指向 FILE 的指针,该指针可以用于读取或写入外部命令的输入输出。

也就是客户端向服务器发送数据后,加一层处理,将数据转换成指令并将结果读取并打印出来:

string ExcuteCommand(const string &cmd)
{cout << "get a request cmd: " << cmd << endl;FILE *fp = popen(cmd.c_str(), "r");if(nullptr == fp){perror("popen");return "error";}string ret;char buffer[4096];while(true){char *tmp = fgets(buffer, sizeof(buffer), fp);if(tmp == nullptr) break;ret += buffer;}pclose(fp);return ret;
}

在这里插入图片描述
当然用Windows也是可以的:
在这里插入图片描述
其实我们的xshell原理就类似
在这里插入图片描述
我们是客户端访问远端服务器后输入数据被解析成指令,服务器再将结果返回给我们。

9.实现tcp_socket

系列函数解析:
listen 函数在网络编程中用于将一个套接字从“主动”状态切换到“监听”状态,,通常在调用 socket 函数创建套接字并用 bind 绑定到地址和端口之后使用。

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int listen(int sockfd, int backlog);
  • backlog:指定套接字的最大连接队列长度,即在服务器处理新连接之前,内核中允许排队的未决连接数。这个数值决定了在连接请求被接受之前,内核可以缓存多少个连接请求。

accept 函数是网络编程中的一个重要函数,用于从已经处于监听状态的套接字中接受一个连接请求,并返回一个新的套接字描述符,该描述符用于与客户端进行通信

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • 成功时,返回一个新的套接字描述符,该描述符用于与客户端进行通信。
  • 三次握手完成后, 服务器调用accept()接受连接;
  • 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

connect 函数用于将一个套接字连接到一个远程主机上的指定地址。用于客户端连接服务器,连接成功后,客户端可以通过该套接字与服务器进行数据交换。
tcp通信也是全双工的,因为他的发送缓冲区和接收缓冲区是分开的,所以接收和发送是可以同时进行的。
我们先来实现简单的tcpsocket的cs通信:
服务器代码逻辑:

#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
//#include "Task.hpp"
#include "Daemon.hpp"
using namespace std;const string defaultip = "0.0.0.0";
const int backlog = 5; 
extern Log lg;enum
{UsageError = 1,SocketError,BindError,ListenError,
};class TcpServer
{
public:TcpServer(const uint16_t &port, const string &ip = defaultip):  _port(port), _ip(ip){}void InitServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in _obj;bzero(&_obj, sizeof(_obj));_obj.sin_family = AF_INET;_obj.sin_port = htons(_port);_obj.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(_obj);if (bind(_listensock, (struct sockaddr *)&_obj, len) < 0){lg(Fatal, "Server bind is error %s", strerror(errno));exit(BindError);}lg(Info, "Server bind is succes , socketfd is %d ,port :%d", _listensock, _port);// 监听if (listen(_listensock, backlog) < 0){lg(Fatal, "Server listen is error %s", strerror(errno));exit(ListenError);}lg(Info, "Server listen is succes ");}void _test(int _sockefd, uint16_t &port, string &clientip){char buffer[4096];while (true){ssize_t n = read(_sockefd, buffer, sizeof(buffer) - 1); // 留一个位置给 '\0'if (n > 0){buffer[n] = 0; // 添加终止符,防止溢出cout << "client say# " << buffer << endl;string echo_string = "tcpserver echo# ";echo_string += buffer;write(_sockefd, echo_string.c_str(), echo_string.size());}else if (n == 0){lg(Info, "%s:%d quit, server close _sockefd: %d", clientip.c_str(), port, _sockefd);break;}else{lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", _sockefd,clientip.c_str(), port);break;}}}void Start(){lg(Info, "tcpServer is running....");for (;;){struct sockaddr_in client;socklen_t len = sizeof(client);int _sockefd = accept(_listensock, (struct sockaddr *)&client, &len);if (_sockefd < 0){lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno));continue;}char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));uint16_t clientport = ntohs(client.sin_port);lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", _sockefd, clientip);_test(_sockefd, client.sin_port, _ip);close(_sockefd);}}~TcpServer() {}private:int _listensock;uint16_t _port;string _ip;
};

客户端代码逻辑:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
using namespace std;
Log lg;void Usage(const string &proc)
{cout << "\n\rUsage: " << proc << " serverip serverport\n"<< endl;
}// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = 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 sockfd = 0;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){cerr << "socket error" << endl;return 1;}int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){lg(Fatal, "Client connect is error %s", strerror(errno));}while (true){string message;cout << "Please Enter# ";getline(cin, message);int n = write(sockfd, message.c_str(), message.size());if (n < 0){cerr << "write error..." << endl;}char inbuffer[4096];n = read(sockfd, inbuffer, sizeof(inbuffer));if (n > 0){inbuffer[n] = 0;cout << inbuffer << endl;}}close(sockfd);}return 0;
}

运行起来既实现
在这里插入图片描述

可是当前的代码是单进程的,如果有两台主机同时向服务器发送数据,服务器只能处理先连接上的,那我们服务器能同时处理多个客户端的数据该怎么办呢?
我们可以将代码逻辑改成多进程版本的:
核心代码:

pid_t id=fork();
if(id==0)
{close(_listensock);if(fork()>0)exit(0);_test(_sockefd, client.sin_port, _ip);close(_sockefd);exit(0);
}
close(_sockefd);
pid_t w_id=waitpid(id,nullptr,0);

这时就完成了多进程的代码逻辑。
在这里插入图片描述
其中:

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

目的是在子进程中创建孙子进程让孙子进程执行_test代码,这样在执行_test的时候,子进程已经被父进程回收,所以可以继续创建子进程完成逻辑,实现多主机可以同时访问服务器的功能。使用多线程的话也是类似的逻辑,但是创建线程的成本比进程低的多,这里就不演示了。
接下来我们编写一个基于线程池处理汉译英功能的服务器。首先直接将我们之前编写的单例模式的线程池拿过来:

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>struct ThreadInfo
{pthread_t tid;std::string name;
};static const int defalutnum = 10;template <class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&mutex_);}void Unlock(){pthread_mutex_unlock(&mutex_);}void Wakeup(){pthread_cond_signal(&cond_);}void ThreadSleep(){pthread_cond_wait(&cond_, &mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto &ti : threads_){if (ti.tid == tid)return ti.name;}return "None";}public:static void *HandlerTask(void *args){ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);std::string name = tp->GetThreadName(pthread_self());while (true){tp->Lock();while (tp->IsQueueEmpty()){tp->ThreadSleep();}T t = tp->Pop();tp->Unlock();t();}}void Start(){int num = threads_.size();for (int i = 0; i < num; i++){threads_[i].name = "thread-" + std::to_string(i + 1);pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t = tasks_.front();tasks_.pop();return t;}void Push(const T &t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPool<T> *GetInstance(){if (nullptr == tp_) // ???{pthread_mutex_lock(&lock_);if (nullptr == tp_){std::cout << "log: singleton create done first!" << std::endl;tp_ = new ThreadPool<T>();}pthread_mutex_unlock(&lock_);}return tp_;}private:ThreadPool(int num = defalutnum) : threads_(num){pthread_mutex_init(&mutex_, nullptr);pthread_cond_init(&cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&cond_);}ThreadPool(const ThreadPool<T> &) = delete;const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:std::vector<ThreadInfo> threads_;std::queue<T> tasks_;pthread_mutex_t mutex_;pthread_cond_t 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 <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <signal.h>
#include "Log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "Daemon.hpp"
using namespace std;const string defaultip = "0.0.0.0";
const int backlog = 6; 
extern Log lg;enum
{UsageError = 1,SocketError,BindError,ListenError,
};class TcpServer;class ThreadData
{
public:ThreadData(int fd, const string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t){}
public:int sockfd;string clientip;uint16_t clientport;TcpServer *tsvr;
};class TcpServer
{
public:TcpServer(const uint16_t &port, const string &ip = defaultip):  _port(port), _ip(ip){}void InitServer(){_listensock = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in _obj;bzero(&_obj, sizeof(_obj));_obj.sin_family = AF_INET;_obj.sin_port = htons(_port);_obj.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(_obj);if (bind(_listensock, (struct sockaddr *)&_obj, len) < 0){lg(Fatal, "Server bind is error %s", strerror(errno));exit(BindError);}lg(Info, "Server bind is succes , socketfd is %d ,port :%d", _listensock, _port);// 监听if (listen(_listensock, backlog) < 0){lg(Fatal, "Server listen is error %s", strerror(errno));exit(ListenError);}lg(Info, "Server listen is succes ");void Start(){ThreadPool<Task>::GetInstance()->Start();lg(Info, "tcpServer is running....");for (;;){struct sockaddr_in client;socklen_t len = sizeof(client);int _sockefd = accept(_listensock, (struct sockaddr *)&client, &len);if (_sockefd < 0){lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno));continue;}char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));uint16_t clientport = ntohs(client.sin_port);lg(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", _sockefd, clientip);Task t(_sockefd, clientip, clientport);ThreadPool<Task>::GetInstance()->Push(t);}}~TcpServer() {}private:int _listensock;uint16_t _port;string _ip;
};

客户端代码逻辑:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
using namespace std;
Log lg;void Usage(const string &proc)
{cout << "\n\rUsage: " << proc << " serverip serverport\n"<< endl;
}// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}string serverip = argv[1];uint16_t serverport = 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 sockfd = 0;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){cerr << "socket error" << endl;return 1;}//  客户端发起connect的时候,进行自动随机bindint n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));if (n < 0){lg(Fatal, "Client connect is error %s", strerror(errno));return 1;}while (true){string message;cout << "Please Enter# ";getline(cin, message);int n = write(sockfd, message.c_str(), message.size());if (n < 0){cerr << "write error..." << endl;break;}else if (n == 0) {break;}char inbuffer[4096];n = read(sockfd, inbuffer, sizeof(inbuffer));if (n > 0){inbuffer[n] = 0;cout << inbuffer << endl;}else if(n == 0) {break;}}close(sockfd);}return 0;
}

英译汉搜索逻辑:

#pragma once#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include "Log.hpp"
using namespace std;
Log lg;const string dictname = "./dict.txt";
const string sep = ":";//yellow:黄色...
static bool Split(string &s, string *part1, string *part2)
{auto pos = s.find(sep);if(pos == string::npos) return false;*part1 = s.substr(0, pos);*part2 = s.substr(pos+1);return true;
}class Init
{
public:Init(){ifstream in(dictname);if(!in.is_open()){lg(Fatal, "ifstream open %s error", dictname.c_str());exit(1);}string line;while(getline(in, line)){string part1, part2;Split(line, &part1, &part2);dict.insert({part1, part2});}in.close();}string translation(const string &key){auto iter = dict.find(key);if(iter == dict.end()) return "Unknow";else return iter->second;}
private:unordered_map<string, string> dict;
};

任务代码逻辑:

#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Init.hpp"extern Log lg;
Init init;class Task
{
public:Task(int sockfd, const std::string &clientip, const uint16_t &clientport): sockfd_(sockfd), clientip_(clientip), clientport_(clientport){}Task(){}void run(){char buffer[4096];while(1) {ssize_t n = read(sockfd_, buffer, sizeof(buffer)); if (n > 0){buffer[n] = 0;std::cout << "client key# " << buffer << std::endl;std::string echo_string = init.translation(buffer);n = write(sockfd_, echo_string.c_str(), echo_string.size()); if(n < 0){lg(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));}}else if (n == 0){lg(Info, "%s:%d quit, server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);break;}else{lg(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd_, clientip_.c_str(), clientport_);break;}}close(sockfd_);}~Task(){}private:int sockfd_;std::string clientip_;uint16_t clientport_;
};

在这里插入图片描述

10.守护进程

我们之前学习过前台进程和后台进程,在命令行中前台进程是必须要一直存在的,能接受键盘信号的就是前台进程。所以说谁拥有键盘文件谁就是前台进程,一个会话可以有多个后台进程但只能有一个前台进程。&符号就是用来将程序设置到后台运行的:

在这里插入图片描述
jobs指令可以查看后台任务:
在这里插入图片描述
fg加任务号可以将后台任务提到前台:
在这里插入图片描述
ctrl Z可将将其暂停并放回后台:
在这里插入图片描述

bg加任务号可将后台任务在跑起来:
在这里插入图片描述

在这里插入图片描述
每次登录xshell就会建立一个会话,所以有会话id的概念,TTY为控制终端,一个进程组只有一个进程的pid和pgid相同,组长就是进程组的第一个。
在这里插入图片描述
在这里插入图片描述
我们可以观察到终端是 ?这表明它没有关联到任何终端。
守护进程的父进程 ID(PPID)是 1,这表明它们可能是由 init启动的。守护进程的会话 ID 通常与其进程组 ID 相同,表示它们属于同一会话
函数setsid

#include <unistd.h>pid_t setsid(void);

setsid 函数是用于创建新的会话的系统调用。它的主要作用是将当前进程创建为新的会话的领导者,从而实现与当前会话的断开,通常用于守护进程的创建。也就是说守护进程是自成会话的进程,需要注意的是setsid的调用进程不能是进程组组长,所以我们需要fork子进程执行这个函数。守护进程也是孤儿进程
代码逻辑实现:

#pragma once#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const std::string nullfile = "/dev/null";void Daemon(const std::string &cwd = "")
{// 1. 忽略其他异常信号signal(SIGCLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);signal(SIGSTOP, SIG_IGN);// 2. 将自己变成独立的会话if (fork() > 0)exit(0);setsid();// 3. 更改当前调用进程的工作目录if (!cwd.empty())chdir(cwd.c_str());// 4. 标准输入,标准输出,标准错误重定向至/dev/nullint fd = open(nullfile.c_str(), O_RDWR);if(fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}
}

逻辑解析:

  • 定义了一个常量 nullfile,指定了 /dev/null,任何写入到它的数据都会被丢弃,读取则返回 EOF。
  • 忽略异常信号这些信号处理设置确保了守护进程不会因为这些信号而被意外终止。
  • fork():创建一个子进程。父进程退出,子进程继续执行。这让子进程脱离原有的会话。
  • setsid():创建一个新的会话,并将调用进程设置为该会话的会话组长。这样,进程就不再与终端相关联,可以在后台运行。
  • chdir(cwd.c_str());:如果提供了工作目录路径,则更改进程的工作目录。通常,守护进程会将工作目录更改为根目录。
  • open(nullfile.c_str(), O_RDWR);:打开 /dev/null 设备文件。dup2(fd, 0);、dup2(fd, 1);、dup2(fd, 2);:将标准输入(0)、标准输出(1)、标准错误(2)重定向到 /dev/null。这样,守护进程的输入输出不会干扰终端,也不会产生任何不必要的输出。

相关文章:

Linux 网络套接字解析:实现网络通信

目录 一.网络基础1.协议2.OSI与TCP/IP模型3.网络通信流程4.IP与Mac地址 二.网络编程套接字1.端口号2.网络字节序3.tcp、udp协议4.socket编程5.sockaddr结构解析6.实现Udp_socket7.实现Windows与Linux通信8.Linux下远程执行指令9.实现tcp_socket10.守护进程 一.网络基础 1.协议…...

vue3 组合式API

<!-- 深度监听 deep 点击按钮控制台&#xff0c;才输出count变化了: 1, 老值: 0;否则控制台不输出 --> <script setup>import { ref,watch } from vueconst state ref({count:0})const setCount () > {state.count.value}watch(state, () > {console.log(…...

二、什么是Vue中的响应式?Vue的响应式原理

什么是Vue中的响应式 Vue中的响应式&#xff0c;简而言之就是当数据发生变化时&#xff0c;页面跟随变化。使用过Vue的v-model都有比较深刻的感受&#xff0c;我们在代码中修改双向绑定的数据后&#xff0c;页面上的数据也会自动更新&#xff0c;页面跟随变化 我们看个例子&am…...

快9月了才开始强化,跟张宇还是武忠祥?

快9月了才开始强化&#xff0c;跟张宇还是武忠祥&#xff01; 说真的&#xff0c;我也替这位同学着急&#xff0c;但是考研数学越是进度慢&#xff0c;就越不能急&#xff01;急着赶进度&#xff0c;容易出事&#xff01;遇到这个问题的朋友肯定不止一位&#xff0c;那我就帮大…...

SSM好易学学习平台---附源码92142

摘 要 随着互联网趋势的到来&#xff0c;各行各业都在考虑利用互联网将自己推广出去&#xff0c;最好方式就是建立自己的互联网系统&#xff0c;并对其进行维护和管理。在现实运用中&#xff0c;应用软件的工作规则和开发步骤&#xff0c;采用Java技术建设好易学学习平台。本文…...

对于mp4 ios和mac safari不能播放问题处理

直接对原mp4文件进行重新转码就可以了 ffmpeg -i origin.mp4 -vcodec h264 -profile:v high -level 4.1 orgin_hl.mp4 原因源文件不符合苹果基本规则 苹果官网文档...

开发同城交友找搭子系统app前景分析

开发同城交友系统APP的背景 社交需求多样化&#xff1a; 随着城市化的加速和人们生活节奏的加快&#xff0c;现代人的社交圈子往往较为狭窄&#xff0c;难以结识新朋友。传统的线下交友方式受限于时间、地点等因素&#xff0c;难以满足现代人对于交友的多样化需求。互联网和智…...

faiss向量数据库测试《三体》全集,这家国产AI加速卡,把性能提了7倍!

在人工智能和机器学习技术的飞速发展中&#xff0c;向量数据库在处理高维数据方面扮演着日益重要的角色。近年来&#xff0c;随着大型模型的流行&#xff0c;向量数据库技术也得到了进一步的发展和完善。 向量数据库为大型模型提供了一个高效的数据管理和检索平台&#xff0c;…...

负载均衡---相关概念介绍(一)

负载均衡&#xff08;Load Balance&#xff09;是集群技术的一种重要应用&#xff0c;旨在将负载&#xff08;工作任务&#xff09;进行平衡、分摊到多个操作单元上进行运行&#xff0c;从而提高系统的并发处理能力、增加吞吐量、加强网络处理能力&#xff0c;并提供故障转移以…...

计算机基础知识复习8.14

子线程抛异常主线程能否catch 在不做任何处理的情况下&#xff0c;主线程不能catch 解决方式&#xff1a; 子线程使用try catch来捕获异常 为线程设置未捕获异常处理器UncaughtExceptionHandler 通过future的get方法捕获异常 JVM相关参数 显示指定堆内存-Xms和-Xmx指定最…...

【io深层理解】

io深层理解 1.内核态2.用户态3. select IO多路复用执行原理4. select io多路复用限制和不足 1.内核态 一个进程会涉及多文件的修改&#xff0c;比如说。那么在内核态就会维护一个表&#xff0c;这个表叫文件描述符bitmap&#xff0c;这个表会传递给内核态&#xff0c;当然肯定传…...

【懒人工具】指定新文件,替换全盘旧文件

没辙&#xff0c;就是懒 最近在调整.clang-format&#xff0c;这个format文件要跟着项目走&#xff0c;只换本地默认的还不够。调整好以后一个项目一个项目的换&#xff0c;有时候会漏掉&#xff0c;索性全盘一次性换完。 基于自己操作的流程&#xff0c;写了个脚本&#xff0…...

React+Vis.js(02):设置节点样式

文章目录 1、修改vis.js的节点和关系颜色2、修改vis.js节点的字体颜色2.1 统一设置节点字体颜色2.2 自定义某个节点的字体颜色3、设置vis.js节点的边框颜色和宽度3.1 设置单个节点3.2 统一设置1、修改vis.js的节点和关系颜色 在vis.js中,可以通过color属性,来给node节点添加…...

3G网络要彻底没了

2月21日,三大运营商公布了最新的用户数据,移动联通电信三家5G套餐用户数合计超过了7.5亿。信通院早前公布的数据显示,一月份,国内市场5G手机出货量2632.4万部,占同期手机出货量的79.7%。 这两项数据,说明我们已经进入到了5G时代,5G的普及速度远超很多人的想象。就在5G逐…...

如何配置ESXI主机的IP地址管理

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…...

软件测试学习笔记丨测试用例设计方法

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/31921 一&#xff0c;黑盒测试方法论 1&#xff0c;等价类 1.1 定义 等价类划分是一种重要的、常用的黑盒测试方法不需要考虑程序的内部结构&#xff0c;只需要考虑程序的输入规格即可它将…...

MinIO基本用法

在现代云计算和大数据领域&#xff0c;对象存储因其可扩展性、可靠性和低成本成为数据存储的重要选择。MinIO作为一个高性能、分布式的对象存储系统&#xff0c;凭借其开源、简单易用以及与Amazon S3兼容的特性&#xff0c;在业界得到了广泛的应用。本文将带您了解MinIO的基本用…...

MySQL windows版本安装

一、下载MySQL安装包 访问MySQL官网&#xff1a;首先&#xff0c;访问MySQL的官方网站&#xff08;MySQL&#xff09;&#xff0c;或者更具体地&#xff0c;访问MySQL的下载页面&#xff08;MySQL :: Download MySQL Community Server&#xff09;。 选择适合的版本&#xff1…...

Python实现人脸轮廓提取

目录 一、背景知识1.1 人脸检测和轮廓提取的意义1.2 人脸检测方法概述1.3 轮廓提取方法概述二、常用的人脸轮廓提取方法2.1 基于边缘检测的轮廓提取2.2 基于形态学操作的轮廓提取2.3 基于特征点检测的轮廓提取三、Python实现人脸轮廓提取3.1 安装依赖库3.2 使用Dlib进行人脸检测…...

Prettier+Vscode setting提高前端开发效率

文章目录 前言Prettier第一步&#xff1a;下载依赖&#xff08;团队合作&#xff09;或下载插件&#xff08;独立开发&#xff09;第二步&#xff1a;添加.prettierrc.json文件**以下是我使用的****配置规则** 第三步&#xff1a;添加.prettierignore文件**以下是我常用的****配…...

YOLOv10实时端到端目标检测

文章目录 前言一、非极值大抑制(NMS)二、NMS算法的具体原理和步骤三、YOLOV10创新点四、YOLOv10使用教程五、官方github地址 前言 距离上次写YOLOv5已经过去了两年&#xff0c;正好最近用YOLOv10重构了项目&#xff0c;总结下YOLOv10。 YOLOv10真正实时端到端目标检测&#xff…...

Java中的Annotation注解

常用注解 override&#xff1a;重写方法deprecated&#xff1a;弃用SuppressWarnings&#xff1a;抑制编译器警告 元注解&#xff08;注解的注解&#xff09; Target&#xff1a;描述注解所能修饰的类型Retention&#xff1a;描述注解的生命周期&#xff08;SOURCE源代码、C…...

小五金加工:细节决定产品质量与性能

在小五金加工领域&#xff0c;细节往往决定着最终产品的质量、性能以及市场竞争力。看似微不足道的细微之处&#xff0c;实际上蕴含着巨大的影响。时利和将介绍小五金加工中细节的重要性。 首先&#xff0c;细节关乎产品的精度。小五金零件通常尺寸较小&#xff0c;但对精度的要…...

VS Code安装配置ssh服务结合内网穿透远程连接本地服务器详细步骤

文章目录 前言1. 安装OpenSSH2.VS Code配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…...

世界首位「AI科学家」问世!独立生成10篇学术论文! 横扫「顶会」?

大家好&#xff0c;我是 Bob! &#x1f60a; 一个想和大家慢慢变富的 AI 程序员&#x1f4b8; 分享 AI 前沿技术、项目经验、面试技巧! 欢迎关注我&#xff0c;一起探索&#xff0c;一起破圈&#xff01;&#x1f4aa; AI科学家出世 最近一位人工智能AI科学家横空出世。 它是…...

【高阶数据结构】图

图 1. 图的基本概念2. 图的存储结构2.1 邻接矩阵2.2 邻接表2.3 邻接矩阵的实现2.4 邻接表的实现 3. 图的遍历3.1 图的广度优先遍历3.2 图的深度优先遍历 4. 最小生成树4.1 Kruskal算法4.2 Prim算法 5. 最短路径5.1 单源最短路径--Dijkstra算法5.2 单源最短路径--Bellman-Ford算…...

调研-音视频

音视频 基础概念主要内容音频基础概念音频量化过程音频压缩技术视频基础概念视频bug视频编码H264视频像素格式YUVRGB参考文献基础概念 ● 实时音视频应用环节 ○ 采集、编码、前后处理、传输、解码、缓冲、渲染等很多环节。 主要内容 音频 基础概念 三要素:音调(音频)、…...

【数据结构】链式结构实现:二叉树

二叉树 一.快速创建一颗二叉树二.二叉树的遍历1.前序、中序、后序遍历&#xff08;深度优先遍历DFS&#xff09;2.层序遍历&#xff08;广度优先遍历BFS&#xff09; 三.二叉树节点的个数四.二叉树叶子节点的个数五.二叉树的高度六.二叉树第k层节点个数七.二叉树查找值为x的节点…...

20221元组

在Python语言中, (7)是一种可变的、有序的序列结构,其中元素可以重复。 A.元组(tuple) B. 字符串(str) C. 列表(list) D.集合(set) ChatGPT 说&#xff1a; ChatGPT 在Python中&#xff0c;选项 C 列表(list) 符合题目描述。 解释&#xff1a; 列表 (list) 是一种可变的、有…...

艾瑞白皮书解读(三)丨剖析制造业、工程设计、创投数据治理痛点与典型方案

2024年7月 艾瑞咨询公司对国内数据治理行业进行了研究&#xff0c;访问了国内多位大中型企业数据治理相关负责人&#xff0c;深度剖析中国企业在数字化转型过程中面临到的核心数据问题后&#xff0c;重磅发布《2024中国企业数据治理白皮书》&#xff08;以下简称“白皮书”&…...

如何在 Odoo 16 Studio 模块中自定义视图和报告

为了有效地运营公司&#xff0c;需要定制的软件系统。Odoo 平台提供针对单个应用程序量身定制的管理解决方案和用户友好的界面&#xff0c;以便开发应用程序&#xff0c;而无需更复杂的后端功能。该平台支持使用简单的拖放功能和内置工具创建和修改更多定制的 Odoo 应用程序。企…...

Redis的十大数据类型的常用命令(上)

目录 1.key的操作命令2.String的常用命令案例一&#xff1a;dy点赞案例二&#xff1a;文章的喜欢数 3. List的常用命令案例&#xff1a;公众号订阅的消息 4. Hash的常用命令案例&#xff1a;早期购物车设计 5. Set的常用命令案例一&#xff1a;抽奖小程序案例二&#xff1a;朋友…...

智慧服务管理平台小程序开发方案

智慧服务管理平台小程序系统为用户提供一站式、个性化的服务管理解决方案&#xff0c;帮助用户优化服务流程、提升服务效率、增强客户满意度。适用于智慧校园、食堂、养老、智慧停车、智慧园区、智慧医院、智慧农业、康养、智慧社区、智慧农场等行业场景。一、目标用户 企业客户…...

【轻松拿捏】Java中ArrayList 和 LinkedList 的区别是什么?

ArrayList 和 LinkedList 的区别是什么&#xff1f; 1. ArrayList 2. LinkedList 3.总结 &#x1f388;边走、边悟&#x1f388;迟早会好 ArrayList 和 LinkedList 都是 Java 中常用的 List 接口的实现类&#xff0c;但它们在内部结构和操作性能上有所不同。 1. ArrayLis…...

【排序篇】快速排序的非递归实现与归并排序的实现

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 文章目录 1 快速排序非递归2. 归并排序3.排序算法复杂度及稳定性分析 1 快速排序非递归 利…...

Java垃圾收集器工作原理

在Java编程中&#xff0c;对象的内存分配主要发生在堆&#xff08;Heap&#xff09;上。堆是Java虚拟机&#xff08;JVM&#xff09;中的一块运行时数据区&#xff0c;用于存放由new关键字创建的对象和数组。与栈&#xff08;Stack&#xff09;内存分配相比&#xff0c;堆内存分…...

STM32CubeMX stm32不限长度使用DMA收发串口数据

STM32CubeMX 配置 代码 stm32h7xx_it.c /*** brief This function handles UART7 global interrupt.*/ void UART7_IRQHandler(void) {/* USER CODE BEGIN UART7_IRQn 0 */if (UART7 huart7.Instance) // 判断是否是空闲中断{if (__HAL_UART_GET_FLAG(&huart7, UART_FLA…...

Jmeter系列之作用域、执行顺序

这一节主要解释元件作用域和执行顺序&#xff0c;以及整理之前说过的参数化的方式。 作用域 之前也留下了一个问题。怎么给不同的请求设置不同的Header&#xff1f;后续也透露了可以使用Sample Controller&#xff0c;结合元件的作用域来实现 在Jmeter中&#xff0c;元件的作…...

舜宇光学科技社招校招入职测评:商业推理测验真题汇总、答题要求、高分技巧

舜宇光学科技&#xff08;集团&#xff09;有限公司&#xff0c;成立于1984年&#xff0c;是全球领先的综合光学零件及产品制造商。2007年在香港联交所主板上市&#xff0c;股票代码2382.HK。公司专注于光学产品的设计、研发、生产及销售&#xff0c;产品广泛应用于手机、汽车、…...

C语言——构造(结构体)

指针——内存操作 我们对于内存的操作借助于 <string.h>这个库提供的内存操作函数。 内存填充 头文件: #include<string.h> 函数原型: void*memset(void *s,int c,size_t n); 函数功能&#xff1a; 填充s开始的堆内存空间前n个字节&#xff0c;使得每个字节值为c…...

京东2025届秋招 算法开发工程师 第2批笔试

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/17 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;2h 本试卷还有选择题部分&#xff0c;但这部分比较简单就不再展示。 1. 第一题 村子里有一些桩子&#xff0c;从左到右高度依次为 1 , 1 2…...

模具监视器的技术参数有哪些

模具监视器的技术参数涵盖了多个方面&#xff0c;这些参数对于确保模具监视器的性能、稳定性和检测精度至关重要。以下是一些主要的技术参数&#xff1a; 一、显示器参数 屏幕尺寸&#xff1a;常见的模具监视器显示器尺寸为12.5英寸至13.5英寸&#xff0c;具体尺寸可能因不同…...

使用QGIS配置管线流向地图

一、需求概述 在管网项目中,需要进行地图配置使用QGIS显示管网的流向。 二、目标 配置一副管网地图,可以在地图上显示出每个管段的流向。 三、数据结构 管网数据: id[管线编码]source[起始节点ID]target[终点节点ID]dir[方向]1100101FT2101102FT……………………节点数据…...

白骑士的C#教学附加篇 5.1 C#开发工具

系列目录 上一篇&#xff1a;白骑士的C#教学实战项目篇 4.4 游戏开发 在这一部分&#xff0c;我们将介绍一些额外的内容和工具&#xff0c;以帮助您提高 C# 开发的效率和质量。掌握合适的开发工具和调试技巧&#xff0c;可以让您在编写和维护代码时更加高效和从容。 开发工具对…...

C++中的多线程编程和锁机制

二、多线程、锁 2.1 C语言线程库pthread&#xff08;POSIX threads&#xff09; 2.2.1 线程创建 pthread_create #include <pthread.h>pthread_t thread; ThreadData args {1, "Hello from parameterized thread"}; int result pthread_create(&threa…...

【投融界-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…...

自动打电话软件给企业带来了什么?

使用机器人外呼系统肯定都是想要给自己企业带来好处和解决问题的&#xff0c;想让自己的企业有所改变&#xff0c;有更好的发展&#xff0c;所以才会选择使用机器人外呼系统。而它也确实没让大家失望&#xff0c;使用了机器人外呼系统之后确实有许多企业发生了很大改变和进步&a…...

聚鼎科技:新手做装饰画生意卖什么比较好

在艺术的广阔天地里&#xff0c;装饰画以其独特的魅力逐渐成为室内装饰不可或缺的元素。对于刚入行的新手而言&#xff0c;选择合适的装饰画产品至关重要&#xff0c;它关系到业务的成功与否。以下是一些关于新手做装饰画生意卖什么比较好的建议。 考虑到市场需求的多样性&…...

从零开始搭建k8s集群详细步骤

声明&#xff1a;本文仅作为个人记录学习k8s过程的笔记。 节点规划&#xff1a; 两台节点为阿里云ECS云服务器&#xff0c;操作系统为centos7.9&#xff0c;master为2v4GB,node为2v2GB,硬盘空间均为40GB。&#xff08;节点基础配置不低于2V2GB&#xff09; 主机名节点ip角色部…...

大模型智能体可以用来实现哪些需求?

大模型智能体可以用来实现广泛的需求&#xff0c;以下是一些常见的应用场景&#xff1a; 自然语言处理&#xff08;NLP&#xff09;应用 文本生成&#xff1a;自动撰写文章、编写代码、生成新闻摘要。 对话系统&#xff1a;智能客服、虚拟助手、聊天机器人。 语言翻译&#xf…...