18 min. read

הקדמה

C++ היא לא אותה שפה כמו הסטנדרט הראשון שיצא ב-98.
יש 3 צדדים שצריך להכיר בשפה על מנת לבנות מוצרים איכותיים:

  • תכונות השפה.
    כאן נתמקד בחלק מהתכונות החדשות על מנת לבנות קוד אגנוסטי.

  • כלי השפה.
    כלים לבנייה, הרצה, דיבוג וטסט.

  • כלי תוכנה.
    כלים לניהול גרסאות, ניהול תלויות והטמעה של המוצרים בפרודקשן.

מה נלמד כאן?

נציג את הכלים שבהם נעבוד כמו כן נסביר על מה שאנחנו משתמשים בו בפוסט הזה.

מה לא נלמד כאן?

לא נלמד לעבוד עם כל כלי בנפרד - לשם כך אני אשים לינקים להמשך למידה.
מטרת הפוסט הוא להראות באילו כלים להשתמש וליצור סקרנות עבור הקוראים לאיך לבנות קוד מודרני בשפת C++.

באילו כלים נעבוד

בפרוייקט הזה נלמד להשתמש ב-4 כלים מודרניים לבניית שרתים יעילים על גבי C++.

  • boost - ספריית C++ לכלים מודרניים.
  • git - נלמד כיצד להשתמש בגיט בצורה מיטבית עבור ספריות צד שלישי.
  • cmake - נלמד כיצד לבנות את הקוד שלנו בצורה אגנוסטית למערכת ההפעלה.
  • docker - ווירטואליזציה ב-0 מאמץ.

עלייתו של Boost

ספריות Boost אלו ספריות רבות תחת פרוייקט אחד שעוסקות בתחומים מגוונים.

החלוקה של הספריות היא לכל תחום, והפרוייקט הגדול נמצא בגיט-האב:
Github

הספרייה שנעבוד איתה היא - Boost.Beast
Boost.Beast

למה Boost ולא ספריה אחרת?

ניתן להשתמש בספריות אחרות אולם יש כמה נקודות חשובות שיש לעבוד עליהן כאשר לומדים C++.

  • Boost היא ספריה מובילה, הקונספטים שלה נכנסים לסטנדרט של C++.
  • הספריה בנויה בצורת לגו כך שהיא מאפשרת לכם להשתמש בחלקים ממנה.
  • הספריה בנויה לשימוש ספציפי, אם אתם רוצים רק את החלק של השרת לא תצטרכו את כלל הספריה.
  • הספרייה בנויה בצורת Header Only כך שאינכם צריכים לבצע linking לספריות צד שלישי.

כמובן שאני ממליץ לנסות ספריות נוספות כגון : httpp

כתיבת שרת ב-Boost.Beast

ה-Repository נמצא כאן:
https://github.com/boostorg/beast

השתמשתי בקוד הדוגמא של Boost.Beast לשרת אסינכרוני.

אני ממליץ שתעברו על הקוד לבד, החל מפונקציית main ותקראו מה היא עושה.

מה קורה בקוד

המחלקה http_connection אחראית על יצירת התשובות עבור בקשות.
על מנת לאתחל את השרת עלינו להכיל io_context.

מהו io_context

אובייקט io_context מאגד בתוכו תהליכי הרצה אסינכרוניים.
על מנת לאפשר לפונקציות, לתהליכונים ולסוקטים לרוץ ולקבל מידע ללא חסימת שאר התהליכים, יש צורך בלהשתמש במסנכרן.
האובייקט הזה אחראי על ההרצה וסנכרון תהליכים כגון קבלת חיבור חדש מהרשת.

קבלת socket

על מנת לקבל חיבור חדש נשתמש ב-acceptor עם הפונקציה - acceptor.async_accept.
הפרמטר הראשון הוא החיבור שבו נשתמש עבור חיבור חדש.
הפרמטר השני הוא הפונקציה אותה נריץ בקבלה של חיבור.

ההרצה האסינכרונית

