Unix Socket - Guía rápida

Los sockets permiten la comunicación entre dos procesos diferentes en la misma o diferentes máquinas. Para ser más precisos, es una forma de hablar con otras computadoras usando descriptores de archivos estándar de Unix. En Unix, cada acción de E / S se realiza escribiendo o leyendo un descriptor de archivo. Un descriptor de archivo es solo un número entero asociado con un archivo abierto y puede ser una conexión de red, un archivo de texto, una terminal u otra cosa.

Para un programador, un socket se ve y se comporta de manera muy similar a un descriptor de archivo de bajo nivel. Esto se debe a que los comandos como read () y write () funcionan con sockets de la misma manera que lo hacen con archivos y tuberías.

Los sockets se introdujeron por primera vez en 2.1BSD y posteriormente se refinaron a su forma actual con 4.2BSD. La función de sockets ahora está disponible con la mayoría de las versiones actuales del sistema UNIX.

¿Dónde se usa el zócalo?

Un Unix Socket se utiliza en un marco de aplicación cliente-servidor. Un servidor es un proceso que realiza algunas funciones a pedido de un cliente. La mayoría de los protocolos de nivel de aplicación como FTP, SMTP y POP3 utilizan sockets para establecer una conexión entre el cliente y el servidor y luego para intercambiar datos.

Tipos de enchufe

Hay cuatro tipos de enchufes disponibles para los usuarios. Los dos primeros son los más utilizados y los dos últimos raramente.

Se supone que los procesos se comunican solo entre sockets del mismo tipo, pero no hay ninguna restricción que impida la comunicación entre sockets de diferentes tipos.

  • Stream Sockets- Se garantiza la entrega en un entorno en red. Si envía a través del conector de flujo tres elementos "A, B, C", llegarán en el mismo orden: "A, B, C". Estos sockets utilizan TCP (Protocolo de control de transmisión) para la transmisión de datos. Si la entrega es imposible, el remitente recibe un indicador de error. Los registros de datos no tienen límites.

  • Datagram Sockets- No se garantiza la entrega en un entorno de red. No tienen conexión porque no necesita tener una conexión abierta como en Stream Sockets; crea un paquete con la información de destino y lo envía. Usan UDP (Protocolo de datagramas de usuario).

  • Raw Sockets- Proporcionan a los usuarios acceso a los protocolos de comunicación subyacentes, que admiten abstracciones de socket. Estos sockets están normalmente orientados a datagramas, aunque sus características exactas dependen de la interfaz proporcionada por el protocolo. Los enchufes sin procesar no están destinados al usuario general; se han proporcionado principalmente para aquellos interesados ​​en desarrollar nuevos protocolos de comunicación o para obtener acceso a algunas de las instalaciones más crípticas de un protocolo existente.

  • Sequenced Packet Sockets- Son similares a un socket de transmisión, con la excepción de que se conservan los límites de registro. Esta interfaz se proporciona solo como parte de la abstracción de sockets de Network Systems (NS) y es muy importante en las aplicaciones NS más serias. Los sockets de paquetes secuenciados permiten al usuario manipular los encabezados del Protocolo de paquetes de secuencia (SPP) o del Protocolo de datagramas de Internet (IDP) en un paquete o grupo de paquetes, ya sea escribiendo un encabezado prototipo junto con los datos que se van a enviar, o por especifica un encabezado predeterminado que se utilizará con todos los datos salientes y permite al usuario recibir los encabezados de los paquetes entrantes.

¿Lo que sigue?

Los siguientes capítulos están destinados a fortalecer sus conceptos básicos y preparar una base antes de que pueda escribir programas de servidor y cliente utilizando socket . Si directamente desea ver cómo escribir un programa cliente y servidor, puede hacerlo, pero no se recomienda. Se recomienda encarecidamente que vaya paso a paso y complete estos capítulos iniciales para hacer su base antes de pasar a la programación.

Antes de continuar con el tema real, hablemos un poco sobre las direcciones de red: la dirección IP.

La dirección de host IP, o más comúnmente solo la dirección IP, se utiliza para identificar hosts conectados a Internet. IP significa Protocolo de Internet y se refiere a la capa de Internet de la arquitectura de red general de Internet.

Una dirección IP es una cantidad de 32 bits interpretada como cuatro números u octetos de 8 bits. Cada dirección IP identifica de forma única la red de usuarios participantes, el host en la red y la clase de red de usuarios.

Una dirección IP generalmente se escribe en una notación decimal con puntos de la forma N1.N2.N3.N4, donde cada Ni es un número decimal entre 0 y 255 decimal (00 a FF hexadecimal).

Clases de direcciones

Las direcciones IP son administradas y creadas por la Autoridad de Números Asignados de Internet (IANA). Hay cinco clases de direcciones diferentes. Puede determinar en qué clase se encuentra una dirección IP examinando los primeros cuatro bits de la dirección IP.

  • Class A las direcciones comienzan con 0xxxo 1 to 126 decimal.

  • Class B las direcciones comienzan con 10xxo 128 to 191 decimal.

  • Class C las direcciones comienzan con 110xo 192 to 223 decimal.

  • Class D las direcciones comienzan con 1110o 224 to 239 decimal.

  • Class E las direcciones comienzan con 1111o 240 to 254 decimal.

Direcciones que comienzan con 01111111o 127 decimal, están reservados para loopback y para pruebas internas en una máquina local [Puede probar esto: siempre debe poder hacer ping 127.0.0.1, que apunta a ti mismo]; Las direcciones de clase D están reservadas para multidifusión; Las direcciones de clase E están reservadas para uso futuro. No deben usarse para direcciones de host.

Ejemplo

Class Leftmost bits Start address Finish address
UN 0xxx 0.0.0.0 127.255.255.255
segundo 10xx 128.0.0.0 191.255.255.255
C 110x 192.0.0.0 223.255.255.255
re 1110 224.0.0.0 239.255.255.255
mi 1111 240.0.0.0 255.255.255.255

División en subredes

La división en subredes o la creación de subredes básicamente significa ramificar una red. Se puede hacer por una variedad de razones, como la red en una organización, el uso de diferentes medios físicos (como Ethernet, FDDI, WAN, etc.), la preservación del espacio de direcciones y la seguridad. La razón más común es controlar el tráfico de la red.

La idea básica en la división en subredes es dividir la parte del identificador de host de la dirección IP en dos partes:

  • Una dirección de subred dentro de la propia dirección de red; y
  • Una dirección de host en la subred.

Por ejemplo, un formato de dirección de Clase B común es N1.N2.SH, donde N1.N2 identifica la red de Clase B, el campo S de 8 bits identifica la subred y el campo H de 8 bits identifica el host en la subred.

