Programming Freaks  | دورات ومقالات برمجيه

Please login or register.

Login with username, password and session length
Advanced search  

News:

Programming-Fr34ks.net
Up and running

Author Topic: Network programming in Linux using c  (Read 2834 times)

St0rM

  • [C programmer]
  • Administrator
  • Active Member
  • *****
  • Posts: 209
  • Why So serious ?
    • View Profile
    • WWW
    • Email
Network programming in Linux using c
« on: August 21, 2009, 12:43:03 PM »
Arabic linux network programming in c
مساء الخير عليكم
الدرس ده هيتكلم عن : Network programming in linux
اتوقع منك انك تكون قريت ال C tutorial الي انا كتبتها وتكون فهمتها بشكل كويس لان ديت هتوصلك لمستوي كويس يخليك تقدر تفهم الكلام الي انا هقوله دلوقتي
.. نخش في الجد بقي



مقدمة : اول نقطه واهم نقطه انك تكون عارف ان كل حاجه في ال Linux هي file ملف . سامعك دلوقتي بتقول يعني ايه ؟ وايه علاقة الملفات بموضوعنا ؟ هقولك ان اللينكس بيتعامل مع كل حاجه كملف وده معناه ان اي حاجه بيتم الينكس التعامل معاها بيستخدم فيها دوال الكتابه والقرائه زي write/read. سواء ان كان بيكتب لملف عادي بيكتب ل device بيقرا من كارت الصوت او كارت الفيديو بيقرا او بيكتب لكارته النتورك عشان يستقبل او يبعت البيانات. طيب دلوقتي اذاي اقدر اتعامل
مع الملفات ديت ؟


 
الملفات ديت بيتم التعامل معاها عن طريق حاجه اسمها File descriptor بعيد عن كل التعقيدات التقنيه الي ممكن نتكلم عنها لو شرحنا يعني ايه File descriptor باستفاضه , ولذلك انا هشرحه بشكل مختصر
ال file descriptor هو الوسيلة الي من خلالها تقدر تقرا وتكتب لملف ما . بيتم الحصول علي ال file descriptor دوت باستخدام داله open بتقوم بفتح الملف وارجاع file descriptor تقدر من خلاله تكتب / تقرا من الملف الي انت طلبت فتحه



كل ده جميل بس ايه علاقته بال sockets وال network programming !!
wow sockets ?? يعني ايه socket ??
جميل جدا
تعريف ال Socket
Socket: وسيلة اتصال بين طرفين سواء ان كان الطرفين علي نفس الجهاز او كانوا علي جهازين
هو ده تعريف ال SOCKET  :D


وبما ان ال socket ككل حاجه في الينكس ملف , يبقي طريقة التعامل مع ال socket دوت هو فتحه عن طريق دالة open والحصول علي ال file descriptor بتاعه والقراية والكتابه بداخله باستخدام
write/read صح ؟
 
اه صح بس غلط  ??? ال socket هو نوع متميز من الملفات لانه زي مقلنا بيتم من خلاله التواصل بين طرفين وده معناه اننا محتاجين خصائص معينه نقدر نتعامل بيها مع الملف دوت ! نوع ال protocol الي هنستخدمه الخ الخ . بس ده مش معناه ان دالة open مش بيتم استخدامها لفتح ال  socket لا بيتم استخدامها ولكنها مغلفه داخل داله اخري تسمي
socket()

دالة socket ديت هي الداله المتخصصه بفتح ال socket وارجاع ال file descriptor الخاص بيه , حاجه تانيه جديره بالذكر ان التعامل مع ال  socket هو مجرد قرائه وكتابه كأي ملف ! ولذلك يمكن استخدام الدوال write / read وفي بعض  الاحيان بيتم استخدامهم . ولكن في دوال اخري متخصصه في الموضوع دوت وهي send/recv
استخدامها يعد كنوع من استخدام دوال متخصصه مع ملف خاص . ولكن مش شرط اننا
نستخدمهم

الا اذا اردنا تحكم خاص في عملية ال sending او ال receiving

جميل قوي ايه بردوا علاقة ال socket بال network programming ?
ال  socket كما قلنا من قبل هو ملف خاص بيتم التعامل معاه بدوال القرائه والكتابه وبيتعامل مع ال network card لارسال او استقبال البيانات . وده منعنا انك تقوم بفتح ال device بتاع ال network card وتقوم بعمليات معقده جدا لتتحكم في ارسال واستقبال البيانات ولذلك بنقوم باستخدام ال socket.


 جميل جدا الكلام دوت .في درسنا هنتعامل مع نوع واحد فقط من ال sockets يسمي بال INTERNET SOCKETS وذلك لان في انواع تانيه مش هقوم بسردها هنا ولكن هزودك ب link لل man pages الخاصه بيها عشان تقدر تطلع عليها
دلوقتي بعد معرفنا اننا حنتعامل مع ال INTERNET SOCKET يتبادر لذهن بعضكم الي يعرف في ال networking ان في بروتوكولات للتعامل مع الانترنت TCP/IP ,  UDP
مش هشرحهم هنا بردوا لان الموضوع برمجي بحت ولكن هوضح الفرق بينهم

TCP: يقوم بعمل تحكم في ال PACKETS المرسله والمستقبله عن طريق ال 3 hand shaking ويقوم بالتحقق من وصول البيانات بشكل سليم من طرف الي الطرف الاخر


