Penggunaan fungsi O_NONBLOCK pada PHP

(PHP 4 >= 4.1.0, PHP 5, PHP 7, PHP 8)

socket_set_nonblockSets nonblocking mode for file descriptor fd

Description

socket_set_nonblock(Socket $socket): bool

When an operation (e.g. receive, send, connect, accept, ...) is performed on a non-blocking socket, the script will not pause its execution until it receives a signal or it can perform the operation. Rather, if the operation would result in a block, the called function will fail.

Return Values

Returns true on success or false on failure.

Changelog

VersionDescription
8.0.0 socket is a Socket instance now; previously, it was a resource.

Examples

Example #1 socket_set_nonblock() example

<?php
$socket 
socket_create_listen(1223);
socket_set_nonblock($socket);socket_accept($socket);
?>

This example creates a listening socket on all interfaces on port 1223 and sets the socket to O_NONBLOCK mode. socket_accept() will immediately fail unless there is a pending connection exactly at this moment.

See Also

  • socket_set_block() - Sets blocking mode on a socket
  • socket_set_option() - Sets socket options for the socket
  • stream_set_blocking() - Set blocking/non-blocking mode on a stream

kpobococ at gmail dot com

12 years ago

Beware, when using this function within a loop (i.e. a demon with a socket). The socket_accept(), for example, emits a warning each time there is no incoming connection available to be read. My php error log file got huge in a matter of seconds, eventually crashing the server.

Of course, i used the @ before the function to take care of that problem.