Los nombres de host en términos de números son difíciles de recordar y, por lo tanto, se denominan con nombres ordinarios como Takshila o Nalanda. Escribimos aplicaciones de software para encontrar la dirección IP punteada correspondiente a un nombre de pila.

El proceso de averiguar la dirección IP con puntos en función del nombre de host alfanumérico dado se conoce como hostname resolution.

La resolución del nombre de host se realiza mediante un software especial que reside en sistemas de alta capacidad. Estos sistemas se denominan Domain Name Systems (DNS), que mantienen el mapeo de direcciones IP y los correspondientes nombres ordinarios.

El archivo / etc / hosts

La correspondencia entre los nombres de host y las direcciones IP se mantiene en un archivo llamado hosts . En la mayoría de los sistemas, este archivo se encuentra en/etc directorio.

Las entradas de este archivo tienen el siguiente aspecto:

# This represents a comments in /etc/hosts file.
127.0.0.1       localhost
192.217.44.207  nalanda metro
153.110.31.18   netserve
153.110.31.19   mainserver centeral
153.110.31.20   samsonite
64.202.167.10   ns3.secureserver.net
64.202.167.97   ns4.secureserver.net
66.249.89.104   www.google.com
68.178.157.132  services.amrood.com

Tenga en cuenta que se puede asociar más de un nombre a una dirección IP determinada. Este archivo se utiliza durante la conversión de la dirección IP al nombre de host y viceversa.

No tendría acceso para editar este archivo, por lo que si desea poner cualquier nombre de host junto con la dirección IP, necesitará tener permiso de root.

La mayoría de las aplicaciones de red utilizan la arquitectura Cliente-Servidor, que se refiere a dos procesos o dos aplicaciones que se comunican entre sí para intercambiar información. Uno de los dos procesos actúa como un proceso cliente y otro proceso actúa como un servidor.

Proceso del cliente

Este es el proceso, que normalmente realiza una solicitud de información. Después de recibir la respuesta, este proceso puede terminar o puede realizar algún otro procesamiento.

Example, El navegador de Internet funciona como una aplicación cliente, que envía una solicitud al servidor web para obtener una página web HTML.

Proceso del servidor

Este es el proceso que toma una solicitud de los clientes. Después de recibir una solicitud del cliente, este proceso realizará el procesamiento requerido, recopilará la información solicitada y la enviará al cliente solicitante. Una vez hecho esto, está listo para servir a otro cliente. Los procesos del servidor siempre están alertas y listos para atender las solicitudes entrantes.

Example - El servidor web sigue esperando las solicitudes de los navegadores de Internet y, tan pronto como recibe alguna solicitud de un navegador, recoge una página HTML solicitada y la envía de vuelta a ese navegador.

Tenga en cuenta que el cliente necesita saber la dirección del servidor, pero el servidor no necesita saber la dirección o incluso la existencia del cliente antes de que se establezca la conexión. Una vez que se establece una conexión, ambas partes pueden enviar y recibir información.

Arquitecturas de 2 y 3 niveles

Hay dos tipos de arquitecturas cliente-servidor:

  • 2-tier architecture- En esta arquitectura, el cliente interactúa directamente con el servidor. Este tipo de arquitectura puede tener algunos agujeros de seguridad y problemas de rendimiento. Internet Explorer y Web Server funcionan en una arquitectura de dos niveles. Aquí los problemas de seguridad se resuelven utilizando Secure Socket Layer (SSL).

  • 3-tier architectures- En esta arquitectura, un software más se encuentra entre el cliente y el servidor. Este software intermedio se llama 'middleware'. El middleware se utiliza para realizar todas las comprobaciones de seguridad y el equilibrio de carga en caso de carga pesada. Un middleware toma todas las solicitudes del cliente y después de realizar la autenticación requerida, pasa esa solicitud al servidor. Luego, el servidor realiza el procesamiento requerido y envía la respuesta de regreso al middleware y finalmente el middleware pasa esta respuesta al cliente. Si desea implementar una arquitectura de 3 niveles, puede mantener cualquier middleware como Web Logic o software WebSphere entre su servidor web y su navegador web.

Tipos de servidor

Hay dos tipos de servidores que puede tener:

  • Iterative Server- Esta es la forma más simple de servidor donde un proceso de servidor atiende a un cliente y después de completar la primera solicitud, toma la solicitud de otro cliente. Mientras tanto, otro cliente sigue esperando.

  • Concurrent Servers- Este tipo de servidor ejecuta varios procesos simultáneos para atender muchas solicitudes a la vez porque un proceso puede tardar más y otro cliente no puede esperar tanto. La forma más sencilla de escribir un servidor concurrente en Unix es bifurcar un proceso hijo para manejar cada cliente por separado.

Cómo hacer cliente

Las llamadas del sistema para establecer una conexión son algo diferentes para el cliente y el servidor, pero ambas involucran la construcción básica de un socket. Ambos procesos establecen sus propios sockets.

Los pasos necesarios para establecer un socket en el lado del cliente son los siguientes:

  • Cree un enchufe con el socket() llamada al sistema.

  • Conecte el enchufe a la dirección del servidor utilizando el connect() llamada al sistema.

  • Envía y recibe datos. Hay varias formas de hacer esto, pero la forma más sencilla es utilizar elread() y write() llamadas al sistema.

Cómo hacer un servidor

Los pasos necesarios para establecer un socket en el lado del servidor son los siguientes:

  • Cree un enchufe con el socket() llamada al sistema.

  • Vincular el socket a una dirección usando el bind()llamada al sistema. Para un socket de servidor en Internet, una dirección consiste en un número de puerto en la máquina host.

  • Escuche las conexiones con el listen() llamada al sistema.

  • Acepta una conexión con el accept()llamada al sistema. Esta llamada normalmente bloquea la conexión hasta que un cliente se conecta con el servidor.

  • Envíe y reciba datos utilizando el read() y write() llamadas al sistema.

Interacción cliente y servidor

A continuación se muestra el diagrama que muestra la interacción completa entre el cliente y el servidor:

Se utilizan varias estructuras en la programación de sockets de Unix para contener información sobre la dirección y el puerto, y otra información. La mayoría de las funciones de socket requieren un puntero a una estructura de dirección de socket como argumento. Las estructuras definidas en este capítulo están relacionadas con la familia de protocolos de Internet.

sockaddr

La primera estructura es sockaddr que contiene la información del socket:

struct sockaddr {
   unsigned short   sa_family;
   char             sa_data[14];
};

Esta es una estructura de dirección de socket genérica, que se pasará en la mayoría de las llamadas a funciones de socket. La siguiente tabla proporciona una descripción de los campos de miembros:

Atributo Valores Descripción
sa_family

AF_INET

AF_UNIX

AF_NS

AF_IMPLINK