UDP: يعرف بال connection less protocol وده لان التواصل بين طرفين فيه غير متحكم فيه بشكل كامل ال packets المرسله فيه مش بيتم التحقق منها ومش شرط توصل بنفس الترتيب الي تم بعثها فيه

 واحد يسالني ويقلي طالما كده استخدمه ليه ؟  ??? وده لانه اسرع من ال
TCP  وبيتم استخدامه في ارسال الصور او ملفات ال video او ال streaming (زي ال youtube كده)وده لان الخطأ في نقل البيانات فيه محتمل ويمكن تجاوزه يعني لو نقص فرايم او اتنين من الفيديو مش هتحصل كارثه ومحدش حيحس بحاجه وهتعدي لكن لما تكون بتبعت binary file اي نقص في اي packet فيه معناه corruption في ال DATA وعدم وصول الملف بشكل صحيح ولكن اجدير بالذكر بردو انه يمكنك انت كمبرمج ان تقوم بعمل بروتوكول يعمل فوق ال UDP ويقوم بالتحقق من البيانات المرسله
امثلة البرامج الي بتقول بعمل كده هي برامج ال ftp مثلا !


جميل جدا دلوقتي احنا هنتكلم كمان عن حاجه تانيه عشان نوصل الكلام ببعضه : ال internet sockets فيها هي كمان اكتر من نوع  :D متنحش ايوه فيها اكتر من نوع ولكن احنا في درسنا هنتعلم منهم اتنين بس SOCK_STREAM / SOCK_DGRAM
وبما اننا شرحنا الفرق بين ال tcp وال udp اقدر اقلك بكل فخر ان ال

SOCK_STREAM بيستخدم ال TCP
وال SOCK_DGRAM بيستخدم ال UDP
 
وابقي متأكد كمان انك فهمت الفرق بينهم , في حالة اذا مفهمتش الفرق بينهم زي ماعلي طول بقول مش هيبقي فيه اي مشاكل بس لو سمحت اقفل البراوزر  ???

جميل جدا بعد متكلمنا عن كل الكلام الكتير دوت الي هو مقدمه هنتكلم عن مقدمة تانيه  >:(
هنا هنتكلم عن مقدمة عن ال IP Addresses واذاي نقدر نتحكم في ال socket وعن طريق ايه نقدر نخلي ال socket يربط نفسه بجهاز تاني او طرف تاني

اولا , كل جهاز علي الانترنت ليه IP address وديت اخر حاجه هتكلم فيها عن ال IP addresses
ثانيا كل جهاز علي النت ليه IP addresses ليه حاجه اسمها ports وديت بردوا اخر حاجه حتكلم فيها عن ال ports
في امور تانيه متعلقه بالشبكات مش هشرحها لان ولتاني مره الموضوع برمجي بحت , عاوز مقدمة  في الشبكات هتلاقي لينك في اخر الموضوع لموضوع عربي كمان  :D

ركز في الكلام الجاي لانه مهم جدا

Network byte ordering:
يعني ايه ؟ عشان نتكلم بشكل صريح جهازك مش شرط يكون زي جهاز غيرك
انت البروسيسور بتاعك Intel انا بتاعي Bitingan ماركه جديده في السوق 500 كور ومساحه تيرا هيرتز  :P

ايه بقي الي دخل ال processors في الموضوع ؟ هقلك
دلوقتي انت بتبعتلي سلسه ارقام 1 2 3 4 5
جهازي انا بيقرا ال bytes من اليمين للشمال هيقراها 1 2 3 4 5  جهازك انت بيقراها من الشمال لليمين 54 3 2 1  ولذلك هناك نوعان من ال network byte ordering


Little Endian
Big Endian



لو جهاز بيستخدم الاول هيقرا الbytes بشكل عكسي من الشمال لليمين مثلا , انما جهازي انا هيقراها من اليمين للشمال بشكل صحيح . ولذلك عشان اقدر اتعامل معاك لازم يكون في نوع من انواع التحويل من جهازي لجهازك
بس فكرة اني احول من جهازي لجهازك ديت في حد ذاتها متعبه ومرهقه وغير مجديه !

 وانا هعرف جهازك نوعه منين ؟ مش هتصل بيك بالتليفون اقلك انت intel ولا amd ?
ولذلك تم حل تلك المشكله باستخدام ال api
ال Big Endian هو ال Network byte order الي بتستخدمه ال network ولذلك التسميه
اما الي بيستخدمه جهازك فده بيكون معرف في ال API بتاعتك وانت كمستخدم تقدر تسميه Host byte order
وده بيختلف من جهاز للتاني لو Interl هيكون Little Endian لو بتنجان هيكون Big Endian وهكذا  :)
طيب جميل عشان احول بقي الكلام دوت اعمل ايه ؟ استخدام الداول الاتيه



--man page snip

Code: [Select]
#include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

الداول ديت وظيفتها انها تحول short/long bytes من  Host byte ordering الي Network byte ordering او العكس
ntohs = Network to host shot
ntohl = Network to host long

خلينا نسيب الكلام دوت دلوقتي بعد موضحناه لان فايدته هتبان بعدين
كما سبق وقلنا من قبل ان ال socket بيحتاج مكان يخزن فيه معلومات عن الجهاز المتصل او المكان الي بيتصل منه
طيب هيخزنها فين ؟  network structures هنستخدم منها واحده دلوقتي

