[ENC28J60] Bài 14: Giao thức HTTP – Tạo Webserver với ENC28J60

Giao thức HTTP (Tiếng AnhHyperText Transfer Protocol – Giao thức truyền tải siêu văn bản) là một giao thức thuộc tầng ứng dụng ( lớp trên của giao thức TCP) nói đơn giản là nó hoạt động trên nền giao thức TCPnó chính là trường data của giao thức TCP

Mình đã có viết một bài về giao thức http, các bạn tham khảo tại đây: http://iot47.com/iot-bai-4-gioi-thieu-ngon-ngu-html-va-mo-hinh-http-resquest-reponse/

HTTP Request

Client gửi 1 bản tin tới Server, Server tiếp nhận, phân tích và trả về nội dung mà client muốn. 1 HTTP Request cơ bản gồm các request line và data. Request line có cấu trúc như sau:

Các request line sẽ được kết thúc bằng 2 lần xuống dòng (\r\n\r\n)

Trăm nghe không bằng 1 thấy, tại sao không test thử luôn nhỉ 🙂

Sử dụng project đã làm ở bài 13, chạy mô phỏng, mở trình duyệt, gõ địa chỉ IP của ENC28j60 ( như của mình là 192.168.1.197) các bạn sẽ thấy máy tính gửi lên 1 cái truy vấn tương tự như trên

Chúng ta sẽ nghiên cứu kĩ hơn về http request ở bài TCP Client, còn bài này đang làm TCP Server nên tập trung vào http reponse nhé

HTTP Reponse

Khi nhận 1 request và phân tích request, server đã biết client muốn gì, nó sẽ trả lời lại nội dung tương ứng cho server. 1 HTTP Reponse có cấu trúc cơ bản như sau:

Về cơ bản, chúng ta có thể rút gọn các Status Line xuống còn 2 dòng như sau:
HTTP/1.1 200 OK
Content-Type: text/html

HTTP/1.1 ám chỉ phiên bản HTTP, nó đã được phát triển lên 2.0. Nhưng chưa được đưa vào sử dụng
200 là Status code, ám chỉ server đã tiếp nhận và xử lí request của client thành công
Content-Type: text/html ám chỉ phần data mà server sẽ gửi thuộc loại mã html

Các status line sẽ được kết thúc bằng 2 lần xuống dòng (\r\n\r\n)

Và sau khi gửi xong các statusline thì chính là data của server gửi xuống dưới dạng các mã HTML

Ngôn ngữ HTML

Sau khi nhận được dữ liệu Server trả về (tức các đoạn code html), trình duyệt web sẽ phân tích các mã html đó và tạo ra giao diện hiển thị cho người dùng.

Ngôn ngữ HTML được tạo bởi các cặp thẻ, nó tạo ra khung sườn cho trang Web, kết hợp với CSS và JavaScript tạo nên bộ 3 tam kiếm hợp bích.

Cấu trúc cơ bản của 1 trang HTML

1 số thẻ thường dùng

Thẻ <h1></h1> tạo ra phần chữ tiêu đề ( chữ khá to và đậm)
Ví dụ

Đây là thẻ H1

Các bạn tự tìm hiểu thêm về ngôn ngữ html trên mạng nhé, chúng ta sẽ quay lại chủ đề chính ngày hôm nay

Tạo webserver với ECN28J60

Tiếp tục với code đã viết ở bài 13, tại chỗ

Các bạn xóa dòng phản hồi này đi vì nó không cần thiết nữa, thay vào đó, mình sẽ thêm 1 lệnh kiểm tra data gửi đến xem lệnh gửi đến có phải là truy vấn GET không, nếu đúng thì trả về http reponse cùng với 1 mã html đơn giản

Các bạn chạy mô phỏng và quay lại trình duyệt web để truy cập vào IP của enc28j60

Tuyệt ! 1 trang web siêu đơn giản đã được tạo ra !

Tạo thư viện webserver

Đó chỉ là 1 demo nhỏ thôi, để tạo 1 webserver hoàn chỉnh sẽ phức tạp hơn, vì 1 trang web lớn thì không thể gửi 1 phát xong ngay được mà phải chia nhỏ thành các gói tin để gửi lần lượt. Chúng ta sẽ làm điều đó trong 1 thư viện riêng nhé

Các bạn tạo 2 file thư viện http.chttp.h và add vào project

Thêm mã khởi tạo cho file http.h

Và mã khởi tạo cho file http.c