Representa una familia de direcciones. En la mayoría de las aplicaciones basadas en Internet, usamos AF_INET.
sa_data Dirección específica del protocolo El contenido de los 14 bytes de la dirección específica del protocolo se interpreta de acuerdo con el tipo de dirección. Para la familia de Internet, usaremos la dirección IP del número de puerto, que está representada por la estructura sockaddr_in definida a continuación.

sockaddr en

La segunda estructura que le ayuda a hacer referencia a los elementos del socket es la siguiente:

struct sockaddr_in {
   short int            sin_family;
   unsigned short int   sin_port;
   struct in_addr       sin_addr;
   unsigned char        sin_zero[8];
};

Aquí está la descripción de los campos de miembros:

Atributo Valores Descripción
sa_family

AF_INET

AF_UNIX

AF_NS

AF_IMPLINK

Representa una familia de direcciones. En la mayoría de las aplicaciones basadas en Internet, usamos AF_INET.
sin_port Puerto de servicio Un número de puerto de 16 bits en orden de bytes de red.
sin_addr Dirección IP Una dirección IP de 32 bits en orden de bytes de red.
sin_zero No utilizado Simplemente establezca este valor en NULL ya que no se está utilizando.

en addr

Esta estructura se usa solo en la estructura anterior como un campo de estructura y contiene netid / hostid de 32 bits.

struct in_addr {
   unsigned long s_addr;
};

Aquí está la descripción de los campos de miembros:

Atributo Valores Descripción
s_addr Puerto de servicio Una dirección IP de 32 bits en orden de bytes de red.

hostent

Esta estructura se utiliza para mantener la información relacionada con el host.

struct hostent {
   char *h_name; 
   char **h_aliases; 
   int h_addrtype;  
   int h_length;    
   char **h_addr_list
	
#define h_addr  h_addr_list[0]
};

Aquí está la descripción de los campos de miembros:

Atributo Valores Descripción
h_name ti.com etc. Es el nombre oficial del anfitrión. Por ejemplo, tutorialspoint.com, google.com, etc.
h_aliases TI Contiene una lista de alias de nombres de host.
h_addrtype AF_INET Contiene la familia de direcciones y, en el caso de una aplicación basada en Internet, siempre será AF_INET.
h_length 4 Tiene la longitud de la dirección IP, que es 4 para la dirección de Internet.
h_addr_list in_addr Para direcciones de Internet, la matriz de punteros h_addr_list [0], h_addr_list [1], y así sucesivamente, son puntos para estructurar in_addr.

NOTE - h_addr se define como h_addr_list [0] para mantener la compatibilidad con versiones anteriores.

sirviente

Esta estructura particular se utiliza para mantener la información relacionada con el servicio y los puertos asociados.

struct servent {
   char  *s_name; 
   char  **s_aliases; 
   int   s_port;  
   char  *s_proto;
};

Aquí está la descripción de los campos de miembros:

Atributo Valores Descripción
nombre de http Este es el nombre oficial del servicio. Por ejemplo, SMTP, FTP POP3, etc.
s_aliases ALIAS Contiene la lista de alias de servicio. La mayoría de las veces, se establecerá en NULL.
deporte 80 Tendrá número de puerto asociado. Por ejemplo, para HTTP, será 80.
s_proto

TCP

UDP

Está configurado según el protocolo utilizado. Los servicios de Internet se proporcionan mediante TCP o UDP.

Consejos sobre estructuras de enchufes

Las estructuras de direcciones de socket son una parte integral de cada programa de red. Los asignamos, los completamos y les pasamos punteros a varias funciones de socket. A veces pasamos un puntero a una de estas estructuras a una función de socket y completa el contenido.

Siempre pasamos estas estructuras por referencia (es decir, pasamos un puntero a la estructura, no a la estructura en sí), y siempre pasamos el tamaño de la estructura como otro argumento.

Cuando una función de socket completa una estructura, la longitud también se pasa por referencia, de modo que la función pueda actualizar su valor. A estos los llamamos argumentos valor-resultado.

Siempre, establezca las variables de estructura en NULL (es decir, '\ 0') usando memset () para las funciones bzero (), de lo contrario, puede obtener valores basura inesperados en su estructura.

Cuando un proceso de cliente desea conectar un servidor, el cliente debe tener una forma de identificar el servidor al que desea conectarse. Si el cliente conoce la dirección de Internet de 32 bits del host en el que reside el servidor, puede comunicarse con ese host. Pero, ¿cómo identifica el cliente el proceso de servidor particular que se ejecuta en ese host?

Para resolver el problema de identificar un proceso de servidor en particular que se ejecuta en un host, tanto TCP como UDP han definido un grupo de puertos conocidos.

Para nuestro propósito, un puerto se definirá como un número entero entre 1024 y 65535. Esto se debe a que todos los números de puerto menores a 1024 se consideran conocidos ; por ejemplo, telnet usa el puerto 23, http usa 80, ftp usa 21, y así.

Las asignaciones de puertos a los servicios de red se pueden encontrar en el archivo / etc / services. Si está escribiendo su propio servidor, debe tener cuidado de asignar un puerto a su servidor. Debe asegurarse de que este puerto no esté asignado a ningún otro servidor.

Normalmente, es una práctica asignar cualquier número de puerto superior a 5000. Pero hay muchas organizaciones que tienen servidores escritos con números de puerto superiores a 5000. Por ejemplo, Yahoo Messenger se ejecuta en 5050, SIP Server se ejecuta en 5060, etc.

Ejemplos de puertos y servicios

Aquí hay una pequeña lista de servicios y puertos asociados. Puede encontrar la lista más actualizada de puertos de Internet y servicios asociados en IANA - Asignaciones de puertos TCP / IP .

Service Port Number Service Description
eco 7 UDP / TCP devuelve lo que recibe.
descarte 9 UDP / TCP desecha la entrada.
tiempo de día 13 UDP / TCP devuelve la hora ASCII.
chargen 19 UDP / TCP devuelve caracteres.
ftp 21 Transferencia de archivos TCP.
telnet 23 Inicio de sesión remoto TCP.
smtp 25 Correo electrónico TCP.
tiempo de día 37 UDP / TCP devuelve tiempo binario.
tftp 69 Transferencia de archivos triviales UDP.
dedo 79 Información de TCP sobre los usuarios.
http 80 TCP World Wide Web.
iniciar sesión 513 Inicio de sesión remoto TCP.
OMS 513 UDP diferente información sobre los usuarios.
Xserver 6000 Ventanas TCP X (NB> 1023).

Funciones de puerto y servicio

Unix proporciona las siguientes funciones para obtener el nombre del servicio del archivo / etc / services.

  • struct servent *getservbyname(char *name, char *proto) - Esta llamada toma el nombre del servicio y el nombre del protocolo, y devuelve el número de puerto correspondiente para ese servicio.

  • struct servent *getservbyport(int port, char *proto) - Esta llamada toma el número de puerto y el nombre del protocolo y devuelve el nombre del servicio correspondiente.

