תכנות Socket ב-C

תכנות Socket ב-C

תכנות שקעים היא דרך לחבר שני צמתים ברשת כדי לתקשר זה עם זה. שקע אחד (צומת) מקשיב ליציאה מסוימת ב-IP ואילו השקע השני מושיט יד לשני כדי ליצור חיבור. השרת יוצר את שקע המאזין בזמן שהלקוח מושיט יד לשרת.
תכנות Socket נמצא בשימוש נרחב ביישומי הודעות מיידיות סטרימינג בינארי ושיתופי פעולה מסמכים פלטפורמות סטרימינג מקוונות וכו'.

דוּגמָה

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

server.c

C
   #include         #include         #include         #include         #include         #include         #define PORT 8080   int     main  (  int     argc       char     const  *     argv  [])   {      int     server_fd       new_socket  ;      ssize_t     valread  ;      struct     sockaddr_in     address  ;      int     opt     =     1  ;      socklen_t     addrlen     =     sizeof  (  address  );      char     buffer  [  1024  ]     =     {     0     };      char  *     hello     =     'Hello from server'  ;      // Creating socket file descriptor      if     ((  server_fd     =     socket  (  AF_INET       SOCK_STREAM       0  ))      <     0  )     {      perror  (  'socket failed'  );      exit  (  EXIT_FAILURE  );      }      // Forcefully attaching socket to the port 8080      if     (  setsockopt  (  server_fd       SOL_SOCKET        SO_REUSEADDR     |     SO_REUSEPORT       &  opt        sizeof  (  opt  )))     {      perror  (  'setsockopt'  );      exit  (  EXIT_FAILURE  );      }      address  .  sin_family     =     AF_INET  ;      address  .  sin_addr  .  s_addr     =     INADDR_ANY  ;      address  .  sin_port     =     htons  (  PORT  );      // Forcefully attaching socket to the port 8080      if     (  bind  (  server_fd       (  struct     sockaddr  *  )  &  address        sizeof  (  address  ))       <     0  )     {      perror  (  'bind failed'  );      exit  (  EXIT_FAILURE  );      }      if     (  listen  (  server_fd       3  )      <     0  )     {      perror  (  'listen'  );      exit  (  EXIT_FAILURE  );      }      if     ((  new_socket      =     accept  (  server_fd       (  struct     sockaddr  *  )  &  address        &  addrlen  ))       <     0  )     {      perror  (  'accept'  );      exit  (  EXIT_FAILURE  );      }          // subtract 1 for the null      // terminator at the end      valread     =     read  (  new_socket       buffer        1024     -     1  );         printf  (  '%s  n  '       buffer  );      send  (  new_socket       hello       strlen  (  hello  )     0  );      printf  (  'Hello message sent  n  '  );      // closing the connected socket      close  (  new_socket  );          // closing the listening socket      close  (  server_fd  );      return     0  ;   }   

לקוח.ג

C
   #include          #include         #include         #include         #include         #define PORT 8080   int     main  (  int     argc       char     const  *     argv  [])   {      int     status       valread       client_fd  ;      struct     sockaddr_in     serv_addr  ;      char  *     hello     =     'Hello from client'  ;      char     buffer  [  1024  ]     =     {     0     };      if     ((  client_fd     =     socket  (  AF_INET       SOCK_STREAM       0  ))      <     0  )     {      printf  (  '  n   Socket creation error   n  '  );      return     -1  ;      }      serv_addr  .  sin_family     =     AF_INET  ;      serv_addr  .  sin_port     =     htons  (  PORT  );      // Convert IPv4 and IPv6 addresses from text to binary      // form      if     (  inet_pton  (  AF_INET       '127.0.0.1'       &  serv_addr  .  sin_addr  )       <=     0  )     {      printf  (      '  n  Invalid address/ Address not supported   n  '  );      return     -1  ;      }      if     ((  status      =     connect  (  client_fd       (  struct     sockaddr  *  )  &  serv_addr        sizeof  (  serv_addr  )))       <     0  )     {      printf  (  '  n  Connection Failed   n  '  );      return     -1  ;      }          // subtract 1 for the null      // terminator at the end      send  (  client_fd       hello       strlen  (  hello  )     0  );      printf  (  'Hello message sent  n  '  );      valread     =     read  (  client_fd       buffer        1024     -     1  );         printf  (  '%s  n  '       buffer  );      // closing the connected socket      close  (  client_fd  );      return     0  ;   }   