Tóm tắt quy trình khi bạn truy cập vào webserver

  • Client (tức trình duyệt) gửi bản tin connect (SYN) server trả lời và hoàn tất việc bắt tay
  • Client gửi http request tới server (PSH|ACK)
  • Server nhận được http reuqest và gửi trả mã html, do chip AVR có giới hạn RAM nên mình đã khởi tạo bộ đệm là 512 byte (trong net.c) , do vậy 1 lần gửi dữ liệu đi chỉ được tối đa 12 byte, lúc này sẽ có 3 trường hợp
    1. Gửi gói 1 lần xong luôn
    2. Gửi gói lần cuối
    3. Gửi gói n lần nữa mới đến lần cuối
  • Sau khi gửi xong chúng ta sẽ đóng kết nối với client

Chúng ta sẽ xử lí tất cả các trường hợp nhé, trước tiên mình phải tạo ra 1 giao diện web đã, mình sẽ sử dụng giao điện web đã viết ở tutorial IOT-ESP8266 bài 5 – nó có dạng hiển thị như này

Còn đây là mã nguồn của nó

Trong mã trên mình có xài kí tự tiếng việt UTF-8, mà phần mềm AVRCodevision không hỗ trợ editer utf-8 nên mình sẽ chuyển sang dạng HEX để code webserver của chúng ta hiện được tiếng Việt nhé !
Mình xài tool online này để chuyển : https://onlineutf8tools.com/convert-utf8-to-hexadecimal

Trước khi chuyển đổi thì mình cũng nhét thêm mấy dòng Status Line ở đầu luôn, như vậy toàn bộ code web sẽ như sau:

Như vậy là có cục data webserver,các bạn bỏ vào file http.c và đừng quên đưa nó vào bộ nhớ flash

Nếu không có nhu cầu xài tiếng việt thì không cần chuyển sang dạng HEX như này đâu

Giờ sẽ là công việc khó khăn hơn, viết code để đẩy cục data này đi khi có lệnh GET từ browser

Do phần header bắt đầu từ 0 đến trường IP->Urgent_Pointer là 54 byte, bộ đệm của chúng ta max là 512 byte nên mình còn dư 512-54 = 458 byte cho dữ liệu http. Nếu dữ liệu http > 458 thì phải cắt nhỏ ra gửi

Mình sẽ tạo 1 số biến toàn cục để lưu trữ số lượng gói tin đã gửi, đang gửi, trạng thái gửi, số hiệu gói tin vừa gửi trong http.c

Đồng thời viết luôn 1 vài phương thức Set Get để các thư viện khác có thê truy cập vào các biến này

Đừng quên thêm nguyên mẫu hàm cho nó vào file html.h nhé

Chỉnh lại code ở bài 13 một chút cho gọn

Mình sẽ tách 1 số đoạn code thiết lập struct tcp sang 1 hàm riêng cho gọn hàm TCP_read. Cụ thể, tạo thêm 1 hàm tên là TCP_make_herder trong file tcp.c

Và định nghĩa thêm các tham số của đối số type vào file tcp.h

Hàm TCP_make_herder sẽ kiểm tra tham số type để xử lí Struct tcp cho phù hợp

Bây giờ trong hàm TCP_read, chúng ta có thể viết gọn lại bằng cách gọi hàm TCP_make_herder. Đây là hàm TCP_read gọn nhẹ !

OK ! Bây giờ chúng ta sẽ bắt đầu gửi trả nội dung trang web cho client khi có 1 request gửi tới

Do trang web của chúng ta khá dài nên sẽ phải chia thành nhiều gói nhỏ để gửi. Khi gửi nhiều gói tin :

Phương pháp đơn giản nhất là : Khi server trả về cho client 1 gói tin, server sẽ chờ nhận được phản hồi ACK của client để gửi tiếp, mà cái phản hồi ACK đấy phải chính xác là của gói tin mà server đã gửi. Chúng ta sẽ check Sequence Number (số hiệu nhận dạng gói tin) xem có trùng với Sequence Number lúc gửi đi không ! Nếu trùng thì có thể chắc chắn 100% client đã nhận được chính xác gói tin mà server đã gửi cho nó. Lúc này chúng ta mới gửi gói tin tiếp theo!

Thực ra đáng lẽ còn phải có 1 trường hợp nữa là server sẽ làm gì khi chờ mãi mà không nhận được ACK phản hồi, nhưng nó sẽ làm phức tạp hóa vấn đề lên rất nhiều. Chúng ta sẽ tạm bỏ qua các vấn đề này và xử lí nó khi làm việc với phương pháp lập trình non-blocking