[EDITOR: One can (and should) use socket_select to detect a new connection on a socket (it's a "readable" event)]

I'm writing a Web Server to support FastCGI. Using Unix socket to communicate with php-fpm, the non-block option cannot be set, which will cause the php-fpm response parser to access illegal memory.

I've used socket() to set non-blocking options, and using fcntl() to set non-blocking options can lead to illegal memory access. Once the non-blocking options are cancelled, everything works. But my Web Server is a non-blocking event-driven model, so I have to use Unix sockets for non-blocking communications.

test.cc

/** * Created by Crow on 12/27/18. * Copyright (c) 2018 Crow All rights reserved. * @author Crow * @brief This file is test the ResponseParser * @details construct the request, send/write it to the peer endpoint. * use tcpdump can get the result [if php-fpm listened on TCP socket] * $ sudo tcpdump port xxxx -i lo -vv -w a.cap * $ wireshark a.cap */ #include <fcntl.h> #include <unistd.h> #include <sys/un.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <arpa/inet.h> #include <cstdio> #include <iostream> #include <fstream> #include "protocol/fastCGI/request_builder.h" #include "protocol/fastCGI/response_parser.h" int main() { std::map<std::string, std::string> param_map; param_map.insert({"REMOTE_PORT", "80"}); param_map.insert({"REMOTE_ADDR", "127.0.0.1"}); param_map.insert({"REQUEST_METHOD", "POST"}); param_map.insert({"SERVER_PROTOCOL", "HTTP/1.1"}); param_map.insert({"SCRIPT_FILENAME", "/home/Crow/1.php"}); param_map.insert({"CONTENT_LENGTH", "11"}); std::string in_str("a=b&c=d&e=f"); platinum::fcgi::RequestBuilder builder(3, 11, in_str, param_map); builder.Build(); auto b = builder.begin_requset(); auto p = builder.fcgi_params(); auto i = builder.fcgi_in(); errno = 0; // ssize_t ret{}; // int fd = ::socket(AF_INET, SOCK_STREAM, 0); // struct sockaddr_in addr{}; // addr.sin_family = AF_INET; // addr.sin_port = ::htons(9000); // addr.sin_addr.s_addr = ::inet_addr("127.0.0.1"); int fd = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC , 0); <= Here! ! ! auto flag = ::fcntl(fd, F_GETFL); flag |= O_NONBLOCK; if (::fcntl(fd, F_SETFL, flag)) { perror("fcntl"); std::abort(); } struct sockaddr_un addr{}; addr.sun_family = AF_UNIX; ::strcpy(addr.sun_path, "/home/Crow/xfc.sock"); auto ret = ::connect(fd, (const struct sockaddr *) &addr, sizeof(addr)); if (ret < 0) perror("connect"); ::write(fd, reinterpret_cast<void *>(&b), sizeof(b)); for (const auto &var : p) { ::write(fd, reinterpret_cast<void *>(var.first.get()), static_cast<size_t>(var.second)); perror("write"); } for (const auto &var : i) { ::write(fd, reinterpret_cast<void *>(var.first.get()), static_cast<size_t>(var.second)); perror("write"); } char buf[10000]; std::vector<unsigned char> data(1024); platinum::fcgi::ResponseParser parser; while (!parser.Complete()) { ret = ::read(fd, data.data(), 1024); parser.feed(data.cbegin(), static_cast<int>(ret)); auto stdout_ = parser.transform_data(); std::string str(stdout_.cbegin(), stdout_.cend()); std::cout << str << std::endl; } close(fd); return 0; }

fastCGI/response_parser.h

/** * Created on 12/26/18. * Copyright (c) 2018 Crow All rights reserved. * @author Crow * @brief */ #ifndef PLATINUM_RESPONSE_PARSER_H #define PLATINUM_RESPONSE_PARSER_H #include "base.h" #include "protocol/fastCGI/component.h" #include "protocol/parser.hpp" namespace platinum { namespace fcgi { enum State : int { COMPLETED, UNCOMPLETED, FAULT, }; class ResponseParser : public platinum::Parser { public: using const_iter = std::vector<FCGIData>::const_iterator; ResponseParser(); ~ResponseParser() override = default; long feed(const_iter iter, long length); auto transform_data() -> const std::vector<FCGIData> & { return transform_data_; } int request_id() { return request_id_; } long long app_status() { return app_status_; } State state() { return static_cast<State>(state_); } Status status() { return static_cast<Status>(status_); } bool Complete() { return complete_; } void Reset(); private: void ParseStdout(const_iter &iter, long &length, long ct_len, long pd_len); void ParseStderr(const_iter &iter, long &length, long ct_len, long pd_len); void ParseEndRequest(const_iter &iter); std::vector<FCGIData> transform_data_; std::vector<FCGIData> name_value_data_; int request_id_; long transform_len_; long padding_len_; long long app_status_; bool complete_; bool in_content_; State state_; Status status_; }; } } #endif //PLATINUM_RESPONSE_PARSER_H

fastCGI/response_parser.cc

/** * Created by Crow on 12/26/18. * Copyright (c) 2018 Crow All rights reserved. * @author Crow * @brief This file is Class ResponseParser. It can be reentrant */ #include "response_parser.h" #include <cstring> #include <string> using namespace platinum::fcgi; ResponseParser::ResponseParser() : request_id_(-1), transform_len_(0), padding_len_(0), app_status_(-1), complete_(false), in_content_(false), state_(State::UNCOMPLETED), status_(Status::FCGI_UNKNOWN_ROLE) { transform_data_.reserve(1024); // make sure reserve space for transform_data_ } /** * @brief feed() the core to parse the FCGI response * @param iter Buffer's cosnt iterator * @param length Buffer's length this time * @return parse result */ long ResponseParser::feed(ResponseParser::const_iter iter, long length) { auto len_temp(length); transform_data_.clear(); // To ensure the last parsing result is complete if (transform_len_) { auto len = transform_len_ > length ? length : transform_len_; transform_data_.insert(transform_data_.cend(), iter, iter + len); length -= len; // reduce the length iter += len; // move the iter transform_len_ -= len; } if (length == 0) { return (len_temp - length); } else if (padding_len_) { auto len = padding_len_ > length ? length : padding_len_; length -= len; iter += len; padding_len_ -= len; } while (length) { // the whole parsing process continus utils length < 0 if (state_ == State::COMPLETED || state_ == State::FAULT || length < sizeof(Header)) { return (len_temp - length); } Header header(iter); // Construct a header iter += sizeof(Header); length -= sizeof(Header); request_id_ = header.request_id(); auto ct_len = header.content_length(); auto pd_len = header.padding_length(); switch (header.type()) { case Type::FCGI_STDOUT: ParseStdout(iter, length, ct_len, pd_len); break; case Type::FCGI_STDERR: ParseStderr(iter, length, ct_len, pd_len); break; case Type::FCGI_END_REQUEST: ParseEndRequest(iter); break; default: break; } } return len_temp - length; } /** * @brief To parse the STDOUT part * @param iter buffer's iterator (ref) * @param length buffer's length (ref) * @param ct_len the content length of FCGI_STDOUT * @param pd_len the padding length of FCGI_STDOUT */ void ResponseParser::ParseStdout(const_iter &iter, long &length, long ct_len, long pd_len) { if (ct_len == 0 && pd_len == 0) return ; auto len1 = ct_len > length ? length : ct_len; // judge if we have enough space to deal with std::string str(iter, iter + len1); std::string::size_type pos; if (!in_content_) { if ((pos = str.find("\r\n\r\n")) != std::string::npos) { name_value_data_.insert(name_value_data_.cend(), iter, iter + pos); iter += pos + 4; length -= pos + 4; ct_len -= pos + 4; len1 -= pos + 4; } else { state_ = State::FAULT; complete_ = true; return ; } in_content_ = true; } transform_data_.insert(transform_data_.cend(), iter, iter + len1); iter += len1; length -= len1; ct_len -= len1; if (length == 0) { transform_len_ += ct_len; padding_len_ = pd_len; return ; } auto len2 = pd_len > length ? length : pd_len; iter += len2; length -= len2; pd_len -= len2; if (length == 0) { padding_len_ = pd_len; return ; } } /** * @beief To parse the STDERR part * @param iter buffer's iterator (ref) * @param length buffer's length (ref) * @param ct_len the conten length of FCGI_STDERR * @param pd_len the padding length of FCGI_STDERR */ void ResponseParser::ParseStderr(const_iter &iter, long &length, long ct_len, long pd_len) { if (ct_len == 0 && pd_len == 0) return ; auto len1 = ct_len > length ? length : ct_len; // judge if we have enough space to deal with transform_data_.insert(transform_data_.cend(), iter, iter + len1); iter += len1; length -= len1; ct_len -= len1; if (length == 0) { transform_len_ += ct_len; padding_len_ = pd_len; return ; } auto len2 = pd_len > length ? length : pd_len; iter += len2; length -= len2; pd_len -= len2; if (length == 0) { padding_len_ = pd_len; return ; } } /** * @brief To parse the EndRequestRecord part * @param iter Buffer's iterator */ void ResponseParser::ParseEndRequest(const_iter &iter) { iter -= sizeof(Header); // back to the Header's start to constrcut the EndRequestRecord EndRequestRocord end_request_record(iter); app_status_ = end_request_record.app_status(); status_ = end_request_record.protocol_status(); complete_ = true; state_ = State::COMPLETED; } void ResponseParser::Reset() { request_id_ = -1; transform_len_ = 0; padding_len_ = 0; app_status_ = -1; complete_ = false; in_content_ = false; state_ = State::UNCOMPLETED; status_ = Status::FCGI_UNKNOWN_ROLE; transform_data_.clear(); name_value_data_.clear(); }

I think the key is ResponseParser, so RequestBuilder is not posted, if necessary, I can fill it up.

gdb backstrace

(gdb) r Starting program: /home/Crow/CLionProjects/platinum/test/bin/response_parser write: Success write: Success write: Success write: Success write: Success write: Success write: Success Program received signal SIGSEGV, Segmentation fault. 0x0000000000405f40 in platinum::fcgi::Header::Header (this=0x7fffffffb788, iter=<error reading variable: Cannot access memory at address 0x638000>) at /home/Crow/CLionProjects/platinum/protocol/fastCGI/component.cc:30 30 : version_(*iter), (gdb) bt #0 0x0000000000405f40 in platinum::fcgi::Header::Header (this=0x7fffffffb788, iter=<error reading variable: Cannot access memory at address 0x638000>) at /home/Crow/CLionProjects/platinum/protocol/fastCGI/component.cc:30 #1 0x0000000000408c70 in platinum::fcgi::ResponseParser::feed (this=0x7fffffffb830, iter=<error reading variable: Cannot access memory at address 0x638000>, length=-58305) at /home/Crow/CLionProjects/platinum/protocol/fastCGI/response_parser.cc:65 #2 0x0000000000402ac7 in main () at /home/Crow/CLionProjects/platinum/test/response_parser_test.cc:81 (gdb)

Without the O_NONBLOCK

[Crow@EvilCrow bin]$ sudo ./response_parser write: Success write: Success write: Success write: Success write: Success write: Success write: Success hello

the request PHP file 1.php

<?php echo hello; ?>

I expect to make Unix Socket work properly after setting up non-blocking IO. As I understand it, Unix sockets and INET sockets should behave identically on blocking. Why does this happen? Thank for you very much.

Postingan terbaru

LIHAT SEMUA