개발 꿀팁/PHP

PHP SOCKET 프로그래밍

Jammie 2022. 7. 12. 14:50
반응형

1. 예비 지식

일그동안 얼마나 많은 사람이 php의 socket 모듈을 사용하는지는 잘 보이지 않았고, 아마도 모두가 스크립트 언어의 범주에 넣었을 것이지만, 사실 php의 socket 모듈은 ftplist, HTTP post 제출, smtp 제출, 패킷 및 smpp 프로토콜, whois 조회 등 많은 일을 할 수 있다.이것들은 비교적 흔히 볼 수 있는 조회들이다.

특히 php의 s는ocket 확장 라이브러리가 할 수 있는 일은 c보다 별로 나쁘지 않다.
php의 socket연결함수
1. 커널에 통합된소켓
이 시리즈의 함수는 오직액티브 연결만 할 뿐 포트 감청 관련 기능은 구현할 수 없다.그리고 4.3.0까지 모든 소켓 연결은 차단 모드에서만 작동한다.
이 함수는 다음과 같습니다.
fsockopen,pfsockopen
이 두 함수의 구체정보는 php.net의 사용자 매뉴얼을 참조할 수 있다.
그들은 모두 한 명씩 돌아올 것이다.리소스 ID 이 리소스는 fgets(), fwrite(), fclose()와 같은 거의 모든 기능을 사용하여 이 기능을 작동시킬 수 있습니다. 모든 기능은 다음과 같은 네트워크 정보 흐름에 대한 규칙을 따릅니다.
fread() 파일 포인터 handle에서 length 바이트까지 읽습니다. 이 함수는 length 바이트를 읽거나 EOF에 도달했을 때 또는 패킷을 사용할 수 있을 때 파일을 읽는 것을 중지합니다.
인터넷에 대해서흐름은 반드시 주의해서 완전한 가방 하나를 얻으면 멈춘다.
2, php 확장 모드블록이 가지고 있는 소켓 기능.
php4.x 이뒤에 이런 모듈 extension=php_sockets.dll이 있고 Linux에는 extension=php_sockets.so이 있다.
이 모듈을 열 때이후 php는 listen 포트, 차단 및 비차단 모드 전환, multi-client 인터랙티브 처리 등 강력한 socket 기능을 갖게 됩니다.
이 시리즈의 함수 열시계는 http://www.php.net/manual/en/ref.sockets.php를 참조한다.
이 리스트를 보니까굉장히 풍성하지 않나요?아쉽게도 이 모듈은 아직 젊고 미숙한 부분이 많아 참고문헌이 매우 적습니다. ()
저도 연구 중입니다.그래서 당분간 구체적으로 논의하지 않고 참고문장 하나만 드리겠습니다.
HTTP: //www.zend.com/pecl/tutorials/sockets.php

2. PHP 소켓을 사용하여 확장하기

서버 측 코드:

<?php
/**
 * File name server.php
 * 서버측 코드
 * 
 * @author guisu.huang
 * @since 2012-04-11
 * 
 */
 
//클라이언트 연결 시 시간 초과 안 함
set_time_limit(0);
//IP와 포트 번호 설정
$address = "127.0.0.1";
$port = 2046; //디버깅할 때 포트를 많이 바꿔 프로그램을 테스트할 수 있습니다!
/**
 *SOCKET 만들기
 * AF_INET=是ipv4 ipv6를 사용하면 AF_INET6 인자가 됩니다
 * SOCK_STREAM은 socket의 tcp 타입으로 UDP의 경우 SOCK_DGRAM을 사용합니다
*/
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() 실패의 원인은:" . socket_strerror(socket_last_error()) . "/n");
//차단 모드
socket_set_block($sock) or die("socket_set_block() 실패의 원인은:" . socket_strerror(socket_last_error()) . "/n");
//소켓 포트에 바인딩
$result = socket_bind($sock, $address, $port) or die("socket_bind() 실패의 원인은:" . socket_strerror(socket_last_error()) . "/n");
//감청을 시작하다
$result = socket_listen($sock, 4) or die("socket_listen() 실패의 원인은:" . socket_strerror(socket_last_error()) . "/n");
echo "OK\nBinding the socket on $address:$port ... ";
echo "OK\nNow ready to accept connections.\nListening on the socket ... \n";
do { // never stop the daemon
	//접속 요청을 수신하고 클라이언트와 서버 간의 정보를 처리하기 위해 서브접속 소켓을 호출한다
	$msgsock = socket_accept($sock) or  die("socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n");
	
	//클라이언트 데이터 읽기
	echo "Read client data \n";
	//socket_read 함수는 \n,\t 또는 \0자를 만날 때까지 클라이언트 데이터를 계속 읽습니다.PHP 스크립트는 이 문자를 입력의 마침표로 간주합니다
	$buf = socket_read($msgsock, 8192);
	echo "Received msg: $buf   \n";
	
	//클라이언트로 데이터 전송 결과 쓰기
	$msg = "welcome \n";
	socket_write($msgsock, $msg, strlen($msg)) or die("socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n");
	//일단 출력이 클라이언트로 돌아가면 부모/자녀 모두 socket_close($msgsock) 함수를 통해 종료됩니다
    socket_close($msgsock);
} while (true);
socket_close($sock);