בביצוע ioc.run(); אנו מריצים את ה-io_context.
הצורה שבה אנחנו מריצים מחדש את ה-Accept אחרי שחיבור אושר היא שוב פעם להריץ את המתודה acceptor.async_accept.

Acceptor היא תבנית נפוצה לחיבור סוקטים.

בדקו לבד כיצד הקוד מתייחס ל-request ואיך כותבים response לתגובה.

איך נוכל לשפר את הפרויקט ההתחלתי?

כמה נקודות מעניינות להתעמק בהן כדי לשפר את הפרוייקט:

  • לאגד את מתודת http_server אל תוך מחלקה ולנהל את מצב התוכנה בצורת OOP.

  • אנחנו משתמשים ב-html שנבנה בצורה הכי פרימיטיבית שיש.
    שימוש בספריות צד ל-html כמו cmtl:
    https://github.com/tinfoilboy/CTML

  • להוסיף TLS כדי להשתמש ב-Https.

איך נשמור את boost בתיקיית הפרוייקט?

כדי להכיל את boost נשמרו אותו בתיקיית 3rdparty אשר תשתמש אותנו לשמירת המודול.

  1. להריץ הוספה של הספרייה לגיט:
1
git submodule add https://github.com/boostorg/boost 3rdparty/boost
  1. לעדכן את הסאב-מודול בצורה רקורסיבית.
    שימו לב, זה ייעדכן את כלל הספריות של boost, כדאי לכם להתנסות בחלק מהספריות האלו!
1
git submodule update --init --recursive

מה האלנטרנטיבות שלי לשמירת תלויות?

ניהול תלויות הוא תחום בתוכנה לאיך אנחנו שומרים ומנהלים גרסאות בין תלויות.
תלות יכולה להיות בקוד - בין מחלקה A למחלקה B.
תלות יכולה להיות בין שתי ספריות - C++ ו-Boost.
תלות יכולה להיות מערכתית - בין שני רכיבי Web API.

מה בעצם האלטרנטיבה שלי ל-git submodules?
לכאלו אנשים מוזרים שלא עובדים עם גיט ספציפי אלה עם כלים אחרים.
או לחילופין לא רוצים לבנות את התלויות שלהם בעזרת גיט אלה בעזרת כלי אחר.

Conan

https://conan.io/
קונאן הוא כלי שקיים כבר כמה שנים.
הכלי בצורתו הפשוטה יודע לפרסם חבילות ולצרוך חבילות בהתאם לגרסא.

ה-Recipe של Boost.Beast:
https://conan.io/center/boost?tab=overview

הגאונות של קונאן היא בשימוש בפייתון ככלי לקונפיגורציית ה-Recipe והצריכה של החבילות.

Vcpkg

https://conan.io/center/boost?tab=overview

כלי ע”י מייקרוסופט לניהול חבילות וצריכתן.
הכלי יותר פשטני מ-קונאן ויודע לקנפג בפרוייקט או בצורה גלובאלית את החבילות.

Build2

https://build2.org/

כלי הרבה יותר מורכב, נועד לניהול תלויות מורכבות בין שפות שנות ל-C++.


כדי ללמוד עוד על-submodules:
https://git-scm.com/book/en/v2/Git-Tools-Submodules

איך הפרוייקט אמור להיראות

קובץ ה-main.cpp מכיל את קוד השרת.
תיקיית 3rdparty המכילה boost.

לבנות את הפרוייקט

ל-C++ יש שני חלקים לבנייה.

  1. בניית כלל היחידות, בדרך כלל קבצי cpp.
  2. ביצוע לינק בין הקבצים הנוצרים מהשלב הראשון.

כדי לבנות את הקוד שלנו נשתמש בכלי הנקרא Cmake.

Cmake הוא כלי המאגד מאפיינים לבנייה, לינק, הגדרת תלויות, התקנת הקוד ואף טסטים.

קודם כל צריך להתקין את ה-Cmake:
https://cmake.org/cmake/help/latest/command/install.html

