systemd的socket激活机制 socket system
yuyutoo 2024-10-11 21:39 1 浏览 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对每个建立的连接都创建了一个新的进程来处理客户端的请求。
相关推荐
- jQuery VS AngularJS 你更钟爱哪个?
-
在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...
- Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0
-
在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...
- 如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例
-
要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...
- 编程技巧:Jquery实时验证,指定长度的「负小数」
-
为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...
- 一篇文章带你用jquery mobile设计颜色拾取器
-
【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...
- 编程技巧:Jquery实时验证,指定长度的「正小数」
-
为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...
- jquery.validate检查数组全部验证
-
问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...
- Vue进阶(幺叁肆):npm查看包版本信息
-
第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...
- layui中使用lay-verify进行条件校验
-
一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...
- jQuery是什么?如何使用? jquery是什么功能组件
-
jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...
- django框架的表单form的理解和用法-9
-
表单呈现...
- jquery对上传文件的检测判断 jquery实现文件上传
-
总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...
- Nodejs之MEAN栈开发(四)-- form验证及图片上传
-
这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...
- 大数据开发基础之JAVA jquery 大数据java实战
-
上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...
- 推荐四个开源的jQuery可视化表单设计器
-
jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)