클라이언트 코드:

<?php
/**
 * File name:client.php
 * 클라이언트 코드
 * 
 * @author guisu.huang
 * @since 2012-04-11
 */
set_time_limit(0);
 
$host = "127.0.0.1";
$port = 2046;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)or die("Could not create	socket\n"); // Socket 만들기
 
$connection = socket_connect($socket, $host, $port) or die("Could not connet server\n");    //연결해.
socket_write($socket, "hello socket") or die("Write failed\n"); // 서버에 메시지 보내기
while ($buff = socket_read($socket, 1024, PHP_NORMAL_READ)) {
    echo("Response was:" . $buff . "\n");
}
socket_close($socket);

CLI를 사용하여 서버 시작:

php server.php

여기 socket_read 함수 참고:
선택적 인수는 상수입니다.
PH_BINARY_READ- 시스템 recv() 함수를 사용한다.이진 데이터를 읽기 위한 보안에 사용된다. (PHP >"묵"인정=4.1.0)
PH_NORMAL_READ- 읽기\ n 또는 \ r (PHP에서) <=4.0.6 디폴트)
파라미터 PHP_NORMAL_READ, 서버의 응답 결과가 없을 경우\ n.socket_read(): unable to read f롬 소켓

3.PHP의 동시 IO 프로그래밍

원문: http: // rango.swoole.com/archives/508

1) 멀티프로세스/멀티스레드 동기 차단

가장 초기의 서버 측 프로그램은 모두 멀티프로세스를 통해서였다.멀티 스레드는 동시 IO 문제를 해결한다.프로세스 모델이 가장 먼저 출현한 것은 유닉스 시스템의 탄생부터이다.프로세스의 개념이 생기다.초기 서버 측 프로그램일반적으로 Accept는 하나의 클라이언트 연결로 하나의 프로세스를 만들고, 그 후 서브프로세스가 순환동기로 들어가 차단된 상태로 클라이언트 연결과 상호 작용하여 데이터를 송수신 처리한다.

https://img-blog.csdn.net/20160401101426294

멀티스레드 모드는 늦게 나타나며, 스레드는 프로세스보다 가볍고, 스레드 간 메모리 스택을 공유하기 때문에 서로 다른 스레드 간 상호 작용이 매우 쉽다.예를 들어 채팅방과 같은 프로그램은 클라이언트 접속끼리 서로 대화할 수 있어 채팅방에 있는 플레이어보다 원하는 다른 사람이 메시지를 보낼 수 있다.멀티스레드 모드로 구현하면 매우 간단하며, 스레드에서 직접 어떤 클라이언트 접속을 읽고 쓸 수 있다.멀티프로세스 모델은 파이프라인, 메시지 큐, 공유 메모리로 데이터를 주고받으며 프로세스 간 통신(IPC)이 복잡한 기술을 통칭해야 가능하다.

코드 인스턴스:

다중 프로세스/스레드 모델의 흐름은