El valor de retorno de cada función es un puntero a una estructura con la siguiente forma:

struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
};

Aquí está la descripción de los campos de miembros:

Atributo Valores Descripción
nombre de http Es el nombre oficial del servicio. Por ejemplo, SMTP, FTP POP3, etc.
s_aliases ALIAS Contiene la lista de alias de servicio. La mayoría de las veces, se establecerá en NULL.
deporte 80 Tendrá el número de puerto asociado. Por ejemplo, para HTTP, será 80.
s_proto

TCP

UDP

Está configurado según el protocolo utilizado. Los servicios de Internet se proporcionan mediante TCP o UDP.

Desafortunadamente, no todas las computadoras almacenan los bytes que componen un valor multibyte en el mismo orden. Considere una Internet de 16 bits que se compone de 2 bytes. Hay dos formas de almacenar este valor.

  • Little Endian - En este esquema, el byte de orden inferior se almacena en la dirección inicial (A) y el byte de orden superior se almacena en la siguiente dirección (A + 1).

  • Big Endian - En este esquema, el byte de orden superior se almacena en la dirección inicial (A) y el byte de orden inferior se almacena en la siguiente dirección (A + 1).

Para permitir que las máquinas con diferentes convenciones de orden de bytes se comuniquen entre sí, los protocolos de Internet especifican una convención canónica de orden de bytes para los datos transmitidos a través de la red. Esto se conoce como orden de bytes de red.

Al establecer una conexión a Internet, debe asegurarse de que los datos de los miembros sin_port y sin_addr de la estructura sockaddr_in estén representados en el orden de bytes de la red.

Funciones de ordenación de bytes

Las rutinas para convertir datos entre la representación interna de un host y el orden de bytes de red son las siguientes:

Función Descripción
htons () Host a red corto
htonl () Host a red de largo
ntohl () Red para alojar mucho
ntohs () Red para acoger corto

A continuación se enumeran algunos detalles más sobre estas funciones:

  • unsigned short htons(unsigned short hostshort) - Esta función convierte cantidades de 16 bits (2 bytes) del orden de bytes del host al orden de bytes de la red.

  • unsigned long htonl(unsigned long hostlong) - Esta función convierte cantidades de 32 bits (4 bytes) del orden de bytes del host al orden de bytes de la red.

  • unsigned short ntohs(unsigned short netshort) - Esta función convierte cantidades de 16 bits (2 bytes) del orden de bytes de la red al orden de bytes del host.

  • unsigned long ntohl(unsigned long netlong) - Esta función convierte cantidades de 32 bits del orden de bytes de la red al orden de bytes del host.

Estas funciones son macros y dan como resultado la inserción de código fuente de conversión en el programa de llamada. En las máquinas little-endian, el código cambiará los valores al orden de bytes de la red. En las máquinas big-endian, no se inserta ningún código ya que no es necesario; las funciones se definen como nulas.

Programa para determinar el orden de bytes del host

Mantenga el siguiente código en un archivo byteorder.cy luego compílelo y ejecútelo en su máquina.

En este ejemplo, almacenamos el valor de dos bytes 0x0102 en el entero corto y luego miramos los dos bytes consecutivos, c [0] (la dirección A) yc [1] (la dirección A + 1) para determinar el byte orden.

#include <stdio.h>

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

   union {
      short s;
      char c[sizeof(short)];
   }un;
	
   un.s = 0x0102;
   
   if (sizeof(short) == 2) {
      if (un.c[0] == 1 && un.c[1] == 2)
         printf("big-endian\n");
      
      else if (un.c[0] == 2 && un.c[1] == 1)
         printf("little-endian\n");
      
      else
         printf("unknown\n");
   }
   else {
      printf("sizeof(short) = %d\n", sizeof(short));
   }
	
   exit(0);
}

Una salida generada por este programa en una máquina Pentium es la siguiente:

$> gcc byteorder.c $> ./a.out
little-endian
$>

Unix proporciona varias llamadas a funciones para ayudarlo a manipular direcciones IP. Estas funciones convierten direcciones de Internet entre cadenas ASCII (lo que los humanos prefieren usar) y valores binarios ordenados por bytes de red (valores que se almacenan en estructuras de direcciones de socket).

Las siguientes tres llamadas de función se utilizan para el direccionamiento IPv4:

  • int inet_aton (const char * strptr, struct in_addr * addrptr)
  • in_addr_t inet_addr (const char * strptr)
  • char * inet_ntoa (estructura in_addr inaddr)

int inet_aton (const char * strptr, struct in_addr * addrptr)

Esta llamada de función convierte la cadena especificada en la notación de puntos estándar de Internet en una dirección de red y almacena la dirección en la estructura proporcionada. La dirección convertida estará en orden de bytes de red (bytes ordenados de izquierda a derecha). Devuelve 1 si la cadena era válida y 0 en caso de error.

A continuación se muestra el ejemplo de uso:

#include <arpa/inet.h>

(...)

   int retval;
   struct in_addr addrptr
   
   memset(&addrptr, '\0', sizeof(addrptr));
   retval = inet_aton("68.178.157.132", &addrptr);

(...)

in_addr_t inet_addr (const char * strptr)

Esta llamada de función convierte la cadena especificada en la notación de puntos estándar de Internet en un valor entero adecuado para su uso como dirección de Internet. La dirección convertida estará en orden de bytes de red (bytes ordenados de izquierda a derecha). Devuelve una dirección IPv4 ordenada por bytes de red binaria de 32 bits e INADDR_NONE en caso de error.

A continuación se muestra el ejemplo de uso:

#include <arpa/inet.h>

(...)

   struct sockaddr_in dest;

   memset(&dest, '\0', sizeof(dest));
   dest.sin_addr.s_addr = inet_addr("68.178.157.132");
   
(...)

char * inet_ntoa (estructura in_addr inaddr)

Esta llamada de función convierte la dirección de host de Internet especificada en una cadena en la notación de puntos estándar de Internet.

A continuación se muestra el ejemplo de uso:

#include <arpa/inet.h>

(...)

   char *ip;
   
   ip = inet_ntoa(dest.sin_addr);
   
   printf("IP Address is: %s\n",ip);
   
(...)

Este capítulo describe las funciones básicas del socket necesarias para escribir un cliente y un servidor TCP completos.

El siguiente diagrama muestra la interacción completa entre el cliente y el servidor:

La función del enchufe

Para realizar E / S de red, lo primero que debe hacer un proceso es llamar a la función socket, especificando el tipo de protocolo de comunicación deseado y familia de protocolos, etc.

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

int socket (int family, int type, int protocol);

Esta llamada devuelve un descriptor de socket que puede usar en llamadas posteriores al sistema o -1 en caso de error.

Parámetros

family - Especifica la familia de protocolos y es una de las constantes que se muestran a continuación -