קומפילציה

 gcc client.c -o clientgcc server.c -o server  


תְפוּקָה

 Client:Hello message sentHello from serverServer:Hello from clientHello message sent  

רכיבי תכנות Socket

1. שקעים

שקעים הם אחד ממרכיבי הליבה המשמשים את התוכנית כדי לגשת לרשת כדי לתקשר עם תהליכים/צמתים אחרים ברשת. זה פשוט שילוב של כתובת IP ומספר יציאה המשמש כנקודת קצה לתקשורת.
דוגמה: 192.168.1.1:8080 כאשר שני החלקים המופרדים על ידי המעי הגס מייצגים את כתובת IP ( 192.168.1.1 ) ואת מספר יציאה (8080).

סוגי שקעים:

  • שקע TCP (שקע זרם): מספק תקשורת מבוססת חיבור אמינה (כלומר. פרוטוקול TCP ).
  • שקע UDP (שקע דאטגרם): מספק תקשורת ללא חיבור מהירה יותר אך לא אמינה (כלומר. פרוטוקול UDP ).

2. מודל שרת-לקוח

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

דיאגרמת מצב עבור מודל שרת ולקוח

תכנות Socket ב-Cדיאגרמת מצב עבור מודל שרת ולקוח של Socket

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

יצירת תהליך בצד השרת

השרת נוצר באמצעות השלבים הבאים:

1. יצירת שקעים

שלב זה כולל את יצירת השקע באמצעות הפונקציה socket() .

פרמטרים:

  • sockfd: מתאר socket מספר שלם (כמו ידית קובץ)
  • תְחוּם: מספר שלם מציין תחום תקשורת. אנו משתמשים ב-AF_ LOCAL כפי שהוגדר בתקן POSIX לתקשורת בין תהליכים באותו מארח. לתקשורת בין תהליכים במארחים שונים המחוברים באמצעות IPV4 אנו משתמשים ב-AF_INET וב-AF_I NET 6 עבור תהליכים המחוברים באמצעות IPV6.
  • סוּג: סוג תקשורת
    SOCK_STREAM: TCP (מכוון חיבור אמין)
    SOCK_DGRAM: UDP (לא אמין ללא חיבור)
  • פּרוֹטוֹקוֹל: ערך פרוטוקול עבור פרוטוקול אינטרנט (IP) שהוא 0. זהו אותו מספר שמופיע בשדה הפרוטוקול בכותרת ה-IP של חבילה. (פרוטוקולים של מאן לפרטים נוספים)
C
   sockfd     =     socket  (  domain       type       protocol  )   

2. הגדר socket opt

זה עוזר במניפולציה של אפשרויות עבור השקע שמפנה מתאר הקובץ sockfd. זה אופציונלי לחלוטין אבל זה עוזר בשימוש חוזר בכתובת וביציאה. מונע שגיאות כגון: כתובת כבר בשימוש.

C
   setsockopt  (  sockfd       level       optname       optval       socklen_t     optlen  );   

3. לאגד

לאחר יצירת השקע הפונקציה bind() קושרת את השקע לכתובת ולמספר היציאה שצוינו ב-adr(מבנה נתונים מותאם אישית). בקוד לדוגמה אנו קושרים את השרת ל-localhost ולכן אנו משתמשים ב-INADDR_ANY כדי לציין את כתובת ה-IP.

C++
   bind  (  sockfd       sockaddr     *  addr       socklen_t     addrlen  );   

פרמטרים:

  • sockfd : מתאר קובץ socket שנוצר באמצעות הפונקציה socket() .
  • addr : מצביע ל-struct sockaddr המכיל את כתובת ה-IP ומספר היציאה כדי לאגד את השקע.
  • addrlen : אורך מבנה ה-adr.

4. הקשיבו

בשלב זה השרת משתמש בפונקציה listen() שמכניסה את שקע השרת למצב פסיבי בו הוא ממתין שהלקוח יתקרב לשרת כדי ליצור חיבור. ה-backlog מגדיר את האורך המקסימלי אליו עשוי תור החיבורים הממתינים ל-sockfd לגדול. אם תגיע בקשת חיבור כאשר התור מלא, הלקוח עשוי לקבל שגיאה עם אינדיקציה של ECONNREFUSED.

C
   listen  (  sockfd       backlog  );   

פרמטרים :

  • sockfd : מתאר קובץ socket שנוצר באמצעות הפונקציה socket() .
  • פיגור : מספר המייצג את גודל התור המחזיק את החיבורים הממתינים בזמן שהשרת ממתין לקבל חיבור.