Socket addressing:

اصعب حاجه هي الي هنتكلم عليها دلوقتي , بعد كده كله هيبقي عادي جدا !
ال socket addressing معناه انك تعطي عنوان لل socket الي انت شغال عليه
وال address دوت هيكون تابع لحاجه اسمها domain . الي domain دوت هو الي بيحدد طريقة ال addressing فاكر لما اتكلمنا عن ال socket types ? وقلنا ان في حاجه اسمها INTERNET_SOCKETS اهي ال INTERNET_SOCKETS ديت بتتبع domain معين اسمه AF_INET هو الي بيحدد طريقة ال addressing لل socket وبيعرفه انه هيقوم باتباع قوانين ال ip addressing
طبعا زي مقلت في انواع تانيه من ال sockets وبالتالي في انواع تانيه من ال domains بس احنا بنتكلم عن ال INTERNET SOCKETS
طيب ال addressing دوت بيتم تخزينه فين ؟
وده يدفعنا اننا نتكلم عن ال IPv4 addressing "ال tutorial ديت مجالها خارج تماما عن ال ipv6"

لتخزين ال addresses لل ipv4 بيتم استخدام structure اسمها sockaddr_in ومتحويتها كالاتي
Code: [Select]
struct sockaddr_in {
    short            sin_family;   
    unsigned short   sin_port;   
    struct in_addr   sin_addr;   
    char             sin_zero[8];   
};

struct in_addr {
    unsigned long s_addr;         
};
اولا sin_family ديت بيتم فيها تعريف ال addressing domain في حالتنا بيكون AF_INTER
ثانيا ال sin_port وده بيتم تحويله من host byte order ل network byte order عن طريق ايه ؟ ايوه بالظبت الدوال الي اتكلمنا عنها
ثالثا ديت structure تانيه بيتم فيها حفظ ال ip address وطبعا بيكون محول ل network byte order وده عن طريق استخدام داله تانيه غير الدوال الي اتكلمنا عنها
وهنشوفها كمان شوية في ال code الي هنستخدمه
رابع واخر حاجه ديت حاجه بتستخدم لل compatibility مع structures تانيه مفيش داعي تفهم معناها كل الي عليك تعمله انك تصفرها

ودلوقتي اسيبكم مع كود صغير تفهموه وحشرحه بعد متقروه
Code: [Select]
#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <netinet/in.h>

