Програмування сокетів на C

Програмування сокетів на C

Програмування сокетів це спосіб з'єднання двох вузлів у мережі для спілкування один з одним. Один сокет (вузол) прослуховує певний порт на IP-адресі, тоді як інший сокет зв’язується з іншим для створення з’єднання. Сервер формує сокет слухача, поки клієнт звертається до сервера.
Програмування сокетів широко використовується в додатках обміну миттєвими повідомленнями, бінарних потоках і спільної роботи над документами, платформах онлайн-потоку тощо.

приклад

У цій програмі на 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  ;   }   

client.c

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  

Компоненти програмування сокетів

1. Розетки

Розетки є одним із основних компонентів, які використовуються програмою для доступу до мережі для зв’язку з іншими процесами/вузлами в мережі. Це просто поєднання IP-адреси та номера порту, який діє як кінцева точка зв’язку.
Приклад: 192.168.1.1:8080 де дві частини, розділені двокрапкою, представляють IP-адреса (192.168.1.1) і номер порту (8080).

Типи розеток:

  • TCP-сокет (потоковий сокет): Забезпечує надійний зв’язок на основі з’єднання (тобто протокол TCP ).
  • UDP-сокет (Datagram Socket): Забезпечує швидший, але ненадійний зв’язок без з’єднання (тобто протокол UDP ).

2. Модель клієнт-сервер

The модель клієнт-сервер відноситься до архітектури, яка використовується в програмуванні сокетів, де клієнт і сервер взаємодіють один з одним для обміну інформацією або послугами. Ця архітектура дозволяє клієнту надсилати запити на обслуговування, а сервер обробляти та надсилати відповідь на ці запити на обслуговування.

Діаграма стану для моделі сервера та клієнта

Програмування сокетів на CДіаграма стану для моделі сервера та клієнта Socket

Програмування сокетів на C — потужний спосіб керування мережевим зв’язком.

Створення процесу на стороні сервера

Сервер створюється за допомогою наступних кроків:

1. Створення сокета

Цей крок передбачає створення сокета за допомогою функції socket().

