تطبيق C++ الخاص بك يستحق تغليفًا أفضل: نغوص في Docker
\n\nلقد كتبت محللًا رائعًا بلغة C++ يعالج بسرعة فائقة غيغابايتات من السجلات. أو ربما أنشأت خدمة مصغرة عالية التحميل للعمل مع مآخذ الشبكة. الكود مُصحح، كل شيء يعمل بسلاسة. ولكن تأتي لحظة الحقيقة - النشر. وهنا تبدأ المشاكل: "ما إصدار libcurl لديك؟"، "لماذا تعطل في بيئة الإنتاج مع خطأ GLIBC؟"، "لماذا تقوم بتثبيت GCC بأكمله على الخادم؟".
\n\nهل يبدو ذلك مألوفًا؟ مرحبًا بك في عالم حاويات تطبيقات C++ باستخدام Docker. إذا كنت لا تزال تعتقد أن Docker مخصص فقط لـ JavaScript و Python، فأنت تفوت الكثير. في الواقع، بالنسبة لـ C++، تمنحك الحاويات قوة خارقة: تحصل على بيئة غير قابلة للتغيير حيث يتم بناء الملف الثنائي مرة واحدة ويعمل في كل مكان - من حاسوبك المحمول إلى مجموعة الإنتاج.
\n\nفي هذه المقالة، لن نناقش النظرية فقط. سنقوم خطوة بخطوة بإنشاء تطبيق C++، وتعبئته في Docker، وتحسين الصورة إلى أصغر حجم ممكن، وجعل الحاوية تعمل كالساعة. هيا بنا!
\n\nلماذا Docker هو أفضل صديق لمطور C++؟
\n\nيعتقد الكثيرون أن تطبيقات C++ سهلة التوزيع كملفات ثنائية. تقنيًا - نعم. عمليًا - هذا طريق إلى الجحيم. توزيعات Linux المختلفة، إصدارات glibc، نقص المكتبات، عدم توافق ABI - هذه فقط بعض المشاكل.
\n\nDocker يحل ثلاث مشاكل رئيسية لمطور C++:
\n\n- \n
- قابلية إعادة إنتاج البناء: أنت تعرف بالضبط أي إصدار من المترجم، CMake، Boost والاعتماديات الأخرى يُستخدم. لا مزيد من "يعمل على جهازي". \n
- العزل: الحاوية لا تلوث النظام المضيف. لا حاجة لتثبيت GCC على خادم الإنتاج - يكفي وقت تشغيل بسيط. \n
- التوسع: الصورة الجاهزة سهلة النشر في Kubernetes أو Docker Swarm. C++ يتعامل مع الحمل بشكل ممتاز، وDocker يساعد في توزيع هذا الحمل. \n
والأهم - حاويات تطبيقات C++ لا تتطلب سحرًا. كل ما تحتاجه هو Docker وقليل من الصبر.
\n\nإنشاء تطبيق C++ بسيط للحاويات
\n\nكمثال، لنأخذ ليس "Hello, World!"، بل شيئًا أكثر حيوية. لنفترض أننا نكتب خادم HTTP صغيرًا باستخدام C++ مع Boost.Beast. ولكن لتجنب إثقال المقالة، سنصنع نسخة مبسطة - خادم صدى مأخذ باستخدام POSIX API الخالص. سيظهر هذا كيفية العمل مع الاعتماديات النظامية.
\n\nملف server.cpp:
#include <iostream>\n#include <string>\n#include <cstring>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <unistd.h>\n\nint main() {\n int server_fd, new_socket;\n struct sockaddr_in address;\n int opt = 1;\n int addrlen = sizeof(address);\n char buffer[1024] = {0};\n\n // إنشاء مأخذ\n if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {\n perror("socket failed");\n exit(EXIT_FAILURE);\n }\n\n // الربط بالمنفذ 8080\n if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {\n perror("setsockopt");\n exit(EXIT_FAILURE);\n }\n address.sin_family = AF_INET;\n address.sin_addr.s_addr = INADDR_ANY;\n address.sin_port = htons(8080);\n\n if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {\n perror("bind failed");\n exit(EXIT_FAILURE);\n }\n\n if (listen(server_fd, 3) < 0) {\n perror("listen");\n exit(EXIT_FAILURE);\n }\n\n std::cout << "Server listening on port 8080" << std::endl;\n\n while (true) {\n if ((new_socket = accept(server_fd, (struct sockaddr *)&address,\n (socklen_t*)&addrlen)) < 0) {\n perror("accept");\n exit(EXIT_FAILURE);\n }\n\n read(new_socket, buffer, 1024);\n