Familia Descripción
AF_INET Protocolos IPv4
AF_INET6 Protocolos IPv6
AF_LOCAL Protocolos de dominio Unix
AF_ROUTE Enrutamiento de enchufes
AF_KEY Toma de Ket

Este capítulo no cubre otros protocolos excepto IPv4.

type- Especifica el tipo de enchufe que desea. Puede tomar uno de los siguientes valores:

Tipo Descripción
SOCK_STREAM Toma de corriente
SOCK_DGRAM Toma de datagrama
SOCK_SEQPACKET Socket de paquete secuenciado
SOCK_RAW Toma sin procesar

protocol - El argumento debe establecerse en el tipo de protocolo específico que se indica a continuación, o 0 para seleccionar el valor predeterminado del sistema para la combinación dada de familia y tipo -

Protocolo Descripción
IPPROTO_TCP Protocolo de transporte TCP
IPPROTO_UDP Protocolo de transporte UDP
IPPROTO_SCTP Protocolo de transporte SCTP

La función de conexión

La función de conexión es utilizada por un cliente TCP para establecer una conexión con un servidor TCP.

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

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

Esta llamada devuelve 0 si se conecta correctamente al servidor; de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • serv_addr - Es un puntero a struct sockaddr que contiene la dirección IP y el puerto de destino.

  • addrlen - Configúrelo en sizeof (struct sockaddr).

La función de enlace

El bind función asigna una dirección de protocolo local a un socket. Con los protocolos de Internet, la dirección del protocolo es la combinación de una dirección IPv4 de 32 bits o una dirección IPv6 de 128 bits, junto con un número de puerto TCP o UDP de 16 bits. Esta función solo la llama el servidor TCP.

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

int bind(int sockfd, struct sockaddr *my_addr,int addrlen);

Esta llamada devuelve 0 si se une con éxito a la dirección; de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • my_addr - Es un puntero a struct sockaddr que contiene la dirección IP local y el puerto.

  • addrlen - Configúrelo en sizeof (struct sockaddr).

Puedes poner tu dirección IP y tu puerto automáticamente

Un valor 0 para el número de puerto significa que el sistema elegirá un puerto aleatorio, y el valor INADDR_ANY para la dirección IP significa que la dirección IP del servidor se asignará automáticamente.

server.sin_port = 0;  		     
server.sin_addr.s_addr = INADDR_ANY;

NOTE- Todos los puertos por debajo de 1024 están reservados. Puede configurar un puerto por encima de 1024 y por debajo de 65535 a menos que sean los que estén siendo utilizados por otros programas.

La función de escuchar

La función de escucha es llamada solo por un servidor TCP y realiza dos acciones:

  • La función de escucha convierte un socket no conectado en un socket pasivo, lo que indica que el kernel debe aceptar solicitudes de conexión entrantes dirigidas a este socket.

  • El segundo argumento de esta función especifica el número máximo de conexiones que el núcleo debe poner en cola para este socket.

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

int listen(int sockfd,int backlog);

Esta llamada devuelve 0 en caso de éxito, de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • backlog - Es el número de conexiones permitidas.

La función de aceptar

La función de aceptación es llamada por un servidor TCP para devolver la siguiente conexión completada desde el frente de la cola de conexiones completadas. La firma de la llamada es la siguiente:

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

int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

Esta llamada devuelve un descriptor no negativo en caso de éxito; de lo contrario, devuelve -1 en caso de error. Se supone que el descriptor devuelto es un descriptor de socket de cliente y todas las operaciones de lectura y escritura se realizarán en este descriptor para comunicarse con el cliente.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • cliaddr - Es un puntero a struct sockaddr que contiene la dirección IP y el puerto del cliente.

  • addrlen - Configúrelo en sizeof (struct sockaddr).

La función de envío

La función de envío se utiliza para enviar datos a través de conectores de flujo o conectores de datagramas CONECTADOS. Si desea enviar datos a través de sockets de datagramas NO CONECTADOS, debe usar la función sendto ().

Puede usar la llamada al sistema write () para enviar datos. Su firma es la siguiente:

int send(int sockfd, const void *msg, int len, int flags);

Esta llamada devuelve el número de bytes enviados, de lo contrario, devolverá -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • msg - Es un puntero a los datos que desea enviar.

  • len - Es la longitud de los datos que desea enviar (en bytes).

  • flags - Está configurado en 0.

La función recv

La función recv se utiliza para recibir datos a través de sockets de flujo o sockets de datagramas CONECTADOS. Si desea recibir datos a través de sockets de datagramas NO CONECTADOS, debe usar recvfrom ().

Puede usar la llamada al sistema read () para leer los datos. Esta llamada se explica en el capítulo de funciones auxiliares.

int recv(int sockfd, void *buf, int len, unsigned int flags);

Esta llamada devuelve el número de bytes leídos en el búfer; de lo contrario, devolverá -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • buf - Es el búfer para leer la información.

  • len - Es la longitud máxima del búfer.

  • flags - Está configurado en 0.

La función sendto

La función sendto se utiliza para enviar datos a través de sockets de datagramas NO CONECTADOS. Su firma es la siguiente:

int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);

Esta llamada devuelve el número de bytes enviados; de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • msg - Es un puntero a los datos que desea enviar.

  • len - Es la longitud de los datos que desea enviar (en bytes).

  • flags - Está configurado en 0.

  • to - Es un puntero a struct sockaddr para el host al que se deben enviar los datos.

  • tolen - Se establece en sizeof (struct sockaddr).

La función recvfrom

La función recvfrom se utiliza para recibir datos de sockets de datagramas NO CONECTADOS.

int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);

Esta llamada devuelve el número de bytes leídos en el búfer; de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • buf - Es el búfer para leer la información.

  • len - Es la longitud máxima del búfer.

  • flags - Está configurado en 0.

  • from - Es un puntero a struct sockaddr para el host donde se deben leer los datos.

  • fromlen - Se establece en sizeof (struct sockaddr).

La función de cierre

La función de cierre se utiliza para cerrar la comunicación entre el cliente y el servidor. Su sintaxis es la siguiente:

int close( int sockfd );

Esta llamada devuelve 0 en caso de éxito, de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

La función de apagado

La función de apagado se utiliza para cerrar de manera elegante la comunicación entre el cliente y el servidor. Esta función ofrece más control en comparación con la función de cierre . A continuación se muestra la sintaxis de apagado :

int shutdown(int sockfd, int how);

Esta llamada devuelve 0 en caso de éxito, de lo contrario, devuelve -1 en caso de error.

Parámetros

  • sockfd - Es un descriptor de socket devuelto por la función socket.

  • how - Pon uno de los números -

    • 0 - indica que la recepción no está permitida,

    • 1 - indica que el envío no está permitido y

    • 2- indica que tanto el envío como la recepción no están permitidos. Cuando cómo se establece en 2, es lo mismo que close ().

