Websocket trên Laravel
Giới thiệu chung Websocket là một khái niệm khá quen thuộc với hầu hết các developer. Nó là công nghệ hỗ trợ giao tiếp giữa client và server bằng cách sử dụng một TCP socket để tạo một kết nối hiệu quả. Mặc dù được thiết kế chuyên sử dụng cho các ứng dụng web, lập trình viên vẫn có thể đưa chúng ...
Giới thiệu chung
Websocket là một khái niệm khá quen thuộc với hầu hết các developer. Nó là công nghệ hỗ trợ giao tiếp giữa client và server bằng cách sử dụng một TCP socket để tạo một kết nối hiệu quả. Mặc dù được thiết kế chuyên sử dụng cho các ứng dụng web, lập trình viên vẫn có thể đưa chúng vào bất kì loại ứng dụng nào khác.
Ưu điểm
WebSockets cung cấp khả năng giao tiếp hai chiều mạnh mẽ, có độ trễ thấp và dễ xử lý lỗi. Không cần phải có nhiều kết nối như phương pháp Comet long-polling và cũng không có những nhược điểm như Comet streaming. API cũng rất dễ sử dụng trực tiếp mà không cần bất kỳ các tầng bổ sung nào, so với Comet, thường đòi hỏi một thư viện tốt để xử lý kết nối lại, thời gian chờ timeout, các Ajax request (yêu cầu Ajax), các tin báo nhận và các dạng truyền tải tùy chọn khác nhau (Ajax long-polling và jsonp polling).
Nhược điểm
Những nhược điểm của WebSockets gồm có:
Nó là một đặc tả mới của HTML5, nên nó vẫn chưa được tất cả các trình duyệt hỗ trợ.
Không có phạm vi yêu cầu nào. Do WebSocket là một TCP socket chứ không phải là HTTP request, nên không dễ sử dụng các dịch vụ có phạm vi-yêu cầu, như SessionInViewFilter của Hibernate. Hibernate là một framework kinh điển cung cấp một bộ lọc xung quanh một HTTP request.
Khi bắt đầu một request, nó sẽ thiết lập một contest (chứa các transaction và liên kết JDBC) được ràng buộc với luồng request. Khi request đó kết thúc, bộ lọc hủy bỏ contest này.
Vậy là tôi đã giới thiệu tổng quan cho các bạn về websocket Những ưu điểm và nhược điểm khi sử dụng websocket.
Bây giờ tôi sẽ hướng dẫn các bạn sử dụng websocket trong Laravel.
Cách install Laravel các bạn có thể tham khảo ở bài viết này: https://viblo.asia/phamkykhoi/posts/ZK1ov1VxR5b9
Trong thư mục root của project các bạn tạo ra một file server.php như sau
<?php $host = 'localhost'; //host $port = '80'; //port $null = null; //null var //Create TCP/IP sream socket $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); //reuseable port socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); //bind socket to specified host socket_bind($socket, $host, $port); //listen to port socket_listen($socket); //create & add listning socket to the list $clients = [$socket]; //start endless loop, so that our script doesn't stop while (true) { //manage multipal connections $changed = $clients; //returns the socket resources in $changed array socket_select($changed, $null, $null, 0, 10); //check for new socket if (in_array($socket, $changed)) { $socket_new = socket_accept($socket); //accpet new socket $clients[] = $socket_new; //add socket to client array $header = socket_read($socket_new, 1024); //read data sent by the socket perform_handshaking($header, $socket_new, $host, $port); //perform websocket handshake socket_getpeername($socket_new, $ip); //get ip address of connected socket $response = mask(json_encode(['type' => 'system', 'message' => $ip . ' connected'])); //prepare json data send_message($response); //notify all users about new connection //make room for new socket $found_socket = array_search($socket, $changed); unset($changed[$found_socket]); } //loop through all connected sockets foreach ($changed as $changed_socket) { //check for any incomming data while (socket_recv($changed_socket, $buf, 1024, 0) >= 1) { $received_text = unmask($buf); //unmask data $tst_msg = json_decode($received_text); //json decode $user_name = $tst_msg->name; //sender name $user_message = $tst_msg->message; //message text $user_color = $tst_msg->color; //color //prepare data to be sent to client $response_text = mask(json_encode(['type' => 'usermsg', 'name' => $user_name, 'message' => $user_message, 'color' => $user_color])); send_message($response_text); //send data break 2; //exist this loop } $buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ); if ($buf === false) { // check disconnected client // remove client for $clients array $found_socket = array_search($changed_socket, $clients); socket_getpeername($changed_socket, $ip); unset($clients[$found_socket]); //notify all users about disconnected connection $response = mask(json_encode(['type' => 'system', 'message' => $ip . ' disconnected'])); send_message($response); } } } // close the listening socket socket_close($sock); function send_message($msg) { global $clients; foreach ($clients as $changed_socket) { @socket_write($changed_socket, $msg, strlen($msg)); } return true; } //Unmask incoming framed message function unmask($text) { $length = ord($text[1]) & 127; if ($length == 126) { $masks = substr($text, 4, 4); $data = substr($text, 8); } elseif ($length == 127) { $masks = substr($text, 10, 4); $data = substr($text, 14); } else { $masks = substr($text, 2, 4); $data = substr($text, 6); } $text = ""; for ($i = 0; $i < strlen($data); ++$i) { $text .= $data[$i] ^ $masks[$i % 4]; } return $text; } //Encode message for transfer to client. function mask($text) { $b1 = 0x80 | (0x1 & 0x0f); $length = strlen($text); if ($length <= 125) $header = pack('CC', $b1, $length); elseif ($length > 125 && $length < 65536) $header = pack('CCn', $b1, 126, $length); elseif ($length >= 65536) $header = pack('CCNN', $b1, 127, $length); return $header . $text; } //handshake new client. function perform_handshaking($receved_header, $client_conn, $host, $port) { $headers = []; $lines = preg_split("/ /", $receved_header); foreach ($lines as $line) { $line = chop($line); if (preg_match('/A(S+): (.*)z/', $line, $matches)) { $headers[$matches[1]] = $matches[2]; } } $secKey = $headers['Sec-WebSocket-Key']; $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); //hand shaking header $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake " . "Upgrade: websocket " . "Connection: Upgrade " . "WebSocket-Origin: $host " . "WebSocket-Location: ws://$host:$port/server.php " . "Sec-WebSocket-Accept:$secAccept "; socket_write($client_conn, $upgrade, strlen($upgrade)); }
Tiếp theo các bạn tạo một form chat đơn giản như sau
resourcesviewschat.blade.php
<!DOCTYPE html> <html> <head> <meta charset='UTF-8'/> <style type="text/css"> .chat_wrapper { awidth: 500px; margin-right: auto; margin-left: auto; background: #CCCCCC; border: 1px solid #999999; padding: 10px; font: 12px 'lucida grande', tahoma, verdana, arial, sans-serif; } .chat_wrapper .message_box { background: #FFFFFF; height: 150px; overflow: auto; padding: 10px; border: 1px solid #999999; } .chat_wrapper .panel input { padding: 2px 2px 2px 5px; } .system_msg { color: #BDBDBD; font-style: italic; } .user_name { font-weight: bold; } .user_message { color: #88B6E0; } </style> </head> <body> <div class="chat_wrapper"> <div class="message_box" id="message_box"></div> <div class="panel"> <input type="text" name="name" id="name" placeholder="Your Name" maxlength="10" style="awidth:20%"/> <input type="text" name="message" id="message" placeholder="Message" maxlength="80" style="awidth:60%"/> <button id="send-btn">Send</button> </div> </div> </body> </html>
Tiếp theo các bạn tạo một file
publicchat.js
$(document).ready(function () { //create a new object WebSocket // Ở đây domain VirtualHost tôi để là laravel.dev var wsUri = "ws://laravel.dev/server.php"; websocket = new WebSocket(wsUri); websocket.onopen = function (ev) { // connection is open $('#message_box').append("<div class="system_msg">Connected!</div>"); //notify user } $('#send-btn').click(function () { //use clicks message send button var mymessage = $('#message').val(); //get message text var myname = $('#name').val(); //get user name if (myname == "") { //empty name? alert("Enter your Name please!"); return; } if (mymessage == "") { //emtpy message? alert("Enter Some message Please!"); return; } //prepare json data var msg = { message: mymessage, name: myname, color: '<?php echo $colours[$user_colour]; ?>' }; //convert and send data to server websocket.send(JSON.stringify(msg)); }); // Return message from server websocket.onmessage = function (ev) { var msg = JSON.parse(ev.data); //PHP sends Json data var type = msg.type; //message type var umsg = msg.message; //message text var uname = msg.name; //user name var ucolor = msg.color; //color if (type == 'usermsg' && umsg != null) { $('#message_box').append("<div><span class="user_name" style="color:#" + ucolor + "">" + uname + "</span> : <span class="user_message">" + umsg + "</span></div>"); } if (type == 'system' && umsg != null) { $('#message_box').append("<div class="system_msg">" + umsg + "</div>"); } $('#message').val('); //reset text }; websocket.onerror = function (ev) { $('#message_box').append("<div class="system_error">Error Occurred - " + ev.data + "</div>"); }; websocket.onclose = function (ev) { $('#message_box').append("<div class="system_msg">Connection Closed</div>"); }; });
Ở file resourcesviewschat.blade.php các bạn nhúng thêm file js vào tạo vào.
<script src='chat.js'></script>
Tiếp theo các bạn tạo một routes để chạy views chat của mình
Route::get('chat', function () { return view('chat'); });
Bây giờ các bạn chạy web lên với domain là http://laravel.dev/chat Sau đó các bạn mở command line lên. Truy xuất vào thư mục project của mình và chạy lệnh.
php server.php
Lúc này các bạn có thể sử dụng được ứng dụng chat của mình với websocket trên Laravel.
Ở bài này tôi chia sẻ cho các bạn cách để có thể chạy được websocket trên Laravel. Ở những bài sau tôi sẽ chia sẽ kỹ hơn các vấn đề trong websocket.
Chúc các bạn học tốt !