تابع stream_socket_recvfrom() در PHP
تابع stream_socket_recvfrom() یکی از توابع مفید و کماستفاده در PHP برای دریافت داده از یک استریم شبکه (TCP/UDP یا دیگر پروتکلهای مبتنی بر استریم) است. این تابع بهویژه وقتی با سرورهای UDP یا ارتباطات دیتاگرامی کار میکنید و نیاز به دانستن آدرس فرستنده دارید، کاربردی است. در این مقاله به صورت دقیق پارامترها، مقدار بازگشتی، نمونههای عملی و نکات پیشرفته را بررسی میکنیم.
معرفی و امضاء تابع
امضاء کلی تابع به صورت زیر است:
string|false stream_socket_recvfrom ( resource $socket , int $length [, int $flags = 0 [, string &$remote_address ]] )توضیحات کوتاه:
- $socket: یک resource که معمولاً از stream_socket_client یا stream_socket_server به دست میآید.
- $length: حداکثر تعداد بایتهایی که میخواهیم دریافت کنیم.
- $flags: پرچمهای اختیاری که رفتار دریافت را تغییر میدهند (مثلاً مشابه MSG_PEEK یا MSG_OOB در لایه سوکت؛ پیادهسازی و پشتیبانی بسته به سیستم عامل متفاوت است).
- $remote_address: پارامتر ارسالی-مرجعی که در صورت وجود آدرس فرستنده را (مثلاً “127.0.0.1:12345”) برمیگرداند.
مقدار بازگشتی و رفتارهای مهم
- در موفقیت، یک رشته حاوی دادههای دریافتشده بازگردانده میشود.
- در صورت خطا، مقدار false برگشت داده میشود.
- برای ارتباطات TCP اگر اتصال توسط طرف مقابل بسته شده باشد، تابع رشتهٔ خالی (“”) بازمیگرداند که باید به معنی EOF تفسیر شود.
- برای پروتکلهای دیتاگرامی مثل UDP، $remote_address معمولاً حاوی آدرس فرستنده است.
- اگر طول داده از $length بیشتر باشد، با توجه به سیستم عامل، دادهها ممکن است تا آن طول بریده شوند و بقیه دور ریخته شود.
نمونه عملی: سرور UDP ساده
<?php
$addr = "udp://0.0.0.0:12345";
$socket = stream_socket_server($addr, $errno, $errstr, STREAM_SERVER_BIND);
if (!$socket) {
die("Failed to bind: $errstr ($errno)n");
}
while (true) {
$data = stream_socket_recvfrom($socket, 1500, 0, $peer);
if ($data === false) {
// خطای دریافت
continue;
}
echo "Received from $peer: $datan";
// پاسخ به همان آدرس با stream_socket_sendto
stream_socket_sendto($socket, "ACK", 0, $peer);
}
?>توضیح: این کد یک سرور UDP ساده روی پورت 12345 ایجاد میکند. در حلقهٔ بینهایت، با stream_socket_recvfrom داده را دریافت کرده و آدرس فرستنده را در متغیر $peer قرار میدهد. سپس با stream_socket_sendto پاسخی به همان آدرس میفرستد. توجه داشته باشید طول 1500 بایت انتخاب شد که معادل MTU تقریبی است تا از برش جلوگیری کنیم.
نمونه: کار با TCP و تشخیص EOF
<?php
$server = stream_socket_server("tcp://0.0.0.0:23456", $errno, $errstr);
if (!$server) die("Error: $errstrn");
$client = stream_socket_accept($server);
if ($client) {
while (!feof($client)) {
$data = stream_socket_recvfrom($client, 4096);
if ($data === false) break;
if ($data === "") {
// اتصال توسط کلاینت بسته شده
break;
}
echo "Got: $datan";
}
fclose($client);
}
fclose($server);
?>توضیح: در ارتباط TCP، تابع ممکن است رشتهٔ خالی برگرداند که نشاندهندهٔ بسته شدن اتصال از سمت مقابل است. بنابراین باید به صراحت این حالت را تشخیص داده و اتصال را ببندید.
پرچمها، غیرمسدود (non-blocking) و زمانبندی
- برای کار در حالت غیرمسدود از stream_set_blocking($socket, false) استفاده کنید. در این حالت stream_socket_recvfrom ممکن است بلافاصله رشتهٔ خالی یا false بازگرداند اگر دادهای آماده نباشد.
- برای انتظار قابلکنترل از stream_select برای بررسی readability استفاده نمایید تا از مصرف CPU بالا جلوگیری شود.
- برای تعیین تایماوت سطحی میتوانید stream_set_timeout یا stream_context_set_option استفاده کنید.
مقایسه خلاصه با توابع مشابه
| تابع | موارد استفاده | نکته |
|---|---|---|
| stream_socket_recvfrom() | دریافت از استریمها، مناسب برای UDP که نیاز به آدرس فرستنده باشد | آدرس فرستنده را میتواند برگرداند |
| socket_recvfrom() | API سوکت سطح پایین با کنترل بیشتر | نیاز به ایجاد سوکت با socket_create دارد |
| fread() | خواندن ساده از استریمهای بایتی، معمولاً برای فایل یا TCP | آدرس فرستنده را برنمیگرداند |
نکات حرفهای و تلههای رایج
- برای UDP همیشه به احتمال برش (truncation) توجه کنید؛ اگر انتظار پیامهای بزرگی دارید، طول مناسب تعیین کنید یا از پروتکل لایهٔ اپلیکیشن برای بخشبندی استفاده کنید.
- در حالت non-blocking از stream_select یا حلقههای بازجویی همراه با usleep استفاده کنید تا مصرف CPU کاهش یابد.
- در صورتی که پرچمهایی مانند MSG_PEEK استفاده میکنید، مطمئن شوید سیستم عامل و نسخهٔ PHP شما آنها را پشتیبانی میکند؛ رفتار در سیستمعاملهای مختلف تفاوت دارد.
- برای برنامههای با بار زیاد یا چند کانکشن همزمان از event-loop یا کتابخانههایی مانند ReactPHP استفاده کنید تا کارایی بهتری داشته باشید.
نمونه بهبود یافته: خواندن امن با stream_select
<?php
$socket = stream_socket_server("udp://0.0.0.0:12345", $errno, $errstr, STREAM_SERVER_BIND);
stream_set_blocking($socket, false);
while (true) {
$read = [$socket];
$write = $except = null;
$tv_sec = 5;
$num = stream_select($read, $write, $except, $tv_sec);
if ($num === false) {
// خطای select
break;
} elseif ($num > 0) {
$data = stream_socket_recvfrom($socket, 1500, 0, $peer);
if ($data !== false && $data !== "") {
echo "From $peer: $datan";
}
} else {
// تایماوت - هیچ دادهای دریافت نشد
// کارهای دورهای را اینجا انجام دهید
}
}
?>توضیح: این نسخه از stream_select استفاده میکند تا تنها زمانی که سوکت خواندنی است، اقدام به recv کند. این الگو برای برنامههای سروری که باید همزمان چند کار انجام دهند مناسب است و از busy-loop جلوگیری میکند.
جمعبندی
تابع stream_socket_recvfrom ابزار ساده و قدرتمندی برای دریافت داده از استریمهای شبکهای است، بهویژه هنگام کار با UDP و نیاز به دانستن آدرس فرستنده. با درک رفتارهای خاص مانند رشتهٔ خالی برای EOF، نحوهٔ تنظیم حالت non-blocking، و استفاده از stream_select میتوانید سرورهای مقاومتر و بهینهتری بسازید. همواره تست بر روی سیستم عامل مقصد را فراموش نکنید تا از سازگاری پرچمها و رفتارهای خاص اطمینان حاصل کنید.
آیا این مطلب برای شما مفید بود ؟