Параметри:

  • sockfd: дескриптор сокета ціле число (як дескриптор файлу)
  • домен: ціле число вказує домен зв'язку. Ми використовуємо AF_ LOCAL, як визначено в стандарті POSIX для зв’язку між процесами на одному хості. Для обміну даними між процесами на різних хостах, підключених за допомогою IPV4, ми використовуємо AF_INET і AF_I NET 6 для процесів, підключених за допомогою IPV6.
  • тип: тип спілкування
    SOCK_STREAM: TCP (орієнтований на надійне з'єднання)
    SOCK_DGRAM: UDP (ненадійне підключення)
  • протокол: Значення протоколу для Інтернет-протоколу (IP), яке дорівнює 0. Це те саме число, яке відображається в полі протоколу в IP-заголовку пакета. (протоколи man для отримання додаткової інформації)
C
   sockfd     =     socket  (  domain       type       protocol  )   

2. Установіть опцію розетки

Це допомагає маніпулювати параметрами для сокета, на який посилається дескриптор файлу sockfd. Це абсолютно необов’язково, але це допомагає повторно використовувати адресу та порт. Запобігає помилкам, таким як: адреса вже використовується.

C
   setsockopt  (  sockfd       level       optname       optval       socklen_t     optlen  );   

3. Зв'язати

Після створення сокета функція bind() прив’язує сокет до адреси та номера порту, вказаних у addr(спеціальна структура даних). У прикладі коду ми прив’язуємо сервер до локального хосту, тому ми використовуємо INADDR_ANY для визначення IP-адреси.

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

Параметри:

  • sockfd : дескриптор файлу socket, створений за допомогою функції socket().
  • адреса : покажчик на структуру sockaddr, яка містить IP-адресу та номер порту для зв’язування сокета.
  • addrlen : довжина структури addr.

4. Слухайте

На цьому кроці сервер використовує функцію listen(), яка переводить серверний сокет у пасивний режим, де він очікує, поки клієнт підійде до сервера для встановлення з’єднання. Backlog визначає максимальну довжину, до якої може зрости черга незавершених підключень для sockfd. Якщо запит на підключення надходить, коли чергу заповнено, клієнт може отримати повідомлення про помилку з індикацією ECONNREFUSED.

C
   listen  (  sockfd       backlog  );   

Параметри :

  • sockfd : дескриптор файлу socket, створений за допомогою функції socket().
  • відставання : число, що представляє розмір черги, що містить з’єднання, що очікують, поки сервер очікує прийняття з’єднання.

5. Прийняти

На цьому кроці сервер витягує перший запит на з’єднання з черги незавершених з’єднань для прослуховуваного сокета sockfd створює новий підключений сокет за допомогою прийняти() і повертає новий файловий дескриптор, який посилається на цей сокет. У цей момент між клієнтом і сервером встановлено з’єднання, і вони готові до передачі даних.

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

Параметри:

  • sockfd : дескриптор файлу сокета, що повертається функціями socket() і bind().
  • адреса : покажчик на структуру sockaddr, яка зберігатиме IP-адресу та номер порту клієнта.
  • addrlen : покажчик на змінну, яка визначає довжину структури адреси.

6. Надіслати/отримати

На цьому кроці сервер може надсилати або отримувати дані від клієнта.

Надіслати(): для надсилання даних клієнту

C
   send  (  sockfd       *  buf       len       flags  );   

Параметри:

  • sockfd : дескриптор файлу сокета, що повертається функцією socket().
  • буф : покажчик на буфер, що містить дані для надсилання.
  • тільки : кількість байтів даних для надсилання.
  • прапори : ціле число, що вказує різні варіанти того, як зазвичай надсилаються дані. 0 використовується для поведінки за замовчуванням.

Receive() : отримати дані від клієнта.

C
   recv  (     sockfd       *  buf       len       flags  );   

Параметри:

  • sockfd : дескриптор файлу сокета, що повертається функцією socket().
  • буф : покажчик на буфер, що містить дані для збереження.
  • тільки : кількість байтів даних для надсилання.
  • прапори : ціле число, що вказує різні варіанти способу надсилання даних, як правило, 0 використовується для поведінки за замовчуванням.

6. Закрити

Після завершення обміну інформацією сервер закриває сокет за допомогою функції close() і звільняє системні ресурси.

C
   close  (  fd  );   

Параметри:

  • fd: дескриптор файлу сокета.

Створення процесу на стороні клієнта

Щоб створити процес на стороні клієнта, виконайте наведені нижче дії.

1. Розеткове підключення

Цей крок передбачає створення сокета, який виконується так само, як і створення сокета на сервері.

2. Підключитися

Системний виклик connect() з’єднує сокет, на який посилається дескриптор файлу sockfd, з адресою, вказаною в addr. Адреса та порт сервера вказані в адресі.

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

Параметри

  • sockfd : дескриптор файлу сокета, що повертається функцією socket().
  • адреса : покажчик на структуру sockaddr, що містить IP-адресу сервера та номер порту.
  • addrlen : розмір адреси

3. Надіслати/отримати

На цьому кроці клієнт може надсилати або отримувати дані від сервера, що виконується за допомогою функцій send() і recieve(), подібних до того, як сервер надсилає/отримує дані від клієнта.

4. Закрити

Після завершення обміну інформацією клієнт також повинен закрити створений сокет і звільнити системні ресурси за допомогою функції close() так само, як це робить сервер.

Поширені проблеми та їх вирішення в програмуванні сокетів

  • Помилки підключення: Щоб уникнути помилок підключення, ми повинні переконатися, що клієнт намагається підключитися до правильного IP-адреса та порт .
  • Помилки прив'язки портів: Ці помилки виникають, коли порт уже використовується іншою програмою, у цьому випадку прив’язка до цього порту не вдасться. Спробуйте використати інший порт або закрийте попередню програму за допомогою порту.
  • Блокування сокетів: За замовчуванням сокети блокуються. Це означає, що виклики на зразок accept() або recv() чекатимуть нескінченно довго, якщо немає підключення клієнта або даних. За потреби можна перевести сокет у неблокуючий режим.
Створіть вікторину