1.소켓 만들기서버 포트(bind), 수신 포트(listen)를 바인딩하고 PHP에서 stream_socket_server 함수 하나로 위의 3단계를 수행할 수 있으며, 물론 php sockets 확장을 사용하여 개별적으로 구현할 수도 있습니다.
2.while 루프 진입, 차단accept 조작에 끼우고 클라이언트 접속이 들어오기를 기다린다.새로운 클라이언트가 서버에 connect를 시작하고 운영체제가 이 프로세스를 깨울 때까지 프로그램은 잠들어 있습니다.accept 함수 클라이언트 연결 socket 반환
3.주 프로세스는 다중 프로세스 모델로 통합니다.fork (php: pcntl_fork) 를 통해 하위 프로세스를 만듭니다. 다중 스레드 모델에서는 pthread_create (php: new Thread) 를 사용하여 하위 프로세스를 만듭니다.아래에 특별한 선언이 없는 경우 프로세스를 사용하여 프로세스/스레드를 동시에 나타낼 것이다.
4. 하위 프로세스가 성공적으로 생성되면 w로 들어갑니다.hile 루프, recv(php: fread) 호출에 막혀 클라이언트가 서버로 데이터를 보내기를 기다립니다.데이터를 받으면 서버 프로그램이 처리해 send(php: fwrite)를 이용해 클라이언트에 응답을 보낸다.긴 접속 서비스는 지속적으로 클라이언트와 상호 작용하며 짧은 접속복보통 응답을 받으면 close를 한다.
5.클라이언트 접속이 닫혔을 때,프로세스가 종료되고 모든 리소스가 삭제됩니다.기본 프로세스가 하위 프로세스를 회수합니다.


이런 패턴의 가장 큰 문제는 진행 과정이다./스레드 생성 및 폐기 비용이 매우 큽니다.그래서 위의 패턴은 매우 바쁜 서버 프로그램에 적용할 수 없습니다.이에 대응한 개선판으로 이 문제를 해결한 것이 고전적인 Leader-Follower 모델이다.

코드 인스턴스:

프로그램이 시작되면 N개의 프로세스가 생성되는 것이 특징이다.각 하위 프로세스가 Accept에 들어가고 새로운 연결이 들어오기를 기다립니다.클라이언트가 서버에 접속하면, 하위 프로세스 중 하나가 깨워지고, 클라이언트 요청 처리가 시작되고, 새로운 TCP 연결을 받아들이지 않습니다.이 연결이 닫히면 하위 프로세스가 해제되고 Accept로 다시 들어가 새로운 연결을 처리하는 데 참여합니다.

이 모델의 장점은 프로세스를 완전히 재사용할 수 있고, 추가 소모 없이 매우 성능이 좋다.많은 일반적인 서버 프로그램은 Apache, PHP-FPM과 같은 이 모델을 기반으로 합니다.

멀티프로세스 모델도 일부 단점이 있다.

이 모델은 프로세스의 수에 크게 의존하여 동시 문제를 해결합니다. 클라이언트 연결 시 프로세스를 점유해야 합니다. 작업 프로세스의 개수는 얼마이고 동시 처리 능력은 얼마입니다.운영체제가 만들 수 있는 프로세스 수는 제한적이다.
많은 양의 프로세스를 시작하면 추가적인 프로세스 스케줄 소모가 발생할 수 있다.수백 개의 프로세스가 있을 때 가능한 프로세스의 컨텍스트 전환 스케줄링 소모가 CPU의 1% 미만이면 무시해도 되고, 수천에서 수만 개의 프로세스가 가동되면 소모가 수직 상승한다.스케줄링 소모는 CPU의 수십~100%를 차지할 수 있다.
또한 일부 장면의 멀티프로세스 모델은 해결할 수 없습니다. 예를 들어 인스턴트 메신저(IM)는 한 대의 서버가 동시에 수만에서 수십만 이상의 백만 연결을 유지해야 합니다(클래식 C10K 문제). 멀티프로세스 모델은 힘에 부칩니다.

또 다른 시나리오는 멀티프로세스 모델의 약점이다.보통 웹 서버는 100개의 프로세스를 시작하는데, 하나의 요청으로 100ms를 소모하면 100개의 프로세스가 1000qps를 제공할 수 있어 처리 능력이 좋다.하지만 요청 내 외부 사이트인 Http 인터페이스를 호출해 QQ, 웨이보처럼 로그인을 하려면 한 요청당 10s가 걸리는 긴 시간이 걸린다.그 프로세스는 1초에 0.1개의 요청만을 처리할 수 있고, 100개의 프로세스는 10qps밖에 처리할 수 없는 처리 능력이 너무 떨어진다.