La función de selección

La función de selección indica cuál de los descriptores de archivo especificados está listo para leer, para escribir o tiene una condición de error pendiente.

Cuando una aplicación llama a recv o recvfrom , se bloquea hasta que llegan datos para ese socket. Una aplicación podría estar realizando otro procesamiento útil mientras el flujo de datos entrante está vacío. Otra situación es cuando una aplicación recibe datos de varios sockets.

Llamar a recv o recvfrom en un socket que no tiene datos en su cola de entrada evita la recepción inmediata de datos de otros sockets. La llamada a la función select resuelve este problema al permitir que el programa sondee todos los identificadores de socket para ver si están disponibles para operaciones de lectura y escritura sin bloqueo.

A continuación se muestra la sintaxis de select -

int select(int  nfds, fd_set  *readfds, fd_set  *writefds, fd_set *errorfds, struct timeval *timeout);

Esta llamada devuelve 0 en caso de éxito, de lo contrario, devuelve -1 en caso de error.

Parámetros

  • nfds- Especifica el rango de descriptores de archivo que se probarán. La función select () prueba los descriptores de archivo en el rango de 0 a nfds-1

  • readfds- Apunta a un objeto de tipo fd_set que, en la entrada, especifica los descriptores de archivo que se verificarán para estar listos para leer, y en la salida, indica qué descriptores de archivo están listos para leer. Puede ser NULL para indicar un conjunto vacío.

  • writefds- Apunta a un objeto de tipo fd_set que, en la entrada, especifica los descriptores de archivo que se deben verificar para verificar que estén listos para escribir, y en la salida, indica qué descriptores de archivos están listos para escribir. Puede ser NULL para indicar un conjunto vacío.

  • exceptfds- Apunta a un objeto de tipo fd_set que, en la entrada, especifica los descriptores de archivo que se verificarán para ver si hay condiciones de error pendientes, y en la salida indica qué descriptores de archivo tienen condiciones de error pendientes. Puede ser NULL para indicar un conjunto vacío.

  • timeout- Apunta a una estructura timeval que especifica cuánto tiempo la llamada de selección debe sondear los descriptores para una operación de E / S disponible. Si el valor del tiempo de espera es 0, la selección volverá inmediatamente. Si el argumento de tiempo de espera es NULL, seleccionar bloqueará hasta que al menos un identificador de archivo / socket esté listo para una operación de E / S disponible. De lo contrario, select regresará después de que haya transcurrido el tiempo de espera O cuando al menos un descriptor de archivo / conector esté listo para una operación de E / S.

El valor de retorno de select es el número de identificadores especificados en los conjuntos de descriptores de archivos que están listos para E / S. Si se alcanza el límite de tiempo especificado por el campo de tiempo de espera, seleccione return 0. Existen las siguientes macros para manipular un conjunto de descriptores de archivo:

  • FD_CLR(fd, &fdset)- Borra el bit del descriptor de archivo fd en el conjunto de descriptores de archivo fdset.

  • FD_ISSET(fd, &fdset)- Devuelve un valor distinto de cero si el bit para el descriptor de archivo fd está establecido en el conjunto de descriptores de archivo al que apunta fdset , y 0 en caso contrario.

  • FD_SET(fd, &fdset) - Establece el bit para el descriptor de archivo fd en el conjunto de descriptores de archivo fdset.

  • FD_ZERO(&fdset) - Inicializa el conjunto de descriptores de archivo fdset para que tenga cero bits para todos los descriptores de archivos.

El comportamiento de estas macros no está definido si el argumento fd es menor que 0 o mayor o igual que FD_SETSIZE.

Ejemplo

fd_set fds;

struct timeval tv;

/* do socket initialization etc.
tv.tv_sec = 1;
tv.tv_usec = 500000;

/* tv now represents 1.5 seconds */
FD_ZERO(&fds);

/* adds sock to the file descriptor set */
FD_SET(sock, &fds); 

/* wait 1.5 seconds for any data to be read from any single socket */
select(sock+1, &fds, NULL, NULL, &tv);

if (FD_ISSET(sock, &fds)) {
   recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
   /* do something */
}
else {
   /* do something else */
}

Este capítulo describe todas las funciones auxiliares que se utilizan durante la programación de conectores. Otras funciones de ayuda se describen en los capítulos:Ports and Servicesy Red Byte Orders.

La función de escritura

La función de escritura intenta escribir nbyte bytes desde el búfer apuntado por buf al archivo asociado con el descriptor de archivo abierto, fildes .

También puede usar la función send () para enviar datos a otro proceso.

#include <unistd.h>

int write(int fildes, const void *buf, int nbyte);

Una vez completado con éxito, write () devuelve el número de bytes realmente escritos en el archivo asociado con los archivos. Este número nunca es mayor que nbyte. De lo contrario, se devuelve -1.

Parámetros

  • fildes - Es un descriptor de socket devuelto por la función socket.

  • buf - Es un puntero a los datos que desea enviar.

  • nbyte- Es el número de bytes a escribir. Si nbyte es 0, write () devolverá 0 y no tendrá otros resultados si el archivo es un archivo normal; de lo contrario, los resultados no se especifican.

La función de lectura

La función de lectura intenta leer nbyte bytes del archivo asociado con el búfer, fildes, en el búfer al que apunta buf.

También puede usar la función recv () para leer datos en otro proceso.

#include <unistd.h>

int read(int fildes, const void *buf, int nbyte);

Una vez completado con éxito, write () devuelve el número de bytes realmente escritos en el archivo asociado con los archivos. Este número nunca es mayor que nbyte. De lo contrario, se devuelve -1.

Parámetros

  • fildes - Es un descriptor de socket devuelto por la función socket.

  • buf - Es el búfer para leer la información.

  • nbyte - Es el número de bytes a leer.

La función de la bifurcación

La función de bifurcación crea un nuevo proceso. El nuevo proceso llamado proceso hijo será una copia exacta del proceso de llamada (proceso padre). El proceso hijo hereda muchos atributos del proceso padre.

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

int fork(void);

Una vez completado con éxito, fork () devuelve 0 al proceso hijo y el ID de proceso del proceso hijo al proceso padre. De lo contrario, se devuelve -1 al proceso padre, no se crea ningún proceso hijo y se establece errno para indicar el error.

Parámetros

  • void - Significa que no se requiere ningún parámetro.

La función bzero

La función bzero coloca nbyte bytes nulos en la cadena s . Esta función se utiliza para configurar todas las estructuras de conectores con valores nulos.

void bzero(void *s, int nbyte);

Esta función no devuelve nada.

Parámetros

  • s- Especifica la cadena que debe llenarse con bytes nulos. Esta será una variable de estructura de punto a socket.

  • nbyte- Especifica el número de bytes a llenar con valores nulos. Este será el tamaño de la estructura del zócalo.