שנית, כדי להשתמש בו יש ליצור קובץ בתיקייה הראשית של הפרוייקט בשם: CMakeLists.txt.

הקובץ הזה הוא טקסטואלי אך זה בעצם סקריפט שאומר אילו פקודות להריץ.
הפקודות שמריצים דרך הסקריפט מתארות כיצד יש לקנפג את הבילד של התוכנה או הספרייה.

אם נניח בנינו מוצר מ-3 ספריות שונות שלנו, יהיו לנו 4 סקריפטים.
1 לכל ספרייה ו-1 למוצר הסופי.

לדקומנטציה:
https://cmake.org/cmake/help/latest/

בניית הסקריפט CMakeLists

גרסת ה-Cmake

1
cmake_minimum_required(VERSION 3.2)

קודם כל נצייין את גרסת המינימום שאנחנו רוצים שהסקריפט ירוץ.
אם מוסיפים את השורה הזו - היא חייבת להיות שורה ראשונה בסקריפט.

נוסיף שם לפרוייקט

1
PROJECT(CPP_HTTP_SERVER_ON_DOCKER)

נצרוך את ספריית Boost

1
2
3
4
5
6
add_library(boost_beast INTERFACE)

target_include_directories(boost_beast
SYSTEM
INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/3rdparty/boost/libs/beast/include")

השורה add_library מוסיפה לבילד את הספרייה שרשמנו, שהיא boost_beast.
מכיוון שזו ספריית Header only אנחנו צריכים להגדיר אותו עם INTERFACE.
שזה אומר רק Header ללא קבצי LIB או DLL.

לא אסביר את הכל, להרחבה ניתן לקרוא כאן:
https://cmake.org/cmake/help/latest/command/target_include_directories.html

תנסו להבין למה משתמשים ב-SYSTEM עם INTERFACE.

בונים את הפרוייקט

1
ADD_EXECUTABLE(cpp_http_server main.cpp)

השורה מוסיפה לבסוף את בניית ה-EXECUTABLE שהוא קובץ ההרצה הסופי.
המקור שלנו הוא רק קובץ אחד בשם main.cpp ששם שמרנו את הקוד לתוכנת השרת.

הוספת ספריות ל-Include

מכיוון שזו ספריית Header-Only כל מה שפרוייקט צריך זה את המיקומים ל-Submoduels של התלויות.
מכיוון של-Boost.Beast יש תלויות חיצוניות בספריות Boost שונות, נצטרך להוסיף אותם לפרוייקט.
שימו לב שפשוט הוספתי את כל הספריות :)

וכעת סיימנו את הסקריפט שלנו!
בשורות בודדות הגדרנו תהליך בנייה אגנוסטי לחלוטין - גם לווינדוס וגם ללינוקס!

בניית הפרוייקט

כדי לקרוא יותר לעומק על הפקודות ניתן לקרוא:
https://cmake.org/cmake/help/latest/manual/cmake.1.html

כדי לבנות את הפרוייקט בדרך כלל נשתמש בתקיית build.
בתוך התיקייה ניצור תיקייה חדשה בשם build.
נהיה על התיקייה ונריץ את הפקודה הבא:

1
cmake ..

זה בעצם יכין את כל מה שcmake צריך לבנייה.

אצלי הוא מזהה לבד שזה בווינדוס ומייצר את הפרוייקטים הרלוונטים ל-Visual Studio:

נוכל לבנות בעזרת הפקודה:

1
cmake --build . --config Release

זה ייבנה לנו את הפרוייקט ב-Release.

אם עשיתם את הכל כראוי תוכלו לראות את השרת שלכם ב- build/Release.
הריצו:

1
cpp_http_server.exe 127.0.0.1 8000

וכנסו לכתובת localhost:8000/count:


כבר עכשיו יש לכם שרת עובד עם תהליך בנייה אגנוסטי!

מה עכשיו?

