logo

Програмиране на сокет в C

Програмиране на сокет е начин за свързване на два възела в мрежа за комуникация помежду си. Единият сокет (възел) слуша определен порт на IP, докато другият сокет се свързва с другия, за да формира връзка. Сървърът формира гнездото за слушател, докато клиентът достига до сървъра.
Програмирането на сокети се използва широко в приложения за незабавни съобщения, двоичен стрийминг и сътрудничество на документи, онлайн стрийминг платформи и др.

Пример

В тази C програма обменяме едно поздравително съобщение между сървър и клиент, за да демонстрираме модела клиент/сървър.

сървър.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('%sn' buffer);  send(new_socket hello strlen(hello) 0);  printf('Hello message sentn');  // 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(  'nInvalid address/ Address not supported n');  return -1;  }  if ((status  = connect(client_fd (struct sockaddr*)&serv_addr  sizeof(serv_addr)))  < 0) {  printf('nConnection Failed n');  return -1;  }    // subtract 1 for the null  // terminator at the end  send(client_fd hello strlen(hello) 0);  printf('Hello message sentn');  valread = read(client_fd buffer  1024 - 1);   printf('%sn' 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 сокет (сокет за дейтаграма): Осигурява комуникация без връзка по-бърза, но ненадеждна (т.е. 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 хедъра на пакета. (протоколите за повече подробности)
C
sockfd = socket(domain type protocol) 

2. Задайте опция за гнездо

Това помага при манипулирането на опциите за сокета, посочен от файловия дескриптор sockfd. Това е напълно незадължително, но помага при повторно използване на адрес и порт. Предотвратява грешки като: адресът вече се използва.

C
setsockopt(sockfd level optname optval socklen_t optlen); 

3. Завържете

След създаването на сокета функцията bind() свързва сокета с адреса и номера на порта, посочени в addr(персонализирана структура на данните). В примерния код ние свързваме сървъра с localhost, следователно използваме INADDR_ANY, за да посочим IP адреса.

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

Параметри:

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

4. Слушайте

В тази стъпка сървърът използва функцията listen(), която поставя сокета на сървъра в пасивен режим, където чака клиентът да се приближи до сървъра, за да осъществи връзка. Назад дефинира максималната дължина, до която може да нарасне опашката от чакащи връзки за sockfd. Ако пристигне заявка за свързване, когато опашката е пълна, клиентът може да получи грешка с индикация ECONNREFUSED.

C
listen(sockfd backlog); 

Параметри :

  • sockfd : файлов дескриптор на сокет, създаден с помощта на функцията 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. Адресът и портът на сървъра са посочени в addr.

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

Параметри

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

3. Изпращане/Получаване

В тази стъпка клиентът може да изпраща или получава данни от сървъра, което се извършва с помощта на функциите send() и recieve(), подобно на начина, по който сървърът изпраща/получава данни от клиента.

4. Затворете

След като обменът на информация приключи, клиентът също трябва да затвори създадения сокет и да освободи системните ресурси, като използва функцията close() по същия начин, както го прави сървърът.

Често срещани проблеми и техните корекции при програмирането на сокет

  • Неуспешни връзки: За да избегнем неуспешно свързване, трябва да се уверим, че клиентът се опитва да се свърже с правилния IP адрес и порт .
  • Грешки при свързване на портове: Тези грешки възникват, когато порт вече се използва от друго приложение, в този сценарий свързването към този порт ще бъде неуспешно. Опитайте да използвате друг порт или затворете предишното приложение, използвайки порта.
  • Блокиращи сокети: По подразбиране сокетите се блокират. Това означава, че извиквания като accept() или recv() ще чакат за неопределено време, ако няма клиентска връзка или данни. Можете да настроите гнездото в режим без блокиране, ако е необходимо.
Създаване на тест