La función bcmp

La función bcmp compara la cadena de bytes s1 con la cadena de bytes s2. Se supone que ambas cadenas tienen una longitud de nbyte bytes.

int bcmp(const void *s1, const void *s2, int nbyte);

Esta función devuelve 0 si ambas cadenas son idénticas, 1 en caso contrario. La función bcmp () siempre devuelve 0 cuando nbyte es 0.

Parámetros

  • s1 - Especifica la primera cadena a comparar.

  • s2 - Especifica la segunda cadena a comparar.

  • nbyte - Especifica el número de bytes a comparar.

La función bcopy

La función bcopy copia nbyte bytes de la cadena s1 a la cadena s2. Las cadenas superpuestas se manejan correctamente.

void bcopy(const void *s1, void *s2, int nbyte);

Esta función no devuelve nada.

Parámetros

  • s1 - Especifica la cadena de origen.

  • s2v - Especifica la cadena de destino.

  • nbyte - Especifica el número de bytes a copiar.

La función memset

La función memset también se usa para establecer variables de estructura de la misma manera quebzero. Eche un vistazo a su sintaxis, que se muestra a continuación.

void *memset(void *s, int c, int nbyte);

Esta función devuelve un puntero a void; de hecho, un puntero a la memoria configurada y debe convertirlo en consecuencia.

Parámetros

  • s - Especifica la fuente a configurar.

  • c - Especifica el carácter a establecer en lugares de nbyte.

  • nbyte - Especifica el número de bytes a configurar.

Para convertir un proceso en un servidor TCP, debe seguir los pasos que se indican a continuación:

  • Cree un socket con la llamada al sistema socket () .

  • Vincula el conector a una dirección mediante la llamada al sistema bind () . Para un socket de servidor en Internet, una dirección consiste en un número de puerto en la máquina host.

  • Escuche las conexiones con la llamada del sistema listen () .

  • Acepte una conexión con la llamada al sistema accept () . Esta llamada normalmente se bloquea hasta que un cliente se conecta con el servidor.

  • Envíe y reciba datos mediante las llamadas al sistema read () y write () .

Ahora pongamos estos pasos en forma de código fuente. Coloque este código en el archivo server.cy compílelo con el compilador gcc .

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

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int  n;
   
   /* First call to socket() function */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
   
   /* Initialize socket structure */
   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = 5001;
   
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY;
   serv_addr.sin_port = htons(portno);
   
   /* Now bind the host address using bind() call.*/
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR on binding");
      exit(1);
   }
      
   /* Now start listening for the clients, here process will
      * go in sleep mode and will wait for the incoming connection
   */
   
   listen(sockfd,5);
   clilen = sizeof(cli_addr);
   
   /* Accept actual connection from the client */
   newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
	
   if (newsockfd < 0) {
      perror("ERROR on accept");
      exit(1);
   }
   
   /* If connection is established then start communicating */
   bzero(buffer,256);
   n = read( newsockfd,buffer,255 );
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
   
   printf("Here is the message: %s\n",buffer);
   
   /* Write a response to the client */
   n = write(newsockfd,"I got your message",18);
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
      
   return 0;
}

Manejar múltiples conexiones

Para permitir que el servidor maneje múltiples conexiones simultáneas, realizamos los siguientes cambios en el código anterior:

  • Coloque la declaración de aceptación y el siguiente código en un bucle infinito.

  • Una vez establecida la conexión, llame a fork () para crear un nuevo proceso.

  • El proceso hijo cerrará sockfd y llamará a la función doprocessing , pasando el nuevo descriptor de archivo de socket como argumento. Cuando los dos procesos han completado su conversación, como lo indica el retorno de doprocessing () , este proceso simplemente finaliza.

  • El proceso padre cierra newsockfd . Como todo este código está en un bucle infinito, volverá a la declaración de aceptación para esperar la próxima conexión.

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

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

void doprocessing (int sock);

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int n, pid;
   
   /* First call to socket() function */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
   
   /* Initialize socket structure */
   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = 5001;
   
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY;
   serv_addr.sin_port = htons(portno);
   
   /* Now bind the host address using bind() call.*/
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR on binding");
      exit(1);
   }
   
   /* Now start listening for the clients, here
      * process will go in sleep mode and will wait
      * for the incoming connection
   */
   
   listen(sockfd,5);
   clilen = sizeof(cli_addr);
   
   while (1) {
      newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
		
      if (newsockfd < 0) {
         perror("ERROR on accept");
         exit(1);
      }
      
      /* Create child process */
      pid = fork();
		
      if (pid < 0) {
         perror("ERROR on fork");
         exit(1);
      }
      
      if (pid == 0) {
         /* This is the client process */
         close(sockfd);
         doprocessing(newsockfd);
         exit(0);
      }
      else {
         close(newsockfd);
      }
		
   } /* end of while */
}

El siguiente segmento de código muestra una implementación simple de la función doprocessing .

void doprocessing (int sock) {
   int n;
   char buffer[256];
   bzero(buffer,256);
   n = read(sock,buffer,255);
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
   
   printf("Here is the message: %s\n",buffer);
   n = write(sock,"I got your message",18);
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
	
}

Para convertir un proceso en un cliente TCP, debe seguir los pasos que se indican a continuación & minus;

  • Cree un socket con la llamada al sistema socket () .

  • Conecte el enchufe a la dirección del servidor mediante la llamada al sistema connect () .

  • Envía y recibe datos. Hay varias formas de hacer esto, pero la forma más sencilla es usar las llamadas al sistema read () y write () .

Ahora pongamos estos pasos en forma de código fuente. Pon este código en el archivoclient.c y compilarlo con gcc compilador.

Ejecute este programa y pase el nombre de host y el número de puerto del servidor, para conectarse al servidor, que ya debe haber ejecutado en otra ventana de Unix.

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

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

int main(int argc, char *argv[]) {
   int sockfd, portno, n;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   
   char buffer[256];
   
   if (argc < 3) {
      fprintf(stderr,"usage %s hostname port\n", argv[0]);
      exit(0);
   }
	
   portno = atoi(argv[2]);
   
   /* Create a socket point */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
	
   server = gethostbyname(argv[1]);
   
   if (server == NULL) {
      fprintf(stderr,"ERROR, no such host\n");
      exit(0);
   }
   
   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
   serv_addr.sin_port = htons(portno);
   
   /* Now connect to the server */
   if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR connecting");
      exit(1);
   }
   
   /* Now ask for a message from the user, this message
      * will be read by server
   */
	
   printf("Please enter the message: ");
   bzero(buffer,256);
   fgets(buffer,255,stdin);
   
   /* Send message to the server */
   n = write(sockfd, buffer, strlen(buffer));
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
   
   /* Now read server response */
   bzero(buffer,256);
   n = read(sockfd, buffer, 255);
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
	
   printf("%s\n",buffer);
   return 0;
}

