اذهب إلى المحتوى

سوف نستعرض في هذا الدرس بعض الأمثلة على كيفية التعامل مع خادم العميل (Client server).

مثال Hello TCP Client

هذا البرنامج مكمّل لبرنامج Hello TCP Server، ويمكنك تشغيل أي منهما للتحقق من صلاحيتهما. انظر شيفرة المثال فيما يلي:

#include <cstring>
#include <iostream>
#include <string>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {

سنأخذ هنا عنوان IP ورقم بوابة كوسائط لبرنامجنا، نتابع …

    if (argc != 3) {
       std::cerr << "Run program as 'program <ipaddress> <port>'\n";
        return -1;
    }
    auto &ipAddress = argv[1];
    auto &portNum = argv[2];
    addrinfo hints, *p;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    int gAddRes = getaddrinfo(ipAddress, portNum, &hints, &p);
    if (gAddRes != 0) {
        std::cerr << gai_strerror(gAddRes) << "\n";
        return -2;
    }
    if (p == NULL) {
        std::cerr << "No addresses found\n";
        return -3;
    }

ينشئ استدعاء ()socket قناة socket جديدة ويعيد الواصف الخاص بها …

   int sockFD = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (sockFD == -1) {
        std::cerr << "Error while creating socket\n";
        return -4;
    }

لاحظ عدم وجود استدعاء ()bind كما في برنامج Hello TCP Server، فذلك غير ضروري رغم أنك تستطيع استدعاءها، لأنه لا يلزم العميل أن يكون له رقم منفذ (port number) ثابت، لذا فإن الاستدعاء التالي سيربطه برقم عشوائي. سيحاول استدعاء ()connect أن ينشئ اتصال TCP بالخادم المحدد …

    int connectR = connect(sockFD, p -> ai_addr, p -> ai_addrlen);
    if (connectR == -1) {
        close(sockFD);
        std::cerr << "Error while connecting socket\n";
        return -5;
    }
    std::string reply(15, ' ');

هنا يحاول استدعاء ()recv الحصول على إجابة من الخادم، لكن الإجابة قد تحتاج إلى عدة استدعاءات لـ ()recv قبل أن تُستقبَل بالكامل، سنحاول حل هذا لاحقًا …

    auto bytes_recv = recv(sockFD, &reply.front(), reply.size(), 0);
    if (bytes_recv == -1) {
        std::cerr << "Error while receiving bytes\n";
        return -6;
    }
    std::cout << "\nClient recieved: " << reply << std::endl;
    close(sockFD);
    freeaddrinfo(p);
    return 0;
}

مثال Hello TCP Server

نقترح أن تلقي نظرة سريعة على كتاب Beej's Guide to Network Programming، والذي سيفسر لك معظم المفاهيم المُستخدَمة هنا.

سننشئ خادم TCP بسيطًا هنا يعرض الجملة "Hello World" لكل الاستدعاءات الواردة، ثم يُغلقها. كما أنّ الخادم سيتواصل مع العملاء تكراريًا (iteratively)، أي عميلًا واحدًا في كل مرّة. سنشغّل الخادم عبر منفذ محدّد، لذلك سنأخذ رقم المنفذ كوسيط. انظر:

#include <cstring>    // sizeof()
#include <iostream>
#include <string>

الترويسات التالية من أجل ()getaddrinfo و ()socket والدوال الصديقة …

#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>    // close()

