Програмування сокетів на 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 модель клієнт-сервер відноситься до архітектури, яка використовується в програмуванні сокетів, де клієнт і сервер взаємодіють один з одним для обміну інформацією або послугами. Ця архітектура дозволяє клієнту надсилати запити на обслуговування, а сервер обробляти та надсилати відповідь на ці запити на обслуговування.
Діаграма стану для моделі сервера та клієнта
Діаграма стану для моделі сервера та клієнта 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 для отримання додаткової інформації)
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() чекатимуть нескінченно довго, якщо немає підключення клієнта або даних. За потреби можна перевести сокет у неблокуючий режим.