하나의 프로세스 내에서 모든 동시 IO를 처리할 수 있는 기술이 있을까요?답은 있다.

IO 다중화/ 이벤트 루프/ 비동기 비차단
사실 IO 다중의 역사는 여러 프로세스만큼 길며 리눅스는 일찍이 select 시스템 콜을 제공해 한 프로세스 내에서 1024개의 연결을 유지할 수 있다.이후 폴 시스템 콜이 추가돼 폴은 1024 제한 문제를 해결하고 임의의 수의 연결을 유지할 수 있도록 개선했다.근데 셀렉트/폴이 또 하나 문제가 있어요접속에 이벤트가 있는지 순환적으로 체크해야 한다는 것이다.이 문제는 서버에 100만 개의 접속이 있을 경우, 어느 시점에 한 개의 접속만이 서버에 데이터를 송신하는 select/폴은 100만 사이클을 해야 돼요.이 중 1회만 명중했고 나머지 99만9999회는 무효로 CPU 자원을 낭비했다.

리눅스 2.6 커널이 새로운 epoll 시스템 콜을 제공해 무제한 접속이 가능하며 폴링 없이도 C10K 문제를 해결할 수 있었다.현재 각종 고동시 비동기 IO의 서버 프로그램은 epoll을 기반으로 구현되고 있는데, 예를 들어 Nginx, Node.js, Erlang, Golang이다.Node.js와 같은 단일 프로세스 단일 스레드의 프로그램은 모두 1백만 TCP 연결을 유지할 수 있는데, 모두 epoll 기술 덕분이다.

IO는 비동기 비차단 프로그램을 다중화하여 고전적인 Reactor 모델을 사용하는데, Reactor는 말 그대로 원자로를 의미하며 그 자체로 어떠한 데이터 송수신을 처리하지 않는다.다만 하나의 소켓 핸들의 사건 변화를 감시할 수 있다

Reactor에는 네 가지 핵심 작업이 있습니다.

add 소켓 추가 reactor, listen socket이나 클라이언트 socket, 파이프라인, eventfd, 신호 등
set 이벤트 감청 수정, 감청 설정 가능유형, 예를 들어 읽을 수 있고 쓸 수 있습니다.리슨 소켓은 새로운 클라이언트가 접속되어 accept가 필요한 것으로 읽힌다.클라이언트 연결은 데이터를 수신하기 때문에 rec가 필요합니다.v.사건을 쓰기는 좀 어렵다.하나의 SOCKET은 캐시 영역이 있으며 클라이언트에 2M의 데이터를 연결해서 보내려면 한 번에 보낼 수 없습니다. 운영 체제의 기본 TCP 캐시 영역은 256K에 불과합니다.한 번에 256K만 보낼 수 있고, 버퍼가 꽉 차면 send가 EAGAIN 에러로 돌아온다.이때 쓰기 가능한 이벤트를 감청해야 하는데, 비동기 프로그래밍에서는 쓰기 가능한 이벤트를 감청해야 send 동작이 완전히 차단되지 않습니다.
del을 reactor에서 제거, 더 이상 사용하지 않음사건을 감청하다
콜백은 사건 발생 후 대응처리 논리는 일반적으로 add/set 시에 작성된다.C언어는 함수 포인터로 구현되며 JS는 익명함수, PHP는 익명함수, 객체 메소드 배열, 문자열 함수명으로 구현된다.
Reactor는 이벤트 발생기일 뿐,실제 socket 핸들에 대한 조작은 connect/accept, send/recv, close와 같은 callback에서 이루어졌다.구체적인 부호화는 아래의 유사점을 참조할 수 있다코드:


Reactor 모델은 멀티프로세스,멀티스레드 결합은 비동기 비블록킹 IO를 구현하고 멀티코어에 활용한다.현재 유행하고 있는 비동기 서버 프로그램은 다음과 같은 방식이다.

Nginx:멀티프로세스Reactor
Nginx+Lua:멀티프로세스Reac토르+프로토콜
Golang: 싱글 스레드 Reactor+ 다중 스레드 프로토콜
Swoole:멀티스레드Reactor+멀티프로세스Worker