int main(int argc, char *argv[]) {

هنا نتحقق مما إذا كان رقم المنفذ قد أُعطي أم لا …

    if (argc != 2) {
        std::cerr << "Run program as 'program <port>'\n";
        return -1;
    }

    auto &portNum = argv[1];

في السطر التالي، عدد الاتصالات المسموح بها في طابور الوارد …

    const unsigned int backLog = 8;

نحتاج إلى مؤشرين، res سيأخذ القيمة وp سيكرر عليها …

    addrinfo hints, *res, *p;
    memset( &hints, 0, sizeof(hints));
    // راجع الكتاب لمزيد من التوضيحات
    hints.ai_family = AF_UNSPEC; // التي ستستخدم IP لا تحدّد بعدُ نسخة

في السطر التالي، يشير SOCK_STREAM إلى TCP

   hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    int gAddRes = getaddrinfo(NULL, portNum, &hints, &res);
    if (gAddRes != 0) {
        std::cerr << gai_strerror(gAddRes) << "\n";
        return -2;
    }
    std::cout << "Detecting addresses" << std::endl;
    unsigned int numOfAddr = 0;

يحرص طول ipv6 على إمكانية تخزين عنواني ipv4/6 في هذا المتغير، وبما أن ()getaddrinfo قد أعطتنا قائمة من العناوين، فسنكرر عليها ونسأل المستخدم أن يختار أحدها لربطها بالبرنامج …

   char ipStr[INET6_ADDRSTRLEN];
    for (p = res; p != NULL; p = p -> ai_next) {
        void *addr;
        std::string ipVer;
        // ipv4 إن كان العنوان من النوع
        if (p -> ai_family == AF_INET) {
            ipVer = "IPv4";
            sockaddr_in *ipv4 = reinterpret_cast < sockaddr_in *> (p -> ai_addr);
            addr = &(ipv4 -> sin_addr);
            ++numOfAddr;
        }
        // ipv6 إن كان العنوان من النوع
        else {
            ipVer = "IPv6";
            sockaddr_in6 *ipv6 = reinterpret_cast < sockaddr_in6 * > (p -> ai_addr);
            addr = &(ipv6 -> sin6_addr);
            ++numOfAddr;
        }
        // من الشكل الثنائي إلى الشكل النصي IPv4/6 تحويل عناوين
        inet_ntop(p -> ai_family, addr, ipStr, sizeof(ipStr));
        std::cout << "(" << numOfAddr << ") " << ipVer << " : " << ipStr <<
            std::endl;
    }
    // في حال عدم العثور على أيّ عنوان
    if (!numOfAddr) {
        std::cerr << "Found no host address to use\n";
        return -3;
    }
    // اسأل المستخدم أن يختار عنوانا
    std::cout << "Enter the number of host address to bind with: ";
    unsigned int choice = 0;
    bool madeChoice = false;
    do {
        std::cin >> choice;
        if (choice > (numOfAddr + 1) || choice < 1) {
            madeChoice = false;
            std::cout << "Wrong choice, try again!" << std::endl;
        } else
            madeChoice = true;
    } while (!madeChoice);
    p = res;
    // كواصف socketFD لننشئ قناة جديدة، ستُعاد
    //  تعيد هذه الاستدعاءات في العادة القيمة -1 في حال وقوع خطأ ما
    int sockFD = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (sockFD == -1) {
        std::cerr << "Error while creating socket\n";
        freeaddrinfo(res);
        return -4;
    }
    // لنربط العنوان بالقناة التي أنشأناها للتو
    int bindR = bind(sockFD, p->ai_addr, p->ai_addrlen);
    if (bindR == -1) {
        std::cerr << "Error while binding socket\n";

        // في حال وقوع خطأ، احرص على إغلاق القناة وتحرير الموارد
        close(sockFD);
        freeaddrinfo(res);
        return -5;
    }
    // وأخيرا، ابدأ بالإنصات إلى الاتصالات الواردة عبر قناتنا
    int listenR = listen(sockFD, backLog);
    if (listenR == -1) {
        std::cerr << "Error while Listening on socket\n";
        // في حال وقوع خطأ، احرص على إغلاق القناة وتحرير الموارد
        close(sockFD);
        freeaddrinfo(res);
        return -6;
    }

    // البنية كبيرة كفاية لتحمل عنوان العميل
    sockaddr_storage client_addr;
    socklen_t client_addr_size = sizeof(client_addr);
    const std::string response = "Hello World";

فيما يلي حلقة لا نهائية للتواصل مع الاتصالات الواردة، وستتعامل معها بالتتابع أي واحدة في كل مرة. كذلك فيما يلي من الأمثلة سنستدعي ()fork لكل اتصال مع العملاء …

    while (1) {
        // يعيد واصف قناة جديد accept استدعاء
        int newFD
            = accept(sockFD, (sockaddr *) &client_addr, &client_addr_size);
        if (newFD == -1) {
            std::cerr << "Error while Accepting on socket\n";
            continue;
        }

فيما يلي، يعيد استدعاء send البيانات التي مرَّرتها أنت كمعامِل ثاني، وطولها كمعامل ثالث، ويعيد عدد البِتَّات المرسلة فعليًا …

        auto bytes_sent = send(newFD, response.data(), response.length(), 0);
        close(newFD);
    }
    close(sockFD);
    freeaddrinfo(res);
    return 0;
}

سيعمل البرنامج على النحو التالي:

Detecting addresses
(1) IPv4 : 0.0.0.0
(2) IPv6 : ::
Enter the number of host address to bind with: 1

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 127: Client server examples من كتاب C++ Notes for Professionals


تفاعل الأعضاء

أفضل التعليقات



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...