פרסום השרת!
אבל איך נפרסם את השרת?
ניתן לבנות סקריפט שמאגד את הכל ואורז את קובץ ה-Exe שלנו.
אך בשביל לפרסם אותו בתוך שרת נצטרך להרים מערכת הפעלה בווירטואליזציה, לקנפג את הכל לבד - זה לא כיף.

בשביל זה יש לנו טכנולוגיה חדשה שנקראת Docker.

Docker

דוקר היא שיטה לווירטואליזציה נוחה וחלוקה של משאבים ברמת האפליקציה ולא ברמת מערכת ההפעלה.

לעומת הרצה של מכונות ווירטואליות כאשר כל מכונה עם מערכת הפעלה משלה.
אנו יכולים לנצל מערכת הפעלה אחת, ומסנכרן אחד על מנת להריץ קונטיינרים רבים.

בונים DOCKERFILE

קובץ דוקר הוא בעצם המתכון לתמונת דוקר - Docker Image.
כל שורה בקובץ דוקר היא גם שכבה - Docker Layer.

הדוקר יודע לשמור שכבות ובכך לא לבצע אותם כל הזמן מחדש במידה ויש שכפול של שכבות.
בתהליך הזה הדוקר יידע כיצד להגדיר את השכבות ובסוף ייצור תמונה שתייצג את השרת הסופי שלנו.

לאחר מכן תמונה כזו יכולה להיות משומשת ליצירת Container.
מה שרץ בסופו של דבר זה Conatiner ולא - Image.
כפי שאמרנו ה-Image רק מתאר איך השרת שלנו בנוי.

בניית הקובץ

כדי לבנות את קובץ ההגדרות יש ליצור קובץ בשם Dockerfile ללא סיומת.

כשורה ראשונה נבסס את התמונה שלנו על תמונה של ubuntu - מערכת מבוססת לינוקס.

1
FROM ubuntu:latest

כמו ב-cmake זו השורה הראשונה והיא מבססת את התמונה הבסיסית שבה נשתמש.

התקנת כלים

1
2
RUN apt-get -y update && apt-get install -y
RUN apt-get -y install g++ cmake

בעזרת RUN נריץ פקודות על גבי המכונה.

הפקודה הראשונה מעדכנת את החבילות המותקנות.
הפקודה השנייה תתקין את הקומפיילר g++.

העתקה של כל הקוד

1
COPY . .

פשוט נעתיק את כל התוכן של התיקייה לתיקיית היעד הבסיסית.

תהליך ה-cmake בתוך הקונטיינר

1
2
3
4
5
6
RUN mkdir build

WORKDIR /build

RUN cmake ..
RUN cmake --build . --config Release

זה מה שעשינו ידנית עם ה-cmake.
הפקודה WORKDIR משנה את ה-Current Directory כדי לעבוד בתוך תיקיית build.

הפצת הפורט והרצה

1
2
3
4
ENV PORT=8080
EXPOSE 8080

CMD "/build/cpp_http_server" "0.0.0.0" "8080"

אנו מגדירים משתנה סביבה בשם PORT כרגע אנו לא משתמשים בו אך זה נכון יותר להגדיר משתנה סביבה שיגדיר מהו הפורט.

הפקודה EXPOSE 8080 מגדירה אילו פורטים הקונטיינר הולך לחשוף.

ולבסוף נשתמש בפקודת CMD שמגדירה אילו פקודה “מריצה” את הקונטיינר.

אנו נריץ את השרת שלנו עם localhost והפורט 8080.

הרצת דוקר

במערכות ווינדוס ניתן להתקין Docker Desktop.
לשימוש אישי ולא קנייני.

התוכנה עוזרת לכם לנהל את התמונות והקונטיינרים שיש לכם על המכונה.

המטרה היא להריץ את הקונטיינר על לינוקס בתוך הווינדוס שלנו.

בשביל זה הכלי השני שנשתמש בו הוא wsl:
https://learn.microsoft.com/en-us/windows/wsl/install

בניית התמונה

כדי לבנות את התמונה יש להריץ את הפקודה docker build.
שם התמונה תהיה שם המשתמש שלנו ב-wsl.