Mình tạo thêm biến status trong thư viện http.c và sẽ sử dụng nó với mục đích là kiểm tra xem server có đang “bận” trả lời request GET cho thằng nào không. Nếu == 1 thì là đang bận rồi, thằng nào đang GET thì chờ chút đê. Nếu == 0 thì là đang rảnh, có thể phản hồi ngay

Sử lại hàm TCP_send 1 chút

Ở hàm cũ thì mình chỉ nhận dữ liệu và tự tính toán độ dài dữ liệu trong hàm TCP_send
Mình sẽ sửa lại thành nhận dữ liệu kèm cả độ dài của dữ liệu và thêm 1 cái offset để căn chỉnh sữ liệu cho tiện. Bởi vì data web của chúng ta lưu vào bộ nhớ flash của chíp atmega328, mà truy cập flash ở AVR có hơi vất vả hơn các dòng chip khác 1 chút.

Đây là hàm TCP_send viết lại

Như vậy khi gọi hàm TCP_send chúng ta phải nhập vào thêm 2 thông tin đó là:

  • data : Đây là 1 con trỏ dạng flash trỏ tới nơi lưu trữ nội dung web được lưu ở bộ nhớ flash
  • data_length : Độ dài của data sẽ gửi đi là bao nhiêu ( 0 -> 458)

Thư viện http sẽ có nhiệm vụ cung cấp cho bên tcp biết 2 thông số này, vì vậy mình sẽ viết 1 hàm làm việc đó

Ngoài việc cung cấp 2 thông số data data_length qua phương pháp truy cập gián tiếp qua con trỏ ( đối với data_length) và thông qua con trỏ 2 cấp ( đối với data ở dạng flash). Hàm HTTP_get_data_num còn trả về 1 – tức dữ liệu gửi đi hết rồi, và 0 – vẫn còn dữ liệu để gửi tiếp

<tại hàm TCP_read> chỗ //có 1 request gửi tới kìa
Chúng ta sẽ bắt đầu trả lời gói tin đầu tiên ngay tại đây

Các gói tin tiếp theo sẽ được gửi khi chúng ta nhận được cờ ACK
<Tại chỗ nhận cờ ACK trong hàm TCP_read>

Tổng quan, toàn bộ hàm TCP_read lúc này sẽ như sau:

Vất vả rồi 🙁 chạy mô phỏng và test thử thôi

Pơ phệch ! Web server của chúng ta đã hiện nguyên hình

Chúng ta sẽ hoàn thành nốt công việc đóng kết nối với client (thanh loadding sẽ ngừng quay) và gọi hàm xóa biến status về 0 để báo server rảnh, sẵn sàng phản hồi các truy vấn khác

tại chỗ //gửi hết rồi,không còn gì để gửi nữa

Chạy thử

OK rồi ! Trình duyệt web đã ngừng loadding sau khi đã nhận xong data !

Các bạn có thể xóa phần in dữ liệu debug ra màn hình trong hàm TCP_make_herder để chương trình phản hồi nhanh hơn (nhanh hơn hẳn luôn @@)

Xóa luôn máy dòng debug trong thư viện http

Nói chung chỗ nào có chạy hàm UART thì xóa đi để web load ở tốc độ bàn thờ

Điều khiển thiết bị

Tiếp theo là tiết mục điều khiển thiết bị thần thánh

Trước lệnh kiểm tra “GET / ” trong hàm TCP_read chỗ nhận cờ TCP_PSH|TCP_ACK, các bạn thêm vài dòng lệnh so sánh chuỗi rồi bật tắt thiết bị là được.

Download

Tải full source bài 14 tại đây

Full tutorial ENC28J60: http://iot47.com/category/iot-tutorial/ethernetgiao-tiep-enc28j60/

Full tutorila IoT – ESP8266: http://iot47.com/category/iot-tutorial/giao-tiep-esp8266/

Full tutorial lập trình led ma trận: http://iot47.com/category/ma-tran-led/

 

Từ tác giả:

Nếu có bất kì thắc mắc nào trong bài viết, vui lòng để lại comment dưới mỗi bài ! Mình sẽ không trả lời thắc mắc của các bạn ở facebook hay email !

Nếu trong phần code bạn nhìn thấy nhưng thứ kiểu như &amp; thì đó là lỗi hiển thị, cụ thể 3 kí tự < > & bị biến đổi thành như thế
&amp; là &
&lt;  là <
&gt; là >

Giới thiệu Đào Nguyện 80 bài viết
DIY,chế cháo, viết blog chia sẽ kiến thức về lập trình,điện tử - IoT. Rất mong được giao lưu, kết bạn với các bạn cùng đam mê. Địa chỉ Facebook: https://www.facebook.com/nguyendao207