systemd的socket激活机制 socket system
yuyutoo 2024-10-11 21:39 6 浏览 0 评论
1. 什么是socket激活机制
What was socket activation again? -- Basically, socket activation simply means that systemd sets up listening sockets (IP or otherwise) on behalf of your services (without these running yet), and then starts (activates) the services as soon as the first connection comes in. Depending on the technology the services might idle for a while after having processed the connection and possible follow-up connections before they exit on their own, so that systemd will again listen on the sockets and activate the services again the next time they are connected to. For the client it is not visible whether the service it is interested in is currently running or not. The service's IP socket stays continously connectable, no connection attempt ever fails, and all connects will be processed promptly.
- systemd的socket激活机制是一种在有连接请求时才启动服务的技术。这种机制通过让systemd守护进程在应用程序启动前打开监听套接字来工作。当有客户端连接到达时,systemd会启动相应的服务并将套接字传递给它,从而减少资源使用并提高性能。
- 当服务将客户端的请求处理完毕后,服务可以继续运行以处理更多请求,或者在处理完当前请求后退出(依赖于服务的实现)。
- 当服务退出时,包括正常的退出或者服务的异常崩溃,systemd会检测到服务的退出状态,systemd会继续管理套接字并确保服务能够重新启动并处理新的连接请求。
这种机制的主要优点包括:
- 按需启动服务:服务只有在需要时才启动,减少了系统资源的浪费。
- 无缝重启:由于套接字由systemd管理,即使服务进程重启,套接字也不会关闭,从而避免了短暂的停机时间。
- 简化配置:通过统一的套接字管理,可以简化服务的配置和管理。
下图所示为systemd的socket激活机制的简单示例,
- systemd首先替服务myservice创建并监听了socket,端口为8080,此时myservice服务还未启动;
- 当客户端client的connect请求到来时,systemd才去启动服务myservice,同时将创建的socket描述符传递给myservice服务;
- myservice服务接收到systemd的socket描述符后,直接执行accept()去接收client的连接请求,连接建立成功,然后和myservice服务端正常进行数据收发。
2. 服务如何获取systemd打开的socket描述符
sd_listen_fds() 是 systemd 库中的一个重要函数,主要用于套接字激活(socket activation)场景。如下所示,这个函数返回由 systemd 传递给服务的预打开文件描述符的数量。这些文件描述符通常是套接字,但也可能是其他类型的文件描述符。
int sd_listen_fds(int unset_environment);
- 参数unset_environment,如果为非零值,函数会清除相关的环境变量(LISTEN_PID 和 LISTEN_FDS)。这通常在多进程服务中很有用,可以防止子进程误认为这些描述符是为它们准备的。
- 函数返回传递的文件描述符数量,如果没有文件描述符被传递或发生错误,返回 0 或负数。
接下来以一个例子,介绍该函数的用法,对于linux而言,一般进程在启动后, 0、1、2三个文件描述符已经被占用,分别是标准输入、输出和错误。所以,不管是文件描述符还是socket描述符,一般值都是从3开始,而这个3正是SD_LISTEN_FDS_START 宏。sd_listen_fds函数一般使用时需要搭配SD_LISTEN_FDS_START 宏和sd_* 相关函数(如 sd_is_socket、sd_is_socket_inet 等)来使用,sd_* 函数主要用来检查描述符的类型。
#define SD_LISTEN_FDS_START 3
- 假设服务要创建一个tcp的server socket,端口为8080,systemd已经帮服务先创建;
- 调用sd_listen_fds,获取systemd 传递的套接字文件描述符的数量,数量为n,n是大于0的;
- 接着轮询,从描述符3开始,直到3+n结束,调用sd_is_socket_inet函数轮询查找监听8080端口的tcp socket,如果找到,函数sd_is_socket_inet返回值大于0;
- 则服务用找到的描述符handle进行accept等后续的操作。
#include <systemd/sd-daemon.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int last_handle = SD_LISTEN_FDS_START + sd_listen_fds (0);
int handle;
for (handle = SD_LISTEN_FDS_START; handle < last_handle; ++handle)
if (sd_is_socket_inet (handle, AF_INET, SOCK_STREAM, 1,
(uint16_t) 8080) > 0) {
printf("this is a IPv4 TCP socket listening on port 8080\n");
break;
}
//accept
return 0;
}
3. sd_is_* 类函数
常用的sd_is_* 类函数如下所示,
int sd_is_socket(int fd, int family, int type, int listening);
int sd_is_socket_inet(int fd, int family, int type,
int listening, uint16_t port);
int sd_is_socket_unix(int fd, int type, int listening,
const char *path, size_t length);
sd_is_socket() may be called to check whether the specified file descriptor refers to a socket. If the family parameter is not AF_UNSPEC, it is checked whether the socket is of the specified family (AF_UNIX, AF_INET, ...). If the type parameter is not 0,it is checked whether the socket is of the specified type(SOCK_STREAM, SOCK_DGRAM, ...). If the listening parameter is positive, it is checked whether the socket is in accepting mode, i.e. listen() has been called for it. If listening is 0, it is checked whether the socket is not in this mode. If the parameter is negative, no such check is made. The listening parameter should only be used for stream sockets and should be set to a negative value otherwise.
- sd_is_socket()函数,来检查指定的描述符fd指向的是对应参数的一个socket套接字。如果family不是AF_UNSPEC,则需要检测是否是指定的AF_UNIX, AF_INET等,type为socket的类型SOCK_STREAM,SOCK_DGRAM,如果listening为正值,则检测socket是否正在处于accept状态(已调用listen)。
sd_is_socket_inet() is similar to sd_is_socket(), but optionally checks the IPv4 or IPv6 port number the socket is bound to, unless port is zero. For this call family must be passed as either AF_UNSPEC, AF_INET, or AF_INET6.
- sd_is_socket_inet()函数和sd_is_socket()类似,如果入参的port非0,则会去检测对应的socket fd是否在指定的port上进行了bind。
sd_is_socket_unix() is similar to sd_is_socket() but optionally checks the AF_UNIX path the socket is bound to, unless the path parameter is NULL. For normal file system AF_UNIX sockets, set the length parameter to 0. For Linux abstract namespace sockets,set the length to the size of the address, including the initial 0 byte, and set the path to the initial 0 byte of the socket address.
- sd_is_socket_unix()函数,如果path非空,则会去检测对应的socket fd是否为AF_UNIX 域套接字,同时是否在指定的path上进行绑定。一般length参数设置为0。
4. 在ubuntu上创建一个带socket激活机制的服务
服务端代码,myservice.c,
- log采用systemd中的journal相关的log接口sd_journal_print()函数;
- 如果systemd没有传递过来socket描述符,自己创建一个(原生启动,不依赖systemd,仅为了测试);
- 如果systemd传递了socket描述符,判断是否是tcp 8080端口的监听socket对应的描述符,如果是,直接调用accept()函数去处理客户端的连接请求;
- 该代码的功能只是简单的示例,每次处理一个客户端的请求,先读取client的数据,读取到数据后,然后发送"Hello, client!"给客户端,处理完一个客户端的请求后(等待client调用close关闭socket,此时recv或者send阻塞函数返回)关闭对应的socket,继续accept来处理新的client的请求。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <signal.h>
#include <systemd/sd-daemon.h>
#include <netinet/in.h>
#include <systemd/sd-journal.h>
#define SERVER_PORT 8080
#define BUFFER_SIZE 1024
#define true 1
#define false 0
typedef int bool;
int main() {
int handle = -1;
int n = sd_listen_fds(0);
if (n <= 0) {
sd_journal_print(LOG_INFO, "No file descriptors received,we need to creat a tcp socket.\n");
handle = socket(AF_INET, SOCK_STREAM, 0);
if (handle == -1) {
perror("socket");
exit(1);
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(SERVER_PORT);
if (bind(handle, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
sd_journal_print(LOG_INFO, "server bind error\n");
exit(1);
}
if (listen(handle, 10) == -1) {
sd_journal_print(LOG_INFO, "server listen error\n");
exit(1);
}
}else{
sd_journal_print(LOG_INFO, "Received %d file descriptor(s) from systemd \n", n);
int last_handle = SD_LISTEN_FDS_START + n;
bool fd_check_ok = false;
for (handle = SD_LISTEN_FDS_START; handle < last_handle; ++handle)
{
if (sd_is_socket_inet (handle, AF_INET, SOCK_STREAM, 1,
(uint16_t) SERVER_PORT) > 0) {
sd_journal_print(LOG_INFO, "this is a IPv4 TCP socket listening on port %d \n",SERVER_PORT);
fd_check_ok = true;
break;
}
}
if(fd_check_ok != true)
{
sd_journal_print(LOG_ERR, "No file descriptors found for port %d .\n",SERVER_PORT);
return 1;
}
}
struct sockaddr_storage client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
int client_fd;
while (1) {
client_fd = accept(handle, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd < 0) {
sd_journal_print(LOG_INFO,"Accept failed\n");
continue;
}
while (1) {
int bytes_received = recv(client_fd, buffer, BUFFER_SIZE, 0);
if (bytes_received <= 0) {
sd_journal_print(LOG_INFO,"Server Receive failed");
break;
} else {
buffer[bytes_received] = '\0';
sd_journal_print(LOG_INFO,"Message received from client: %s\n", buffer);
}
strcpy(buffer, "Hello, client!");
int bytes_send = send(client_fd, buffer, strlen(buffer), 0);
if(bytes_send<=0)
{
sd_journal_print(LOG_INFO,"Server Send failed");
break;
}
}
close(client_fd);
}
return 0;
}
代码编译
sudo gcc -o /usr/local/bin/myservice myservice.c -lsystemd
在ubuntu下,如果没有安装systemd,需要安装sudo apt install libsystemd-dev。
- 在/etc/systemd/system/下创建myservice.socket文件:监听本地的tcp 8080端口。
[Unit]
Description=My Service Socket
[Socket]
ListenStream=127.0.0.1:8080
[Install]
WantedBy=sockets.target
- 接着,在同一目录下创建myservice.service文件,其中的选项都比较好理解,After是启动顺序,Requires是依赖,ExecStart是服务端程序,标准输入设置为null,因为程序不需要输入,输出和错误设置为journal,用于利用systemd的journal系统打印log(journal是一个强大的日志系统),TimeoutStopSec指定停止服务最长的实际为5秒,如果5秒时间到了后还没有停止就强制停止并报告相关的失败。
[Unit]
Description=My Service
After=network.target myservice.socket
Requires=myservice.socket
[Service]
Type=simple
ExecStart= /usr/local/bin/myservice
StandardOutput=journal
StandardError=journal
StandardInput=null
TimeoutStopSec=5
- 接下来,启动socket单元,并利用systemctl enable命令配置myservice服务在ubuntu系统启动时自动启动。
sudo systemctl start myservice.socket
sudo systemctl enable myservice.socket
启动socket单元后,利用ss -ltnp命令检测端口是否在监听,从输出可以看到8080端口正在监听,
lyf@lyf:/etc/systemd/system$ ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
LISTEN 0 511 127.0.0.1:60737 0.0.0.0:* users:(("node",pid=462,fd=19))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 1000 10.255.255.254:53 0.0.0.0:*
实时显示systemd中服务的log,使用下面的命令,-u后跟myservice.service前半部分,-f选项来持续查看实时日志输出,类似于tail -f命令,
sudo journalctl -u myservice -f
此时,myservice.socket对应的服务已经启动,如下所示,但是由于还没有客户端的实际请求,此时,myservice.service还没有启动,状态是inactive的(之前启动过)。
5. 客户端程序及测试
客户端程序myclient.c,
- 编译 gcc -o myclient myclient.c;
- 连接到本地的8080端口上,先发送"Hello, Server!"给server,然后从server读取数据。如此重复三次。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define SERVER_PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024
int main() {
int sockfd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE] = {0};
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
exit(EXIT_FAILURE);
}
// 连接到服务器
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
for(int snd_count = 0; snd_count < 3;snd_count++)
{
// 发送消息到服务器
strcpy(buffer, "Hello, Server!");
send(sockfd, buffer, strlen(buffer), 0);
printf("Message sent to server: %s\n", buffer);
memset(buffer, '\0', BUFFER_SIZE);
// 接收服务器的响应
int bytes_received = recv(sockfd, buffer, BUFFER_SIZE,0);
if (bytes_received < 0) {
perror("Receive failed");
} else {
buffer[bytes_received] = '\0';
printf("Message received from server: %s\n", buffer);
}
sleep(1);
}
// 关闭套接字
close(sockfd);
return 0;
}
客户端发送请求,收取server端的数据,发送三次"Hello, Server!",收取3次"Hello, client!"。
收到客户端connect请求后,service对应的log输出,
- 接收到客户端发来的3次"Hello, Server!",并做回复;
- 最终阻塞在recv上,当客户端关闭socket后,recv函数返回0,退出;
收到客户端的connect请求后,systemd会自动启动myservice服务,如下所示,
6. 停止服务
- 关于服务的停止,这里牵扯到2个服务,myservice.socket和myservice.service。myservice.socket服务去启动myservice服务。因此如果要彻底停止服务,需要将两个服务都停掉;
- 单纯停止myservice.service服务后,因为systemd socket激活机制,systemd继续在后台监听8080端口,必须将myservice.socket服务停止,才能彻底结束监听8080端口。
lyf@lyf:/etc/systemd/system$ sudo systemctl stop myservice
[sudo] password for lyf:
Stopping 'myservice.service', but its triggering units are still active:
myservice.socket
lyf@lyf:/etc/systemd/system$ ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
LISTEN 0 511 127.0.0.1:60737 0.0.0.0:* users:(("node",pid=462,fd=19))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 1000 10.255.255.254:53 0.0.0.0:*
lyf@lyf:/etc/systemd/system$ sudo systemctl stop myservice.socket
lyf@lyf:/etc/systemd/system$ ss -lntp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
LISTEN 0 511 127.0.0.1:60737 0.0.0.0:* users:(("node",pid=462,fd=19))
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 1000 10.255.255.254:53 0.0.0.0:*
7. 让systemd去accept
The name of the .service unit is by default the same as the name of the .socket unit, but can be altered with the Service= option described below. Depending on the setting of the Accept= option described below, this .service unit must either be named like the .socket unit, but with the suffix replaced, unless overridden with Service=; or it must be a template unit named the same way. Example: a socket file foo.socket needs a matching service foo.service if Accept=no is set. If Accept=yes is set, a service template foo@.service must exist from which services are instantiated for each incoming connection.
- 如果在.socket文件中,设置了Accept=no,默认值是no,不设置既是no。foo.socket对应的服务的文件名为foo.service;
- 如果在.socket文件中,设置了Accept=yes,foo.socket对应的服务应该设置为foo@.service,注意这个@符号;
当在.socket 文件中设置 Accept=yes 时,参考上图,systemd 的行为如下:
- systemd 会为每个新的连接创建一个新的服务实例,每个连接都在独立的进程中处理,提高了安全性和隔离性;
- systemd负责创建socket,bind,listen,accept,然后当有client连接时,accept返回,此时将返回的socket描述符传递给新启动的服务实例;
- .service 文件中应该包含 StandardInput=socket 和 StandardOutput=socket,表示将service服务进程的标准输入、标准输出和accept返回的socket描述符做了一定的“绑定/映射”;
- 在启动的服务中,建立的连接,读,被重定向到标准输入,STDIN_FILENO,0。写,被重定向到标准输出,STDOUT_FILENO,1。service服务程序可以直接从 stdin 读取数据,写入 stdout(不需要执行 listen() 或 accept() 调用,直接拿到的就是0和1描述符);
- 每个连接都在单独的进程中处理,自动实现并发。不需要在程序中实现多线程或多进程逻辑(典型的单连接单进程并发)。
- 每个连接都有独立的进程,可能会增加系统资源使用,适合短连接或低并发场景。
修改后的myservice.socket文件,
[Unit]
Description=My Service Socket
[Socket]
ListenStream=127.0.0.1:8080
Accept=yes
[Install]
WantedBy=sockets.target
修改后的.service文件,需要重命名为myservice@.service,注意StandardInput和StandardOutput都设置为了socket。
[Unit]
Description=My Service
After=network.target myservice.socket
Requires=myservice.socket
[Service]
Type=simple
ExecStart= /usr/local/bin/myservice
StandardInput=socket
StandardOutput=socket
StandardError=journal
service服务端程序myservice.c修改为(client客户端的代码保持一致),
- 代码中没有任何的socket的相关接口,其实也可以将recv和send函数修改为read和write;
- 从标准输入,STDIN_FILENO,读。往标准输出,STDOUT_FILENO,写;
- 向客户端发送的数据中添加了服务端pid的字符串。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <signal.h>
#include <systemd/sd-daemon.h>
#include <systemd/sd-journal.h>
#define BUFFER_SIZE 1024
int main() {
pid_t pid;
pid = getpid();
char buffer[BUFFER_SIZE];
while (1) {
int bytes_received = recv(STDIN_FILENO, buffer, BUFFER_SIZE-1,0);
if (bytes_received <= 0) {
sd_journal_print(LOG_ERR,"Server recv() failed\n");
if(bytes_received == 0)
sd_journal_print(LOG_ERR,"Server recv() return 0, client close the socket \n");
break;
} else {
buffer[bytes_received] = '\0';
sd_journal_print(LOG_ERR,"server %d received client msg: %s \n", pid, buffer);
}
sprintf(buffer,"Hello, client!_%d",pid);
int bytes_send = send(STDOUT_FILENO, buffer, strlen(buffer),0);
if(bytes_send<=0)
{
sd_journal_print(LOG_ERR,"Server send() failed\n");
if(bytes_send == 0)
sd_journal_print(LOG_ERR,"Server send() return 0, client close the socket \n");
break;
}
}
return 0;
}
- 创建好后,重新编译代码,停止并重启myservice.socket服务。
- 启动2个client客户端,对应的log如下所示,
client1,30511为对应启动的server端服务进程的pid。
lyf@lyf:~/systemd_test$ ./myclient
Message sent to server: Hello, Server!
Message received from server: Hello, client!_30511
......
client2,30502为对应启动的server端服务进程的pid。
lyf@lyf:~/systemd_test$ ./myclient
Message sent to server: Hello, Server!
Message received from server: Hello, client!_30502
service服务端的log,可以通过下面的命令查看,
journalctl -xe | grep -i myservice
输出如下,
Jul 10 19:10:54 lyf systemd[1]: Started myservice@16-127.0.0.1:8080-127.0.0.1:60802.service - My Service (127.0.0.1:60802).
?? Subject: A start job for unit myservice@16-127.0.0.1:8080-127.0.0.1:60802.service has finished successfully
?? A start job for unit myservice@16-127.0.0.1:8080-127.0.0.1:60802.service has finished successfully.
Jul 10 19:10:54 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:10:55 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:10:56 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:10:57 lyf systemd[1]: Started myservice@17-127.0.0.1:8080-127.0.0.1:60866.service - My Service (127.0.0.1:60866).
?? Subject: A start job for unit myservice@17-127.0.0.1:8080-127.0.0.1:60866.service has finished successfully
?? A start job for unit myservice@17-127.0.0.1:8080-127.0.0.1:60866.service has finished successfully.
Jul 10 19:10:57 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:10:57 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:10:58 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:10:58 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:10:59 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:10:59 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:11:00 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:00 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:11:01 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:01 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:11:02 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:02 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:11:03 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:03 lyf myservice[30502]: server 30502 received client msg: Hello, Server!
Jul 10 19:11:04 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:04 lyf myservice[30502]: Server recv() failed
Jul 10 19:11:04 lyf myservice[30502]: Server recv() return 0, client close the socket
Jul 10 19:11:04 lyf systemd[1]: myservice@16-127.0.0.1:8080-127.0.0.1:60802.service: Deactivated successfully.
?? The unit myservice@16-127.0.0.1:8080-127.0.0.1:60802.service has successfully entered the 'dead' state.
Jul 10 19:11:05 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:06 lyf myservice[30511]: server 30511 received client msg: Hello, Server!
Jul 10 19:11:07 lyf myservice[30511]: Server recv() failed
Jul 10 19:11:07 lyf myservice[30511]: Server recv() return 0, client close the socket
Jul 10 19:11:07 lyf systemd[1]: myservice@17-127.0.0.1:8080-127.0.0.1:60866.service: Deactivated successfully.
?? The unit myservice@17-127.0.0.1:8080-127.0.0.1:60866.service has successfully entered the 'dead' state.
- 为了便于观察,2个client客户端各进行了10次的数据收发,对应server端的进程的pid和上文中对应,也就是30502和30511;
- client1,systemd[1]: Started myservice@16-127.0.0.1:8080-127.0.0.1:60802.service - My Service (127.0.0.1:60802).,客户端的端口为60802。同理,client2客户端的端口为60866;
- client1, 端口60802,server服务端进程的pid为30502。client2, 端口60866,server服务端进程的pid为30511;
- client1发送完数据后,调用close,server服务端(30502)进程recv返回0,退出,systemd[1]: myservice@16-127.0.0.1:8080-127.0.0.1:60802.service: Deactivated successfully.,服务端进程工作已经完成,自动退出;
- client2发送完数据后,调用close,server服务端(30511)进程recv返回0,退出,systemd[1]: myservice@17-127.0.0.1:8080-127.0.0.1:60866.service: Deactivated successfully.,服务端进程工作已经完成,自动退出;
- 从以上log可以看出systemd对每个建立的连接都创建了一个新的进程来处理客户端的请求。
相关推荐
- Mysql和Oracle实现序列自增(oracle创建序列的sql)
-
Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...
- 关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)
-
概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...
- MySQL CREATE TABLE 简单设计模板交流
-
推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....
- mysql学习9:创建数据库(mysql5.5创建数据库)
-
前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...
- MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别
-
执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...
- Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出
-
在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...
- 美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍
-
多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...
- 汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)
-
全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...
- 【推荐】ProAc Response系列扬声器逐个看
-
有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...
- #本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱
-
作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...
- 专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)
-
Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...
- 常用半导体中英对照表(建议收藏)(半导体英文术语)
-
作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...
- Fyne Audio F502SP 2.5音路低音反射式落地音箱评测
-
FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...
- 有哪些免费的内存数据库(In-Memory Database)
-
以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...
- RazorSQL Mac版(SQL数据库查询工具)
-
RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- Mysql和Oracle实现序列自增(oracle创建序列的sql)
- 关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)
- MySQL CREATE TABLE 简单设计模板交流
- mysql学习9:创建数据库(mysql5.5创建数据库)
- MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别
- Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出
- 美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍
- 汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)
- 【推荐】ProAc Response系列扬声器逐个看
- #本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)