int main(void)
{
        struct sockaddr_in my_addr;

        my_addr.sin_family = AF_INET;
        my_addr.sin_port = htons(80);
        my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));

        return 0;
}
    كدا احنا قمنا بعمل form ل address لل socket
    Domain = AF_INET
    port = 80
    address = INADDR_ANY --> ديت معناها اي address وقمنا بتحويلها من host ل network byte ordering
    جميل طب وبعدين بقي ؟

    نلم بقي الي عرفناه لحد دلوقتي عشان تفضل مركز معايا
     عرفنا يعني ايه file descriprot
      عرفنا يعني ايه  socket
      عرفنا يعني ايه   Byte ordering
       عرفنا يعني ايه Addessing



    كل ده جميل بس بدون معرفة ال API الصحيحه لاستخدامه فهو لايساوي شيئ ودلوقتي انا هقوم باستخدام السورس كود الي فات وهضيف في اضافه اضافه مع كل API function بستخدمها واعرفك انا استخدمتها ليه


    [/list]
    Code: [Select]
    #include <stdio.h>
    #include <string.h>

    #include <sys/types.h>
    #include <sys/socket.h>

    #include <arpa/inet.h>
    #include <netinet/in.h>

    int main(void)
    {
            int     sockfd;
            struct  sockaddr_in my_addr;

            sockfd = socket(PF_INET , SOCK_STREAM , 0);
            if(sockfd == -1)
                   return -1;

            my_addr.sin_family = AF_INET;
            my_addr.sin_port = htons(80);
            my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
            memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));


            return 0;
    }
    اول داله
    Code: [Select]
    int socket(int domain, int type, int protocol);
    domain = PF_INET
    وده لسبب بسيط جدا وهو ان في بعض البروتوكولات ال domain الخاص ب AF_INET مش بيدعمها ولكن PF_INET بيدعمها ولذلك استخدام PF_INTET (PF = Protocol family) واستخدام AF_INET في ال sockaddr_in لل addressing domain
    ثانيا ال type
    SOCK_STREAM , SOCK_DGRAM ?
    رابعا ال protocol عادة بتكون القيمه 0 والاختيار بيرجع للكيرنال ولكن كما قلنا من قبل ووضحنا الاختلاف بين الاتنين فال tcp الي بيتم اختياره في حالة ال streaming
     
    قيمة الارجاع طبعا بتكون file descriptor ل socket مفتوح.

    جميل بعد مابقي عندنا socket متفوح ايه الي حيحصل ؟ حاجه من الاتنين يانتصل يانستني اتصال
    وده معناه اننا ممكن نستخدم bind او connect
    bind بيتم استخدامها عشان تعمل bind لل socket مع ال ip وال  port الخاص بالجهاز الي عاوز تستقبل عليه الاتصال او البيانات
    انما connect بتحتاج ال socket file descriptor و structure تحتوي علي address للجهاز المتصل بيه

    هنتكلم عن bind الاول


    Code: [Select]
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>

    #include <sys/types.h>
    #include <sys/socket.h>

    #include <arpa/inet.h>
    #include <netinet/in.h>

    int main(void)
    {
            int     sockfd = 0;
            int     r = 0;
            struct  sockaddr_in my_addr;

            sockfd = socket(PF_INET , SOCK_STREAM , 0);
            if(sockfd == -1)
                   return -1;

            my_addr.sin_family = AF_INET;
            my_addr.sin_port = htons(80);
            my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
            memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));

            r = bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr));
            if(r == -1)
            {
                    fprintf(stderr , "error at binding");
                    return -1;
            }

            close(sockfd);
            return 0;
    }
    نفس الكلام في الكود الي فات مع اختلاف الداله الجديده
    Code: [Select]
     int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    اول معطي هو ال socket file descriptor , تاني معطي هو ال address structure التالت هو حجم ال structure ديت
    وبكده يبقي انت عامل bind علي البورت 80 تحت اي ip
    بس يعني اي تحت اي ip ? يعني اي كونيكشن لل port 80 هيتوصل بيك . بس انت مش عاوز كده صح ؟
    Code: [Select]
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>

    #include <sys/types.h>
    #include <sys/socket.h>

    #include <arpa/inet.h>
    #include <netinet/in.h>

    int main(void)
    {
            int             sockfd = 0;
            int             r = 0;
            struct          sockaddr_in my_addr;
            const char      *addr = "127.0.0.1";

            sockfd = socket(PF_INET , SOCK_STREAM , 0);
            if(sockfd == -1)
                   return -1;

            my_addr.sin_family = AF_INET;
            my_addr.sin_port = htons(80);
            if(inet_aton(addr , &my_addr.sin_addr) == 0)
            {
                    fprintf(stderr , "Error at forming address\n");
                    return -1;
            }

            memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));

            r = bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr));
            if(r == -1)
            {
                    fprintf(stderr , "error at binding\n");
                    return -1;
            }

            close(sockfd);
            return 0;
    }

    Code: [Select]
    int inet_aton(const char *cp, struct in_addr *inp);

    بتاخد ip address في سلسله نصيه وبتقوم بتحويله بطريقتها ل ip address وتعمله تسجيل في ال sin_addr وبتقوم بتحويله كمان ل net work byte order

    جميل قبل منعمل connect علي اي حاجه اي رايك في اننا نعمل سرفر بسيط ؟ وبعدين نعمل ليه ال client ونبقي خلصنا ال tutorial في سوري واحد ؟

    Code: [Select]
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>

    #include <sys/types.h>
    #include <sys/socket.h>

    #include <arpa/inet.h>
    #include <netinet/in.h>

    int main(void)
    {
            int             sockfd = 0;
            int             r = 0;
            socklen_t       len = 0;
            struct          sockaddr_in my_addr;
            struct          sockaddr_in his_addr;
            const char      *addr = "127.0.0.1";

            sockfd = socket(PF_INET , SOCK_STREAM , 0);
            if(sockfd == -1)
                   return -1;

            my_addr.sin_family = AF_INET;
            my_addr.sin_port = htons(80);
            if(inet_aton(addr , &my_addr.sin_addr) == 0)
            {
                    fprintf(stderr , "Error at forming address\n");
                    return -1;
            }

            memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));

            r = bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr));
            if(r == -1)
            {
                    fprintf(stderr , "error at binding\n");
                    return -1;
            }

            printf("[+]Listening on %s at port %d\n"
                            ,inet_ntoa(my_addr.sin_addr)
                            , ntohs(my_addr.sin_port));

            if(listen(sockfd , 0) != 0)
            {
                    fprintf(stderr , "Error while listening\n");
                    return -1;
            }

            if(accept(sockfd , (struct sockaddr*)&his_addr , &len) != 0)
            {
                    fprintf(stderr , "Error in accepting connection\n");
                    return -1;
            }

            printf("Connection received from %s at %d\n",
                            inet_ntoa(his_addr.sin_addr)
                            ,ntohs(his_addr.sin_port));
            close(sockfd);
            return 0;
    }

    الدوال الجديده

    Code: [Select]
    char *inet_ntoa(struct in_addr in);

    الداله ديت بتقوم بتحويل address من network byte order ل ascii string بيتوصف فيها ال ip بصورته الطبيعيه
    1.1.1.1

    Code: [Select]
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    int listen(int sockfd, int backlog);

    اول حاجه listen بتقوم بعمل listen علي ال socket الي انت عامله bind بالفعل وبتستني اي connection علي ال port الي انت محدده في ال address structure الي انت عاملها bind مع  ال socket
    تاني متغير هو طابور , يعني يقدر يعمل طابور لكام client عاوز يعمل اتصال بالسرفر ؟ احنا عملنا 0
    لان بالفعل ملهاش اي لزمه او قيمه معانا
    نيجي ل accept , القيمه التي تم ارجاعها من accept هي file descriptor ل socket بينك و بين ال client وهو ده الي من خلاله تقدر read/write مع ال client التاني
    واحد يسالني ال socket الاول كده لزمته ايه ؟
    accepting or connecting ! مكنتش هتقدر تفتح قناة اتصال وتعمل listen علي بورت او ip معين من غير ال socket دوت

     وبكده يبقي خلصنا ال server
    نيجي لل client
    Code: [Select]
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>

    #include <sys/types.h>
    #include <sys/socket.h>

    #include <arpa/inet.h>
    #include <netinet/in.h>

    static void usage(const char *argv)
    {
            fprintf(stderr , "%s ip port\n",argv);
            _exit(0);
    }

    int main(int argc , char **argv)
    {
            int             sockfd = 0;
            int             r = 0;
            struct          sockaddr_in his_addr;

            if(argc < 3)
                    usage(argv[0]);

            sockfd = socket(PF_INET , SOCK_STREAM , 0);
            if(sockfd == -1)
                   return -1;
            his_addr.sin_family = AF_INET;
            his_addr.sin_port = htons(atoi(argv[2]));

            if(inet_aton(argv[1] , &his_addr.sin_addr) == 0)
            {
                    fprintf(stderr , "Ip error\n");
                    usage(argv[0]);
            }
            memset(his_addr.sin_zero , 0 , sizeof(his_addr.sin_zero));

            if(connect(sockfd , (struct sockaddr*)&his_addr , sizeof(his_addr)) == -1)
            {
                    fprintf(stderr , "connecting error\n");
                    return -1;
            }

            close(sockfd);
            return 0;
    }
    استخدمنا داله واحده جديده
    Code: [Select]
    int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

    اولا الخطوات المتبعه العاديه في forming an address وده هيكون للشخص المتصل بيه
    فتح socket
    استدعاء الداله واعطائها الsocket وال address والحجم الخاص بال address

    الجدير بالذكر اننا لم نقم بعمل bind ! بالظبت لاننا مش محتاجينه وفي الحاله ديت ال address الخاص بال client يسمي wild address ال ip الداخلي هيكون 0.0.0.0 والبورت كمان هيكون 0 الا اذا اردت انك تقوم بعمل bind لل socket واعطائه address معين وبورت معين كمان ديت ترجعلك

    طبعا الاتصال تقدر تعمل send/recv او read/write وديت مهمتك انت لان الموضوع مش مستحق الذكر هنا علاوه علي اني حاسس بوجع رهيب في ضهري دلوقتي يمكن اضيف الجزئيه البسيطه ديت بعدين
    « Last Edit: August 23, 2009, 04:09:17 PM by St0rM »
    Logged

    St0rM

    • [C programmer]
    • Administrator
    • Active Member
    • *****
    • Posts: 209
    • Why So serious ?
      • View Profile
      • WWW
      • Email
    Re: Network programming in Linux using c
    « Reply #1 on: August 23, 2009, 05:58:22 PM »
    كتابع للجزية ديت هنقوم بعمل تطبيق server/client عادي جدا زي الي قمنا بتطبيقه فوق ولكن هنضمن فيه خاصية تانيه انه هيعمل send ل quote of the day
    ال quote دوت هيتم تطبيقه علي اساس انه مجموعه من السلال النصيه وهنعمل random number نختار منها السلسله الي هنبعتها
    هبدأ بال server.

    Code: [Select]
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    #include <sys/types.h>
    #include <sys/socket.h>

    #include <netinet/in.h>
    #include <arpa/inet.h>

    #include <errno.h>

    char *quotes[] = {"There surely is in human nature an inherent propensity to extract"
                      "all the good out of all the evil.\n"
                      ,"?When choosing between two evils, I always like to try the"
                       " one I've never tried before\n"
                      ,"Sometimes I need what only you can provide: your absence\n"
                      ,"This is your life and it's ending one minute at a time.\n"
                      ,"Only after disaster can we be resurrected.\n"
                      ,"I am Jack's smirking revenge.\n"
                      ,"I have seen the world through your eyes and it makes me sick\n"
                      ,"War does not determine who is right - only who is left\n"
                      ,"A soldier will fight long and hard for a bit of colored ribbon\n"
                      ,"A man will fight harder for his interests than for his rights.\n"
                      ,"Death is nothing, but to live defeated and inglorious is to die daily.\n"
                      ,"DO NOT hate your enemies , it affects your judjment\n"
                      ,NULL};

    int len(char **quotes)
    {
            int x;

            for(x = 0 ; quotes[x] ; x++)
                    ;

            return x;
    }

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

            int sockfd = 0;
            int clientsockfd = 0;
            int length = 0;
            int rnumber = 0;

            socklen_t hisaddrlen = 0;
            struct sockaddr_in my_addr;
            struct sockaddr_in his_addr;

            if((sockfd = socket(PF_INET , SOCK_STREAM , 0)) == -1)
                    return -fprintf(stderr , "Error creating socket\n");

            my_addr.sin_family = AF_INET;
            my_addr.sin_port = htons(1050);
            my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
            /*listen to all packets destined to port 1050*/

            if(bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr)) != 0)
                    return -fprintf(stderr , "Error on binding address\n");

            while(1) /* keep listening and accepting */
            {
                    if(listen(sockfd , 5) != 0)
                            return -fprintf(stderr , "Error while listening\n");

                    clientsockfd = accept(sockfd , (struct sockaddr*)&his_addr , &hisaddrlen);

                    if(clientsockfd == -1)
                            return -fprintf(stderr , "accept connection faild %s\n",
                                            strerror(errno));

                    /* Connection is established
                     * pick a random number
                     * send him the quote
                     */

                    length = len(quotes);
                    srand((unsigned)time(0));
                    rnumber = (rand() % length);

                    if(send(clientsockfd ,quotes[rnumber] , strlen(quotes[rnumber]) , 0) == -1)
                            return -fprintf(stderr , "Sending error\n");
            }

            printf("%s %d\n",inet_ntoa(his_addr.sin_addr) , ntohs(his_addr.sin_port));

            close(sockfd);
            return 0;
    }


    كل الي بيحصل هو كل الي عرفناه عدا حاجه جديده
    اننا عملنا endless loop عشان تعمل listen , accept كل متنتهي connection
    وطبعا لازم نقفل ال client socket كل مره ال connection عن طريق اننا نقفل ال file descriptor بتاع ال client وده لان ال connection خلصت ومفيش داعي ال file descriptor يفضل محجوز علي الفاضي

    بالنسبه لداله send بتقوم بعمل send لل data الي موجوده في ال buffer استخدامها زي استخدام write بالظبت في الحاله ديت
    طبعا ال 0 الي في الاخر دوت قيمه بيتم تمرريرها ل send عشان نقلها اانا مش محتاجين اي flags وقت ال sending , موضوع تاني ال flags دوت ليه نقاشه ومش محتاج تعرف عنه حاجه دلوقتي

    اول مبيعمل send بيقفل ال clientsockfd وبيعمل listen من اول وجديد و accept وهكذا
    دور ال client


    Code: [Select]
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    #include <sys/socket.h>
    #include <sys/types.h>

    #include <netinet/in.h>
    #include <arpa/inet.h>

    static void usage(const char *name)
    {
            fprintf(stderr , "(%s) ip port\n" , name);
    }

    int main(int argc , char **argv)
    {
            int     sockfd = 0;
            int     port = 0;
            char    buffer[BUFSIZ];
            int     r = 0;
            /* there is no need to bind to our own address
             * in case of connecting to server
             * connect() does this for us
             */

            struct  sockaddr_in his_addr;

            if(argc < 3)
            {
                    usage(argv[0]);
                    return -1;
            }

            port = atoi(argv[2]);

            if(port <= 0)
            {
                    usage(argv[0]);
                    return -1;
            }

            if((sockfd = socket(PF_INET , SOCK_STREAM , 0)) == -1)
                    return -fprintf(stderr , "Error creating socket\n");


            his_addr.sin_family = AF_INET;
            his_addr.sin_port = htons(port);
            if(! inet_aton(argv[1] , &his_addr.sin_addr))
                    return -fprintf(stderr , "Ip address error\n");

            if(connect(sockfd , (struct sockaddr*)&his_addr , sizeof(his_addr)) == -1)
                    return -fprintf(stderr , "Error connecting\n");

            /* keep receiving while its
             * not an error nor the end of the string
             */

            while((r = recv(sockfd , buffer , BUFSIZ , 0)) != -1)
            {
                    printf("%s",buffer);
                    if(buffer[r] == '\0')
                            break;
            }

            if(r == -1)
                    return -fprintf(stderr , "Recieving error\n");

            close(sockfd);
            return 0;
    }

    طبعا الشرح واضح جدا , كل الجزئيه الي هفصلها هي
    1- معملناش bind() عشان مش محتاجينه
    2- استخدمنا recv الي بترجع قيمة ال received data وتحققنا اذا كانت -1 انهينا الدوره , واذا كانت اخر حاجه في ال buffer هي NULL CHARACTER يبقي ننهي الدوره لان بذلك الارسال انتهي

    انتهينا من التطبيق البسيط دوت وهنقوم بعمل تطبيق تاني منه بيعمل معالجه لاكتر من connection في نفس الوقت

    Logged

    St0rM

    • [C programmer]
    • Administrator
    • Active Member
    • *****
    • Posts: 209
    • Why So serious ?
      • View Profile
      • WWW
      • Email
    Re: Network programming in Linux using c
    « Reply #2 on: August 25, 2009, 07:12:07 AM »
    Sal3atchy web server

    ايون بالظبت هو ده تطبيقنا النهرده , صلعاتشي ويب سرفر , قصة صلعاتشي طويله جدا بدأت ب system call بسيطه جدا , file descriptor بسيط جدا
    وانتهت ب close :( وياعيني علي الي راح  ??? محدش يعلق علي كلامي عشان انا صايم ماشي ؟  :D

    المهم يارجاله درسنا النهرده هيتكلم عن ويب سرفر بسيط
    مش صلعاتشي بكامل قوته يعني انا حاخد الجزء البسيط الي هنتكلم عليه منه الا وهو جزء ال Multithreaded server

    يعني ايه بقي الكلمه الطويله ديت ؟
    رجع بقي ضهرك كده لورا عشان ححكيلك حكايه جميله , زمان ايام اجدادنا الفراعنه كانت الاجهزه قليله يادوب 10 اجهزه في المملكه العائليه بتاعت العائله السابعه عشر
    وكانوا العيال ولاد فرعون بييستخدموا النتورك عشان يبعتوا رسايل لبعض
    والي كان بيربط الرسايل ديت ببعضها هو server بسيط وغلبان بياخد one request at time
    بس بعد ماتطورنا ووصلنا لمرحة التمدن والتحضر واصبح في مصر مالايقل عن 100 جهاز الكلام دوت مبقاش ينفع ! كان لازم نشوف حل
    ومن ثم , توافق السحره مع بعضيهم واجتمعوا علي انهم لازم يستخدموا طريقة ما عشان يقدروا يحلوا مشكلة ال delay الرهيبه الي كانت بتحصل لما حد بيطلب حاجه من ال server دوت , وجاء الوحي
    استخدام برنامج صلعاتشي  :D تصميم برنامج صلعاتشي بسيط جدا انه هيعمل socket واحد يعمل بيه listen علي بورت معين
    وكل اتصال هيتم قبوله مش هيتم قبوله واستخدامه وبعدين يعمل listen  تاني , لا
    صلعاتشي غير مفاهيم الدنيا لل web servers وقال ان كل request جديده هيتم التعامل معها في process منفصله .

    اي انه ينقسم صلعاتشي الي جزئين , جزء لل listening وجزء اخر يقوم بعمل عمليه جديده كلما جاء request جديده ويعمل له accept ويستخدمه

    وبكده نكون انهينا قصة صلعاتشي ودخلنا في قصة تاني , طب اذاي نقدر نعمل كده ؟
    طبعا انا قلتلك اننا هنقوم بعمل عمليه منفصله لكل request "ملاحظة ان في طرق تانيه زي select/poll بس مش هناقشها هنا هناقشها بعدين "
    دخلنا في الجد بقي وركز معايا في الكلام الجاي

    في حاجه اسمها process forking
    بتقوم بعمل copy من ال  process الي شغاله دلوقتي بال variables بتاعتها وال file descriptor set وكل حاجه
    وده ملوش غير معني واحد بس اننا لو عملنا aceept في ال process الاصليه وبعد ماعملنا accept عملنا fork ل process جديده معني كده اننا هيكون معانا ال file descriptor لل connected socket ونقدر نتعامل معاه زي ماحنا عاوزين جميل ؟

    وبعد منعمل العمليه الجديده والي هتكون في نفس الوقت بتتنفذ بعيد عن العمليه الاصليه , نقدر نعمل  listen من اول وجديد تاني ونسيب العمليه التانيه تخلص حالها مع نفسها
    فكر فيها كأنها برنامج منفصل بيتعامل مع كل request لوحده
    اتفضل بقي اتفرج علي الكود بتاع صلعاتشي فراعنه فيرجن 1.0

    ملاحظة : صلعاتشي هيكون برنامج فعلي قريب وبيتم التجهيز ليه , انا حبيت اشرح جزء ال multithreaded connections منه

    Code: [Select]

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>

    #include <netinet/in.h>
    #include <arpa/inet.h>

    #include <errno.h>


    static void use(const char *name)
    {
            fprintf(stderr , "%s port\n",name);
    }

    const char *http_replay = "<html>"
                            "<title> SLA3ATCHY PHAROS V1.0 Simple </title>"
                            "<body>"
                            "<p>IT WORKSSS!!!</p>"
                            "</body>"
                            "</html>";

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

            int     fd = 0;
            int     sockfd = 0;
            socklen_t his_addr_len = 0;
            int     ftop = 0;
            int     rt = 0;


            struct  sockaddr_in his_addr;
            struct  sockaddr_in my_addr;

            if(argc < 2)
            {
                    use(argv[0]);
                    _exit(0);
            }

            fd = socket(PF_INET , SOCK_STREAM , 0);
            if(fd == -1)
            {
                    perror("socket");
                    _exit(errno);
            }

            my_addr.sin_family = AF_INET;
            my_addr.sin_port = htons(atoi(argv[1]));
            my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

            /* good now addressing is set
             * BIND IT
             */

            rt = bind(fd , (struct sockaddr*)&my_addr , sizeof(my_addr));
            if(rt == -1)
            {
                    perror("bind");
                    _exit(errno);
            }

            /* now that we are bindded
             * we can listen
             * and then we can accept
             * time to listen
             */

            while(1)
            {
                    rt = listen(fd , 5);

                    if(rt == -1)
                    {
                            perror("listen");
                            break;
                    }

                    printf("[+]Listening for connections on %s:%d\n"
                                    ,inet_ntoa(my_addr.sin_addr)
                                    ,ntohs(my_addr.sin_port));
                    sockfd = accept(fd
                                    , (struct sockaddr*)&his_addr
                                    ,&his_addr_len);
                    if(sockfd == -1)
                    {
                            perror("accept");
                            break;
                    }
                    /* we have accepted the connection
                     * time to creat another process
                     * to handel the connection
                     */

                    ftop = fork();

                    switch(ftop)
                    {
                            case 0: /* child process */
                                    rt = send(sockfd , http_replay , strlen(http_replay) , 0);
                                    if(rt == -1)
                                    {
                                            perror("send");
                                            _exit(rt);
                                    }
                                    _exit(rt); /*this process is done */
                                    break;
                            case -1:
                                    perror("fork");
                                    break;
                            default: /*parent process*/
                                    close(sockfd); /* we dont need him*/
                                    break;
                    }

                    /* check for any errors
                     * if any break the loop
                     */

                    if(rt == -1)
                            break;


            }

            if(rt == -1)
                    fprintf(stderr , "server terminated with an error"
                                    "check log files");

            close(fd);

            return rt;
    }


    كل اجزاء الكود مفهوه ناقص حاجه واحده بس

    بعد مابتبدأ accept في التنفيذ وتقوم بارجاع ال connected sockets
    ده بيكون في ال file descriptor table بتاع العمليه الاساسيه , الجدول دوت بيحتوي علي كل ال file descriptors المتفوحه لعمليه ما

    لما بنعمل عمليه تاني بناخد كوبي من الجدول دوت + كوبي من كل المتغيرات الموجوده بداخل البرنامج
    بس العمليه الاصليه مش محتاجه ال connected socket دوت في اي حاجه عشان كده بتقفله

    اما العمليه الفرعيه الي بتقوم بعمل handle لل connection وبتبعت رسالة لل web browser هي الي محتاجاه وعندها كوبي منه يعني تقدر تشتغل عليه بدون تداخل من العمليه التانيه
    بعد مبنخلص بنعمل exit لل process ديت عشان متبقاش معلقة في ال system علاوه علي اننا لو معملناش exit هيبقي  عندنا دور تانيه من ال socket listening وده هيسبب خلل رهيب في البرنامج
    عشان تفهم fork() بشكل مبسط راجع ال man pages بتاعتها

    وبكده نكون خلصنا الجزء البسيط من صلعاتشي فراعنه ويب سيرفر  8) انتظرونا في ال real application بتاعته  :D

    انتهينا من الدوره
    « Last Edit: August 25, 2009, 08:06:40 AM by St0rM »
    Logged

    St0rM

    • [C programmer]
    • Administrator
    • Active Member
    • *****
    • Posts: 209
    • Why So serious ?
      • View Profile
      • WWW
      • Email
    بعد فتره من ظهور الدوره دوت ولاني تجاهلت تماما موضوع ال ipv6 فكرت اني المفروض احط ال ipv6 بس لان وقت مكنت بكتبها كنت بكتبها للمبتدئين جدا , افتكر دلوقتي انك في مستوي كويس

    عموما نتكلم عن ال IPV6 : النسخه التانيه الي ظهرت بعد IPV4 لن ال IPV4 مش مكفي ! بس خلاص مش هتكلم في حاجه تاني  :D

    الفكره ان دلوقتي عاوز اضيف 2 Library calls من ال BSD Socket api للتعامل مع ال ipv6 او حتي التعامل ال generic

    اولا  getaddrinfo بدل من gethostbyname او الكودينج بايدك ال address عن طريق inet_ntoa inet_aton كل ده خلاص راح  :(

    Code: [Select]
           #include <sys/types.h>
           #include <sys/socket.h>
           #include <netdb.h>

           int getaddrinfo(const char *node, const char *service,
                           const struct addrinfo *hints,
                           struct addrinfo **res);

           void freeaddrinfo(struct addrinfo *res);

           const char *gai_strerror(int errcode);

    الفكره كلها بتتلخص انك بتعمل 2 structure ده لو حبيت ممكن واحد بس وتتجاهل ال hints
    Code: [Select]
               struct addrinfo {
                   int              ai_flags;
                   int              ai_family;
                   int              ai_socktype;
                   int              ai_protocol;
                   size_t           ai_addrlen;
                   struct sockaddr *ai_addr;
                   char            *ai_canonname;
                   struct addrinfo *ai_next;
               };

    بتورد فيها المعلومات الي انت بتقترح علي الداله انها تبص عليها وبعدين pointer لل structure ديت بتمرر العنوان بتاعه للداله وهي بتقوم بعمل قائمه متصله بالعناوين ونوع البروتوكول المستخدم , وتقدر تلف في القايمه براحتك وتختار العنوان والبروتوكول المرغوب فيه
    طبعا محتاج تعمل free لل linked list ديت , ومحتاج داله تديك الخطأ وده وظيفة الدالتين التانين

    وبالخلاف طبعا inet_ntoa هتكون inet_ntop
    man inet_ntop

    Code: [Select]
    #include <stdio.h>                                                                                                                   
    #include <string.h>                                                                                                                 
    #include <stdlib.h>                                                                                                                 
    #include <unistd.h>                                                                                                                 
    #include <sys/types.h>                                                                                                               
    #include <sys/socket.h>                                                                                                             
    #include <netdb.h>                                                                                                                   


    /* Program to show the ip and ip protocol for a host name */

    void usage(const char *name)
    {                           
            fprintf(stderr,"%s www.example.com||example.com\n",name);
    }                                                               

    int main(int argc , char *argv[])
    {                               
            struct addrinfo hints , *list , *tmp;
            int ret = 0 ;                       
            void *addr;                         
            char *version , ipaddr[INET6_ADDRSTRLEN];
            struct sockaddr_in *ipv4;               
            struct sockaddr_in6 *ipv6;               

            if(argc < 2 )
            {           
                    usage(argv[0]);
                    exit(0);       
            }

            memset(&hints , 0 , sizeof(struct addrinfo));

            hints.ai_family = AF_UNSPEC;


    بالمنظر ده احنا معانا كل ال addresses بتاعت host.com الي طلبناها مثلا
    طب اذاي هنعمل كونيكشن عليه ؟
    باستخدام

    Code: [Select]
                   struct sockaddr *ai_addr;

    يعني
    Code: [Select]
    connect(socket_fd , tmp->ai_addr , tmp->ai_addrlen);

    good luck
    Logged