4. PHP 소켓 내부 소스

PHP 내부 소스PHP가 제공하는 소켓 프로그래밍은 소켓, bind, listen 등의 함수에 레이어를 추가해 보다 쉽고 편리하게 호출할 수 있도록 하는 것이다.그러나 일부 업무 논리적인 절차는 프로그래머가 스스로 수행해야 한다.
다음은 socket_create소스코드 구현은 PHP의 내부 구현을 설명한다.
앞에서 저희가 php 얘기했던 소켓은확장된 방식으로 구현되었습니다.원본의 ext 디렉터리에서 우리는 sockets 디렉터리를 찾았다.이 카탈로그에는 PHP의 소켓 구현이 저장되어 있다.직접 PHP_FUNCTION(socket_create) 을 검색하여 sockets.c 파일에서 찾았습니다.이 함수의 구현입니다. 다음과 같은 코드가 있습니다

/* {{{ proto resource socket_create(int domain, int type, int protocol) U
   Creates an endpoint for communication in the domain specified by domain, of type specified by type */
PHP_FUNCTION(socket_create)
{
        long            arg1, arg2, arg3;
        php_socket      *php_sock = (php_socket*)emalloc(sizeof(php_socket));
 
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) {
                efree(php_sock);
                return;
        }
 
        if (arg1 != AF_UNIX
#if HAVE_IPV6
                && arg1 != AF_INET6
#endif
                && arg1 != AF_INET) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket domain [%ld] specified for argument 1, assuming AF_INET", arg1);
                arg1 = AF_INET;
        }
 
        if (arg2 > 10) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket type [%ld] specified for argument 2, assuming SOCK_STREAM", arg2);
                arg2 = SOCK_STREAM;
        }
 
        php_sock->bsd_socket = socket(arg1, arg2, arg3);
        php_sock->type = arg1;
 
        if (IS_INVALID_SOCKET(php_sock)) {
                SOCKETS_G(last_error) = errno;
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC));
                efree(php_sock);
                RETURN_FALSE;
        }
 
        php_sock->error = 0;
        php_sock->blocking = 1;
                                                                                                                                           1257,1-8      61%
        ZEND_REGISTER_RESOURCE(return_value, php_sock, le_socket);
}
/* }}} */

Zend API는 실제로 c함수 socket을 PHP용으로 포장했다. c의 socket 프로그래밍에서는 다음과 같은 방식으로 socket을 초기화한다

//Socket 초기화
    if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){  
         printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
         exit(0);  
    }

5. 소켓 함수

함수명 설명
socket_accept() 하나 받기소켓 연결
socket_bind() 소케를t는 IP 주소와 포트에 t
socket_clear_error() 소켓 오류나 마지막 오류 코드 지우기
socket_close() s 닫기ocket 리소스
socket_connect() 시작개별 소켓 연결
socket_create_listen() 지정한 포트에 socket을 엽니다
socket_create_pair() 차이가 없는 socket 쌍을 배열로 만듭니다.
socket_create() 생성socket, 하나의 socket이 생성되는 데이터 구조
socket_get_option( ) socket 옵션 가져오기
socket_getpeername() 원격 유사한 호스트의 IP 주소 가져오기
socket_getsockname() 로컬 소켓의 IP 주소 가져오기
socket_iovec_add() 추가분산/집약 배열에 새 벡터를 추가합니다.
socket_iovec_alloc() 이 함수는 읽고 쓸 수 있는 iovec 데이터 구조를 만듭니다
socket_iovec_delete(디레트)) 할당된 iovec 삭제
socket_iovec_fetch() 지정한 iovec 리소스의 데이터를 반환합니다.
socket_iovec_free() iovec 리소스 풀기
socket_iovec_set() 설정iovec의 새 데이터 값 설정
socket_last_error() 현재 소켓의 마지막 오류 코드 가져오기
socket_listen() 수신인socket의 모든 연결을 설정합니다.
socket_read() 지정한 길이 읽기데이터
socket_readv() 읽기- 분산/ 배열에서 가져온 데이터를 취합합니다.
socket_recv() 종소크t에서 캐시로 데이터 끝내기
socket_recvfrom() 수락지정한 socket에서 데이터를 가져옵니다. 지정하지 않으면 현재 socke를 기본값으로 합니다.T
socket_recvmsg() io에서vec에서 메시지 받기
socket_selectt() 다중 선택
socket_send() 이 함수 보내기연결된 socket에 데이터 보내기
socket_sendmsg() 취소소켓에 숨기기
socket_sendto() 메시지 보내기지정한 주소로 socket
socket_set_block( ) 있음socket에서 블록 모드로 설정
socket_set_nonblock(온블록)) socket에서 비블록 모드로 설정
socket_set_option( ) socket 옵션 설정
socket_shutdown() 이거함수를 사용하면 읽기, 쓰기 또는 지정한 socket을 닫을 수 있습니다
socket_strerror() 반환오류 번호 지정 세부 오류
socket_write() s로 데이터 쓰기ocket 캐시
socket_writev() 데이터 쓰기분산/중합 배열

