1. ๊ฐ์
ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค์๊ฐ ํต์ ์ ํ์์ ์ธ ์์๊ฐ ๋์๋ค. ๊ฐ ๊ธฐ์ ์ ํน์ง๊ณผ ์ ์ ํ ์ฌ์ฉ ์๋๋ฆฌ์ค๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํ๋ค.
graph LR A[์ค์๊ฐ ํต์ ๊ธฐ์ ] --> B[Regular Polling] A --> C[Long Polling] A --> D[Server-Sent Events] A --> E[WebSocket] B -->|๋จ๋ฐฉํฅ| F[HTTP ๊ธฐ๋ฐ] C -->|๋จ๋ฐฉํฅ| F D -->|์๋ฒ->ํด๋ผ์ด์ธํธ| F E -->|์๋ฐฉํฅ| G[TCP ๊ธฐ๋ฐ]
2. ๊ฐ ๊ธฐ์ ๋ณ ํน์ง
2.1 Regular Polling
์๋ ๋ฐฉ์
sequenceDiagram participant Client participant Server loop Every N seconds Client->>+Server: HTTP ์์ฒญ Server-->>-Client: ์ฆ์ ์๋ต end
ํน์ง
- ๊ฐ์ฅ ๋จ์ํ ๊ตฌํ
- ์ฃผ๊ธฐ์ ์ธ HTTP ์์ฒญ
- ๋ถํ์ํ ์์ฒญ ๋ฐ์
- ์๋ฒ ๋ถํ ๋์
์ฝ๋ ์์
// routes/api.php
Route::get('/notifications', [NotificationController::class, 'getLatest']);
// app/Http/Controllers/NotificationController.php
class NotificationController extends Controller
{
public function getLatest()
{
$notifications = Auth::user()
->notifications()
->where('created_at', '>', now()->subMinutes(5))
->get();
return response()->json($notifications);
}
}
// JavaScript
function startPolling() {
setInterval(async () => {
try {
const response = await axios.get('/api/notifications');
processNotifications(response.data);
} catch (error) {
console.error('Polling error:', error);
}
}, 3000);
}2.2 Long Polling
์๋ ๋ฐฉ์
sequenceDiagram participant Client participant Server Client->>+Server: HTTP ์์ฒญ Note right of Server: ๋ฐ์ดํฐ ๋๊ธฐ Server-->>-Client: ๋ฐ์ดํฐ ๋ฐ์ ์ ์๋ต Client->>+Server: ์ HTTP ์์ฒญ Note right of Server: ๋ฐ์ดํฐ ๋๊ธฐ
ํน์ง
- ์๋ฒ๊ฐ ์๋ต์ ์ง์ฐ
- ์ค์๊ฐ์ ๊ฐ๊น์ด ์๋ต
- ์ฐ๊ฒฐ ์ ์ง ๋น์ฉ
- HTTP ํธํ์ฑ ์ข์
์ฝ๋ ์์
// routes/api.php
Route::get('/messages', [MessageController::class, 'waitForNew']);
// app/Http/Controllers/MessageController.php
class MessageController extends Controller
{
public function waitForNew(Request $request)
{
$lastId = $request->input('last_id', 0);
$startTime = time();
$timeout = 20; // 20์ด ํ์์์
while (time() - $startTime < $timeout) {
$messages = Message::where('id', '>', $lastId)
->where('chat_id', $request->chat_id)
->get();
if ($messages->isNotEmpty()) {
return response()->json($messages);
}
sleep(1);
}
return response()->json([]); // ํ์์์ ์ ๋น ์๋ต
}
}// JavaScript
async function startLongPolling() {
try {
const lastMessageId = getLastMessageId(); // ๋ง์ง๋ง ๋ฉ์์ง ID ๊ฐ์ ธ์ค๊ธฐ
const response = await axios.get('/api/messages', {
params: { last_id: lastMessageId }
});
if (response.data.length > 0) {
processMessages(response.data);
}
startLongPolling(); // ์ฆ์ ๋ค์ ์์ฒญ
} catch (error) {
setTimeout(startLongPolling, 5000); // ์๋ฌ ์ ์ฌ์๋
}
}2.3 Server-Sent Events (SSE)
์๋ ๋ฐฉ์
sequenceDiagram participant Client participant Server Client->>+Server: EventSource ์ฐ๊ฒฐ Server-->>Client: ์ด๋ฒคํธ ์คํธ๋ฆผ ์์ loop ์๋ฒ ํธ์ Server->>Client: ์ด๋ฒคํธ ๋ฐ์ดํฐ Server->>Client: ์ด๋ฒคํธ ๋ฐ์ดํฐ end
ํน์ง
- ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ๋จ๋ฐฉํฅ ํต์
- ์๋ ์ฌ์ฐ๊ฒฐ ์ง์
- ํ์ค HTML5 ๊ธฐ์
- ํ ์คํธ ๊ธฐ๋ฐ ๋ฉ์์ง
์ฝ๋ ์์
// routes/web.php
Route::get('/events', [EventController::class, 'stream']);
// app/Http/Controllers/EventController.php
class EventController extends Controller
{
public function stream()
{
return response()->stream(function() {
while (true) {
// ์๋ก์ด ์ด๋ฒคํธ ํ์ธ
$events = Event::where('created_at', '>', now()->subSeconds(2))->get();
if ($events->isNotEmpty()) {
echo "data: " . json_encode($events) . "\n\n";
ob_flush();
flush();
}
sleep(2);
}
}, 200, [
'Cache-Control' => 'no-cache',
'Content-Type' => 'text/event-stream',
'Connection' => 'keep-alive',
'X-Accel-Buffering' => 'no'
]);
}
}// JavaScript
const eventSource = new EventSource('/events');
eventSource.onmessage = (event) => {
const events = JSON.parse(event.data);
processEvents(events);
};
eventSource.onerror = (error) => {
console.error('SSE ์๋ฌ:', error);
eventSource.close();
};2.4 WebSocket
์๋ ๋ฐฉ์
sequenceDiagram participant Client participant Server Client->>Server: WebSocket ์ฐ๊ฒฐ ์์ฒญ Server-->>Client: ์ฐ๊ฒฐ ์น์ธ rect rgb(240, 240, 240) Note over Client,Server: ์๋ฐฉํฅ ์ค์๊ฐ ํต์ Client->>Server: ๋ฉ์์ง ์ ์ก Server->>Client: ๋ฉ์์ง ์ ์ก end
ํน์ง
- ์์ ํ ์๋ฐฉํฅ ํต์
- ๋ฎ์ ์ง์ฐ ์๊ฐ
- ์ด์ง ๋ฐ์ดํฐ ์ง์
- ๋ณ๋์ ํ๋กํ ์ฝ ์ฌ์ฉ
์ฝ๋ ์์
// config/broadcasting.php
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http'
],
],
],
// app/Events/MessageSent.php
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public function __construct(Message $message)
{
$this->message = $message;
}
public function broadcastOn()
{
return new PrivateChannel('chat.'.$this->message->chat_id);
}
}
// app/Http/Controllers/ChatController.php
class ChatController extends Controller
{
public function sendMessage(Request $request)
{
$message = Message::create([
'user_id' => Auth::id(),
'chat_id' => $request->chat_id,
'content' => $request->content
]);
broadcast(new MessageSent($message))->toOthers();
return response()->json($message);
}
}// JavaScript (Laravel Echo)
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true,
});
Echo.private(`chat.${chatId}`)
.listen('MessageSent', (e) => {
console.log('์ ๋ฉ์์ง:', e.message);
appendMessage(e.message);
});3. ๊ธฐ์ ๋น๊ต ๋ฐ ์ ํ ๊ธฐ์ค
| ๊ธฐ์ | ์๋ฐฉํฅ ํต์ | ์ค์๊ฐ์ฑ | ๋ฆฌ์์ค ์ฌ์ฉ | ๊ตฌํ ๋ณต์ก๋ | ๋ธ๋ผ์ฐ์ ์ง์ |
|---|---|---|---|---|---|
| Regular Polling | โ | ๋ฎ์ | ๋์ | ๋ฎ์ | ๋งค์ฐ ์ข์ |
| Long Polling | โ | ์ค๊ฐ | ์ค๊ฐ | ์ค๊ฐ | ๋งค์ฐ ์ข์ |
| SSE | ๋จ๋ฐฉํฅ | ๋์ | ๋ฎ์ | ๋ฎ์ | ์ข์ |
| WebSocket | โ | ๋งค์ฐ ๋์ | ๋ฎ์ | ๋์ | ์ข์ |
4. ์ฌ์ฉ ์๋๋ฆฌ์ค๋ณ ์ถ์ฒ ๊ธฐ์
4.1 ์ค์๊ฐ ์ฑํ
- 1์์: WebSocket
- 2์์: Long Polling
- ์ด์ : ์๋ฐฉํฅ ํต์ , ๋ฎ์ ์ง์ฐ ์๊ฐ ํ์
4.2 ์๋ฆผ ์์คํ
- 1์์: SSE
- 2์์: Long Polling
- ์ด์ : ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก์ ๋จ๋ฐฉํฅ ํต์ ๋ง ํ์
4.3 ์ค์๊ฐ ๋์๋ณด๋
- 1์์: Regular Polling
- 2์์: SSE
- ์ด์ : ์ฃผ๊ธฐ์ ์ ๋ฐ์ดํธ๋ง ํ์, ๊ตฌํ ๋จ์์ฑ
4.4 ์ค์๊ฐ ๊ฒ์
- 1์์: WebSocket
- ์ด์ : ๋น ๋ฅธ ์๋ฐฉํฅ ํต์ ํ์
5. ์ฃผ์์ฌํญ ๋ฐ ๊ณ ๋ ค์ฌํญ
5.1 ํ์ฅ์ฑ (Scalability)
- Regular Polling: ์๋ฒ ๋ถํ ์ฆ๊ฐ ๋น ๋ฆ
- Long Polling: ๋ง์ ์ฐ๊ฒฐ ์ ์ง๋ก ์๋ฒ ๋ถํ
- SSE: ์ฐ๊ฒฐ ์ ์ ํ ์์
- WebSocket: ์ฐ๊ฒฐ ์ํ ๊ด๋ฆฌ ํ์
5.2 ๋ณด์
- Regular/Long Polling: ํ์ค HTTP ๋ณด์
- SSE: CORS ์ ์ฑ ์ ์ฉ
- WebSocket: ๋ณ๋์ ๋ณด์ ์ค์ ํ์
5.3 ์์ ์ฑ
- ๋ชจ๋ ๊ธฐ์ : ๋คํธ์ํฌ ๋ถ์์ ์ฑ ๋๋น ํ์
- WebSocket/SSE: ์ฌ์ฐ๊ฒฐ ๋ฉ์ปค๋์ฆ ๊ตฌํ ์ค์
- Long Polling: ํ์์์ ์ค์ ์ค์
6. ๊ฒฐ๋ก
๊ฐ ๊ธฐ์ ์ ๊ณ ์ ํ ์ฅ๋จ์ ์ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ ์ ํ ๊ธฐ์ ์ ์ ํํด์ผ ํ๋ค:
- ๋จ์ํ ์ฃผ๊ธฐ์ ์ ๋ฐ์ดํธ โ Regular Polling
- ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก์ ์ค์๊ฐ ์๋ฆผ โ SSE
- ์์ ํ ์ค์๊ฐ ์๋ฐฉํฅ ํต์ โ WebSocket
- ์ค์๊ฐ์ฑ ํ์ + ๋ ๊ฑฐ์ ์์คํ โ Long Polling
๊ฐ์ฅ ์ค์ํ ๊ฒ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ตฌ์ฌํญ์ ์ ํํ ํ์ ํ๊ณ , ๊ทธ์ ๋ง๋ ๊ธฐ์ ์ ์ ํํ๋ ๊ฒ์ด๋ค.