A continuación se muestra una lista de todas las funciones relacionadas con la programación de conectores.

Funciones de puerto y servicio

Unix proporciona las siguientes funciones para obtener el nombre del servicio del archivo / etc / services.

  • struct servent *getservbyname(char *name, char *proto) - Esta llamada toma un nombre de servicio y un nombre de protocolo y devuelve el número de puerto correspondiente para ese servicio.

  • struct servent *getservbyport(int port, char *proto) - Esta llamada toma un número de puerto y un nombre de protocolo y devuelve el nombre de servicio correspondiente.

Funciones de ordenación de bytes

  • unsigned short htons (unsigned short hostshort) - Esta función convierte cantidades de 16 bits (2 bytes) del orden de bytes del host al orden de bytes de la red.

  • unsigned long htonl (unsigned long hostlong) - Esta función convierte cantidades de 32 bits (4 bytes) del orden de bytes del host al orden de bytes de la red.

  • unsigned short ntohs (unsigned short netshort) - Esta función convierte cantidades de 16 bits (2 bytes) del orden de bytes de la red al orden de bytes del host.

  • unsigned long ntohl (unsigned long netlong) - Esta función convierte cantidades de 32 bits del orden de bytes de la red al orden de bytes del host.

Funciones de dirección IP

  • int inet_aton (const char *strptr, struct in_addr *addrptr)- Esta llamada de función convierte la cadena especificada, en la notación de puntos estándar de Internet, en una dirección de red y almacena la dirección en la estructura proporcionada. La dirección convertida estará en orden de bytes de red (bytes ordenados de izquierda a derecha). Devuelve 1 si la cadena es válida y 0 en caso de error.

  • in_addr_t inet_addr (const char *strptr)- Esta llamada de función convierte la cadena especificada, en la notación de puntos estándar de Internet, en un valor entero adecuado para su uso como dirección de Internet. La dirección convertida estará en orden de bytes de red (bytes ordenados de izquierda a derecha). Devuelve una dirección IPv4 ordenada por bytes de red binaria de 32 bits e INADDR_NONE en caso de error.

  • char *inet_ntoa (struct in_addr inaddr) - Esta llamada de función convierte la dirección de host de Internet especificada en una cadena en la notación de puntos estándar de Internet.

Funciones principales del socket

  • int socket (int family, int type, int protocol) - Esta llamada devuelve un descriptor de socket que puede usar en llamadas posteriores al sistema o le da -1 en caso de error.

  • int connect (int sockfd, struct sockaddr *serv_addr, int addrlen)- Un cliente TCP utiliza la función de conexión para establecer una conexión con un servidor TCP. Esta llamada devuelve 0 si se conecta correctamente al servidor; de lo contrario, devuelve -1.

  • int bind(int sockfd, struct sockaddr *my_addr,int addrlen)- La función de vinculación asigna una dirección de protocolo local a un conector. Esta llamada devuelve 0 si se une correctamente a la dirección; de lo contrario, devuelve -1.

  • int listen(int sockfd, int backlog)- La función de escucha es llamada solo por un servidor TCP para escuchar la solicitud del cliente. Esta llamada devuelve 0 en caso de éxito; de lo contrario, devuelve -1.

  • int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)- La función de aceptación es llamada por un servidor TCP para aceptar solicitudes de clientes y establecer una conexión real. Esta llamada devuelve un descriptor no negativo en caso de éxito; de lo contrario, devuelve -1.

  • int send(int sockfd, const void *msg, int len, int flags)- La función de envío se utiliza para enviar datos a través de sockets de flujo o sockets de datagramas CONECTADOS. Esta llamada devuelve el número de bytes enviados; de lo contrario, devuelve -1.

  • int recv (int sockfd, void *buf, int len, unsigned int flags)- La función recv se utiliza para recibir datos a través de sockets de flujo o sockets de datagramas CONECTADOS. Esta llamada devuelve el número de bytes leídos en el búfer; de lo contrario, devuelve -1 en caso de error.

  • int sendto (int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen)- La función sendto se utiliza para enviar datos a través de conectores de datagramas NO CONECTADOS. Esta llamada devuelve el número de bytes enviados; de lo contrario, devuelve -1 en caso de error.

  • int recvfrom (int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen)- La función recvfrom se utiliza para recibir datos de sockets de datagramas NO CONECTADOS. Esta llamada devuelve el número de bytes leídos en el búfer; de lo contrario, devuelve -1 en caso de error.

  • int close (int sockfd)- La función de cierre se utiliza para cerrar una comunicación entre el cliente y el servidor. Esta llamada devuelve 0 en caso de éxito; de lo contrario, devuelve -1.

  • int shutdown (int sockfd, int how)- La función de apagado se utiliza para cerrar con gracia una comunicación entre el cliente y el servidor. Esta función ofrece más control en comparación con la función de cierre. Devuelve 0 en caso de éxito, -1 en caso contrario.

  • int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) - Esta función se utiliza para leer o escribir varios sockets.

Funciones auxiliares de socket

  • int write (int fildes, const void *buf, int nbyte)- La función de escritura intenta escribir nbyte bytes desde el búfer al que apunta buf en el archivo asociado con el descriptor de archivo abierto, fildes. Una vez completado con éxito, write () devuelve el número de bytes realmente escritos en el archivo asociado con los archivos. Este número nunca es mayor que nbyte. De lo contrario, se devuelve -1.

  • int read (int fildes, const void *buf, int nbyte)- La función de lectura intenta leer nbyte bytes del archivo asociado con el descriptor de archivo abierto, fildes, en el búfer al que apunta buf. Una vez completado con éxito, write () devuelve el número de bytes realmente escritos en el archivo asociado con los archivos. Este número nunca es mayor que nbyte. De lo contrario, se devuelve -1.

  • int fork (void)- La función de horquilla crea un nuevo proceso. El nuevo proceso, llamado proceso hijo, será una copia exacta del proceso de llamada (proceso padre).

  • void bzero (void *s, int nbyte)- La función bzero coloca nbyte bytes nulos en la cadena s. Esta función se utilizará para configurar todas las estructuras de conectores con valores nulos.

  • int bcmp (const void *s1, const void *s2, int nbyte)- La función bcmp compara la cadena de bytes s1 con la cadena de bytes s2. Se supone que ambas cadenas tienen una longitud de nbyte bytes.

  • void bcopy (const void *s1, void *s2, int nbyte)- La función bcopy copia nbyte bytes de la cadena s1 a la cadena s2. Las cadenas superpuestas se manejan correctamente.

  • void *memset(void *s, int c, int nbyte) - La función memset también se usa para establecer variables de estructura de la misma manera que bzero.