1
docker build . -t <username>/cpp_http_server

הרצת התמונה

ובסוף על מנת להריץ את זה נשתמש בפקודה docker run.

1
docker run -p 49160:8080 -d <username>/cpp_http_server

הפורט -p מיוצג בתוך הפורט הפנימי לפורט החיצוני.
אם בפנים השתמשנו ב-8080 הפורט החיצוני יהיה 49160.

לכן בסוף מהמכונה שלנו נוכל להיכנס דרך הדפדפן לאותה הכתובת רק בפורט החיצוני לקוננטיינר.

1
localhost:49160/count

בונוס - קנפוג Github Actions!

כדי ליצור מוצר מושלם נצטרך להוסיף devops רציני.
והקטע היחידי שחסר לנו הוא CI/CD אוטומטי.

נוכל להתנסות ב-Github Actions:

https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions

CI/CD הוא עקרון אוטומציה לתהליכי הקונגפיגורציה וההתקנה.

נרצה שכחלק מהפרוייקט הרוחבי שלנו, נדע להריץ את הבילד, טסטים והתקנה בכל זמן כחלק מהפיתוח.

תהליכי אג’ייל שונים מיושמים בצורות שונות בחברות שונות - לא משנה איך התהליך אצלכם מתנהל, מה שחשוב לזכור הוא שאוטומציה מלאה לתהליכים מקלה לנו על שימושם ובסופו של דבר עוזרת לפתור באגים הרבה לפני שזה מגיע ללקוח או לשרת המבצעי שלנו.

שלב הרצת ווירטואליזציה יהיה השלב שבנו נתכנן ונריץ את תהליכי ההתקנה השונים שיש במערכת.
נכין בעצם את כל הקוד והמוצרים להרצה.

שלב הפרסום הוא השלב שבו נטמיע את המערכת שלנו או בענן, או אצל לקוח, או בסביבה לבדיקות.

העקרון המנחה שלנו הוא כמה שיותר אוטומציה בתהליכים!

תיקון סקריפט דוקר

נצטרך להוסיף שני דברים.
התקנת גיט ועדכון המודולים של boost.

בהתקנות תוסיפו:

1
RUN apt-get -y install git

לאחר ה-copy:

1
RUN git submodule update --init --recursive

בסוף זה יוצא:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
FROM ubuntu:latest

RUN apt-get -y update && apt-get install -y
RUN apt-get -y install g++ cmake
RUN apt-get -y install git

COPY . .

RUN git submodule update --init --recursive

RUN mkdir build

WORKDIR /build

RUN cmake ..
RUN cmake --build . --config Release

ENV PORT=8080
EXPOSE 8080

CMD "/build/cpp_http_server" "0.0.0.0" "8080"

הוספת Action

כנסו ל-Actions ולחצו להוסיף חדש:

חפשו את ה-Docker Image:

מתווסף משהו בסיסי ביותר, נשתמש בזה למטרות שלנו:

הרצת Action

לבסוף יש להריץ אותו ולוודא שהכל עבר:

הפרוייקט בגיטהאב נמצא כאן:

https://github.com/Ilya122/CppBoostBeastCmakeDockerExample

פרסום תמונת הדוקר

על מנת לפרסם את התוצר המוגמר אתם מוזמנים להתנסות בהגדרות פרסום לאחד מהעננים הפופולריים.

זה קצת יוצא מה-Scope לכן לא אסביר הלאה :)


סיכום

אז מה ראינו כאן?

התנסנו בכתיבת שרת C++ בעזרת Boost.Beast.
ראינו כיצד Cmake מקל לנו על החיים ליצירת פרוייקטים אגנוסטיים.
השתמשנו בווירטואליזצית דוקר על מנת להריץ את הקוד שלנו.

ולבסוף חיברנו את Github Actions לכדי CI/CD התחלתי.

תודה רבה על הקריאה!


אהבתם? מוזמנים להביע תמיכה כאן: כוס קפה