6. PHP 소케t 시뮬레이션 요청

우리는 stream_s를 사용합니다.ocket으로 시뮬레이션을 수행합니다.:

/**
 * 
 * @param $data= array=array('key'=>value)
 */
function post_contents($data = array()) {
    $post = $data ? http_build_query($data) : '';
    $header = "POST /test/ HTTP/1.1" . "\n";
    $header .= "User-Agent: Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)" . "\n";
    $header .= "Host: localhost" . "\n";
    $header .= "Accept: */*" . "\n";
    $header .= "Referer: http://localhost/test/" . "\n";
    $header .= "Content-Length: ". strlen($post) . "\n";
    $header .= "Content-Type: application/x-www-form-urlencoded" . "\n";
    $header .= "\r\n";
    $ddd = $header . $post;
    $fp = stream_socket_client("tcp://localhost:80", $errno, $errstr, 30);
    $response = '';
    if (!$fp) {
        echo "$errstr ($errno)<br />\n";
    } else {
        fwrite($fp, $ddd);
        $i = 1;
        while ( !feof($fp) ) {
            $r = fgets($fp, 1024);
            $response .= $r;
            //이 행을 처리하다
        }
    }
    fclose($fp);
    return $response;
}

위의 절차는 사순환에 이를 수 있음에 주의한다.
이 PHP의 feof($fp) 필요주의해야 할 점이 있다면 왜 사순환에 빠졌는지 살펴보자

        while ( !feof($fp) ) {
            $r = fgets($fp, 1024);
            $response .= $r;
        }

사실 feof는 믿을 만하지만 fgets 함수를 조합해 사용할 때는 조심해야 한다.한 가지 일반적인 방법은 다음과 같다

$fp = fopen("myfile.txt", "r");
while (!feof($fp)) {
   $current_line = fgets($fp);
   //결과에 대해 진일보한 처리를 하여, 사순환으로 들어가는 것을 방지하다
}

일반 텍스트를 처리할 때 fgets가 마지막 문자를 획득하면 foef 함수가 반환하는 결과는 TRUE가 아니다.실제 연산 과정은 다음과 같다.
1) while( )은(는) 루프를 계속합니다.

2) fgets에서 마지막 두 번째 줄의 문자열을 가져옵니다

3) feof는 false를 반환하고 다음 루프에 들어갑니다.

4) fgets 마지막 데이터 가져오기

5) fegets 함수가 호출되면 feof 함수는 false를 반환한다.그래서 계속 루프를 하는 거예요

6) fget은 다른 줄을 얻으려 했지만 결과는 비어 있었다.실제 코드는 이를 의식하지 않고 아예 존재하지 않는 다른 행을 처리하려고 시도하지만fgets가 호출되어 feof가 다시 놓였다.결과는 여전히 false

7) .....

8) 사순환에 빠지다

반응형

'개발 꿀팁 > PHP' 카테고리의 다른 글

php 표준 입출력  (0) 2022.07.12
PHP 엔트리-환경 구축  (0) 2022.07.12
PHP의 Trait 상세설명  (0) 2022.07.12
[CTF]PHP 역직렬화 총결산  (0) 2022.07.12
프로그래밍 기술 L 리눅스 설치 PHP7.3.0  (0) 2022.07.12