5. קבל

בשלב זה השרת מחלץ את בקשת החיבור הראשונה מהתור של חיבורים ממתינים עבור שקע ההאזנה sockfd יוצר שקע מחובר חדש באמצעות לְקַבֵּל() function ומחזיר מתאר קובץ חדש המתייחס לאותו שקע. בשלב זה נוצר החיבור בין הלקוח לשרת והם מוכנים להעביר נתונים.

C
   new_socket  =     accept  (  sockfd       sockaddr     *  addr       socklen_t     *  addrlen  );   

פרמטרים:

  • sockfd : מתאר קובץ socket מוחזר על ידי socket() ו-bind().
  • addr : מצביע ל-struct sockaddr שיכיל את כתובת ה-IP ומספר היציאה של הלקוח.
  • addrlen : מצביע למשתנה המציין את אורך מבנה הכתובות.

6. שלח/קבל

בשלב זה השרת יכול לשלוח או לקבל נתונים מהלקוח.

לִשְׁלוֹחַ(): לשלוח נתונים ללקוח

C
   send  (  sockfd       *  buf       len       flags  );   

פרמטרים:

  • sockfd : מתאר קובץ socket שהוחזר על ידי הפונקציה socket() .
  • buff : מצביע למאגר המכיל את הנתונים שיש לשלוח.
  • רַק : מספר בתים של נתונים שיש לשלוח.
  • דגלים : מספר שלם המציין אפשרויות שונות לאופן שליחת הנתונים בדרך כלל 0 משמש להתנהגות ברירת מחדל.

קבל() : כדי לקבל את הנתונים מהלקוח.

C
   recv  (     sockfd       *  buf       len       flags  );   

פרמטרים:

  • sockfd : מתאר קובץ socket שהוחזר על ידי הפונקציה socket() .
  • buff : מצביע למאגר המכיל את הנתונים שיש לאחסן.
  • רַק : מספר בתים של נתונים שיש לשלוח.
  • דגלים : מספר שלם המציין אפשרויות שונות לאופן שליחת הנתונים בדרך כלל 0 משמש להתנהגות ברירת מחדל.

6. סגור

לאחר השלמת חילופי המידע השרת סוגר את השקע באמצעות הפונקציה close() ומשחרר את משאבי המערכת.

C
   close  (  fd  );   

פרמטרים:

  • fd: מתאר קובץ של השקע.

יצירת תהליך בצד הלקוח

בצע את השלבים הבאים ליצירת תהליך בצד הלקוח:

1. חיבור שקע

שלב זה כרוך ביצירת השקע שנעשה באותו אופן כמו זה של יצירת השקע של השרת

2. התחבר

קריאת המערכת connect() מחברת את השקע שאליו מתייחס מתאר הקובץ sockfd לכתובת שצוינה על ידי addr. הכתובת והיציאה של השרת מצוינות ב-adr.

C++
   connect  (  sockfd       sockaddr     *  addr       socklen_t     addrlen  );   

פרמטרים

  • sockfd : מתאר קובץ socket שהוחזר על ידי הפונקציה socket() .
  • addr : מצביע למבנה sockaddr המכיל את כתובת ה-IP ומספר היציאה של השרת.
  • addrlen : גודל הכתובת.

3. שלח/קבל

בשלב זה הלקוח יכול לשלוח או לקבל נתונים מהשרת, מה שנעשה באמצעות הפונקציות send() ו- recieve() בדומה לאופן שבו השרת שולח/מקבל נתונים מהלקוח.

4. סגור

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

בעיות נפוצות ותיקוניהן בתכנות Socket

  • כשלים בחיבור: כדי למנוע כשלים בחיבור, עלינו לוודא שהלקוח מנסה להתחבר אל הנכון כתובת IP ויציאה .
  • שגיאות כריכת יציאה: שגיאות אלו מתרחשות כאשר יציאה כבר נמצאת בשימוש על ידי יישום אחר בתרחיש זה, הקישור ליציאה זו תיכשל. נסה להשתמש ביציאה אחרת או סגור את היישום הקודם באמצעות היציאה.
  • חסימת שקעים: כברירת מחדל, שקעים חוסמים. המשמעות היא שקריאות כמו accept() או recv() ימתינו ללא הגבלת זמן אם אין חיבור לקוח או נתונים. ניתן להגדיר את השקע למצב לא חוסם במידת הצורך.
צור חידון