[Programación de sockets] Servidor TCP, servidor UDP, socket local [implementación de código en lenguaje C]

Tabla de contenido

0. Preparar conocimientos

0.1 Conceptos endianes grandes y pequeños

0.2 Conversión entre el orden de bytes de la red y el orden de bytes del host

0.3 Conversión de cadena decimal con puntos (función de conversión de dirección IP)

0.4 Estructura IPV4: (man 7 ip)

0.5 Estructura de socket IPV6: (man 7 ipv6)

0.6 Estructura de enchufe universal

1. Funciones del socket de red

1.1 enchufe

1.2 conectar

1.3 enlazar

1.4 escuchar

1.5 aceptar

1.6 Reutilización de puertos

2. Función de envoltura

2.1 envoltura.c

2.2 envoltura.h

3.servidor TCP

3.1 Versión sencilla

3.2 Versión multiproceso

3.3 Versión multiproceso

4. servidor UDP

5. Enchufe local

Resumir:


0. Preparar conocimientos

0.1 Conceptos endianes grandes y pequeños

大端存储模式: significa que el orden de gama baja de los datos se almacena en la dirección alta de la memoria , y el orden de gama alta de los datos se almacena en la dirección baja de la memoria
小端存储模式: significa que el orden de gama baja de los datos se almacena en la dirección baja de la memoria y los datos se almacenan en la dirección baja de la memoria se almacena en la dirección alta de la memoria en la dirección alta de

Cuando se utilizan diferentes métodos de almacenamiento, los datos almacenados son 0x12345678 :
Insertar descripción de la imagen aquí

0.2 Conversión entre el orden de bytes de la red y el orden de bytes del host

        El protocolo TCP/IP estipula que el flujo de datos de la red debe estar en orden de bytes big-endian , es decir, dirección baja y byte alto. Si el host es big-endian, no se requiere conversión para enviar y recibir. De la misma manera, las direcciones IP de 32 bits también deben considerar el orden de bytes de la red y el orden de bytes del host.

        Para que los programas de red sean portátiles y el mismo código C se ejecute normalmente después de la compilación en computadoras big-endian y little-endian, puede llamar a la siguiente función de biblioteca para convertir el orden de bytes de la red y el orden de bytes del host .

#incluir <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostlshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

h representa el host, n representa la red, l representa 32 bits y s representa 16 bits.

Si el host es little-endian, estas funciones convertirán los parámetros en consecuencia y los devolverán. Si el host es big-endian, estas funciones no convertirán los parámetros y los devolverán sin cambios.

Ejemplo de código 1:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    char buf[4] = {
        192, 168, 1, 2
    };
    unsigned int num = *(int*)buf;
    unsigned int sum = htonl(num);
    unsigned char* p = (unsigned char*)&sum;

    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    unsigned short a = 0x0102;
    unsigned short b = htons(a);
    printf("%#x\n", b);

    return 0;
}

Captura de pantalla de ejecución:

 Ejemplo de código 2:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    unsigned char buf[4] = {
        1, 1, 168, 192
    };
    int num = *(int*)buf;
    int sum = ntohl(num);
    unsigned char* p = (unsigned char*)&sum;

    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    return 0;
}

Captura de pantalla de ejecución:

 

0.3 Conversión de cadena decimal con puntos (función de conversión de dirección IP)

        La dirección IP que normalmente vemos es la cadena "192.168.1.2", que debe convertirse.

#incluir <apra/inet.h>

//Convierte la cadena decimal con puntos en datos big-endian de red de 32 bits

int inet_pton(int af, const char *src, void *dst);

Soporta IPV4 e IPV6

parámetro:

        de:

                AF_INET:IPV4

                AF_INET6:IPV6

        src: dirección de la cadena decimal con puntos

        dst: la dirección donde se almacenan los datos de la red de 32 bits

valor de retorno:

        Éxito: 1

        fallido: 0

#incluir <apra/inet.h>

//Convierte datos big-endian de red de 32 bits en una cadena decimal con puntos

const char *inet_ntop(int af, const void *src, char *dst, tamaño socklen_t);

parámetro:

        de:

                AF_INET:IPV4

                AF_INET6:IPV6

        src: dirección de datos de red de 32 bits

        dst: la dirección donde se almacena la cadena decimal con puntos

        tamaño: el tamaño de la matriz de cadenas decimales del punto de almacenamiento (el número de dígitos debe determinarse específicamente)

                INET_ADDRSTRLEN valor de macro 16

valor de retorno:

        Almacene la primera dirección de la cadena decimal con puntos

Ejemplo de código:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    char buf[] = "192.168.1.2";
    unsigned int num = 0;

    inet_pton(AF_INET, buf, &num);
    unsigned char* p = (unsigned char*)&num;
    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    char ip[16] = "";
    inet_ntop(AF_INET, &num, ip, 16);
    printf("%s\n", ip);

    return 0;
}

Captura de pantalla de ejecución:

La comunicación en red resuelve tres problemas principales: protocolo, IP y puerto.

0.4 Estructura IPV4: (man 7 ip)

 struct sockaddr_in {                sa_family_t sin_family; /* familia de direcciones: AF_INET */                in_port_t sin_port; /* puerto en orden de bytes de red */                struct in_addr sin_addr; /* dirección de internet */            };



 /* Dirección de Internet. */
           struct in_addr {                uint32_t s_addr; /* dirección en orden de bytes de red */            };

0.5 Estructura de socket IPV6: (man 7 ipv6)

 struct sockaddr_in6 {                sa_family_t sin6_family; /* AF_INET6 */                in_port_t sin6_port; /* número de puerto */                uint32_t sin6_flowinfo; /* información de flujo IPv6 */                struct in6_addr sin6_addr; /* dirección IPv6 */                uint32_t sin6_scope_id; /* ID de alcance (nuevo en 2.4) */            };





           estructura in6_addr {                char sin firmar s6_addr[16]; /* dirección IPv6 */            };

0.6 Estructura de enchufe universal

struct sockaddr {     sa_family_t sa_family; // Familia de direcciones     char sa_data[14]; // Datos de dirección };


Nota: normalmente se utiliza el siguiente formulario

estructura sockaddr_in dirección;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

1. Funciones del socket de red

1.1 enchufe

#incluir <sys/types.h>

#incluir <sys/socket.h>

int socket(dominio int, tipo int, protocolo int);

Función: Crear socket

parámetro:

        dominio:
                AF_INET Este es el protocolo utilizado por la mayoría de los sockets, usa TCP o UDP para la transmisión, usa la dirección IPv4
                AF_INET6 es similar al anterior, pero usa la dirección IPv6
                Protocolo local AF_UNIX, usado en sistemas Unix y Linux, generalmente
        Tipo: SOCK_STREAM se utiliza cuando el cliente y el servidor están en la misma computadora.
                Este protocolo es una conexión basada en flujo de bytes secuencial, confiable e integrada de datos. Este es el tipo de socket más utilizado y utiliza TCP para la transmisión .
                SOCK_DGRAM Este protocolo es una llamada de transferencia de longitud fija y sin conexión. Este protocolo no es confiable y utiliza UDP para sus conexiones.
                SOCK_SEQPACKET Este protocolo es una conexión confiable de doble línea que envía paquetes de datos de longitud fija para su transmisión. Este paquete debe aceptarse por completo antes de poder leerlo.
                El tipo de socket SOCK_RAW proporciona acceso único a la red. Este tipo de socket utiliza el protocolo público ICMP. (ping y traceroute usan este protocolo)
                SOCK_RDM Este tipo rara vez se usa y no está implementado en la mayoría de los sistemas operativos. Se proporciona a la capa de enlace de datos y no garantiza el orden de los paquetes de datos. Protocolo
:

valor de retorno:

        Éxito: descriptor de archivo

        Fallo: -1

1.2 conectar

#incluir <sys/types.h>

#incluir <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Función: conectarse al servidor

parámetro:

        sockfd: descriptor de archivo de socket

        addr: dirección de la estructura del socket ipv4, incluida la dirección IP y el número de puerto

        addrlen: longitud de la estructura del socket ipv4

valor de retorno:

        Éxito: 0

        Fallo: -1

1.3 enlazar

#incluir <sys/types.h>

#incluir <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

Función: encuadernación

parámetro:

        sockfd: descriptor de archivo de socket

        addr: estructura del socket ipv4, incluida la dirección IP y el número de puerto

        addrlen: tamaño de la estructura del socket ipv4

valor de retorno:

        Éxito: 0

        Fallo: -1

1.4 escuchar

#incluir <sys/types.h>

#incluir <sys/socket.h>

int escucha(int sockfd, int backlog);

Función: escuchar monitoreo

parámetro:

        sockfd: descriptor de archivo de socket

        trabajo pendiente: el valor máximo de la suma de la cola completada y la cola incompleta es 128

                        Ver: cat /proc/sys/net/ipv4/tcp_max_syn_backlog 

valor de retorno:

        Éxito: 0

        Fallo: -1

1.5 aceptar

#incluir <sys/types.h>

#incluir <sys/socket.h>

int aceptar(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

Función: Extrae nuevas conexiones de la cola de conexiones completadas (si no hay nuevas conexiones, aceptar se bloqueará)

parámetro:

        calcetín: enchufe

        dirección: estructura de socket ipv4

        addrlen: dirección del tamaño de la estructura del socket ipv4

valor de retorno:

        Éxito: descriptor de archivo para el nuevo enchufe conectado

        Fallo: -1

1.6 Reutilización de puertos

Inserte el siguiente código entre el socket y las llamadas de enlace en el código del servidor:

int opt ​​= 1;

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 

Nota: Configure un determinado puerto en el programa para su reutilización. Otros programas de red anteriores a este no podrán utilizar este puerto. 

2. Función de envoltura

2.1 envoltura.c

#include <stdlib.h>                                                                                                                                       
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char* s)
{
    perror(s);
    exit(-1);
}

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
    int n;

again:
    if ((n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被信号中断和软件层次中断,不能退出
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}

int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int n;

    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");

    return n;
}
int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int n;

    if ((n = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
    int n;

    if ((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");

    return n;
}
ssize_t Read(int fd, void* ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ((n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)//如果是被信号中断,不应该退出
            goto again;
        else
            return -1;
    }
    return n;
}

ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ((n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");

    return n;
}

/*参三: 应该读取固定的字节数数据*/
ssize_t Readn(int fd, void* vptr, size_t n)
{
    size_t  nleft;              //usigned int 剩余未读取的字节数
    ssize_t nread;              //int 实际读到的字节数
    char* ptr;

    ptr = vptr;
    nleft = n;

    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        }
        else if (nread == 0)
            break;

        nleft -= nread;
        ptr += nread;
    }
    return n - nleft;
}

/*:固定的字节数数据*/
ssize_t Writen(int fd, const void* vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char* ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ((nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }

        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static ssize_t my_read(int fd, char* ptr)
{
    static int read_cnt;
    static char* read_ptr;
    static char read_buf[100];

    if (read_cnt <= 0) {
    again:
        if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR)
                goto again;
            return -1;
        }
        else if (read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;

    return 1;
}

ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, * ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ((rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;
        }
        else if (rc == 0) {
            *ptr = 0;
            return n - 1;
        }
        else
            return -1;
    }
    *ptr = 0;

    return n;
}

int tcp4bind(short port, const char* IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));
    if (IP == NULL) {
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }
    else {
        if (inet_pton(AF_INET, IP, &serv_addr.sin_addr.s_addr) <= 0) {
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    //端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    return lfd;
}

2.2 envoltura.h

#ifndef __WRAP_H_                                                                                                                                         
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char* s);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);
int tcp4bind(short port, const char* IP);
#endif

3.servidor TCP

Diagrama de flujo de creación del modelo de socket:

3.1 Versión sencilla

cliente.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_IP "192.168.0.105"
#define SERVER_PORT 8008
int main()
{
    //创建套接字
    int sock_fd;
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    //连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &addr.sin_addr);
    connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
    //读写数据
    char buf[1024] = "";
    while (1)
    {
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(sock_fd, buf, n);//发送数据
        n = read(sock_fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
    }
    //关闭
    close(sock_fd);

    return 0;
}

servidor.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

#define SERVER_PORT 8008
#define SERVER_IP "192.168.0.106"
#define BACKLOG 128
int main()
{
    //创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    //绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);
    //addr.sin_addr.s_addr = INADDR_ANY;  //绑定的是通配地址
    inet_pton(AF_INET, SERVER_IP, &addr.sin_addr.s_addr);
    bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    //监听
    listen(lfd, BACKLOG);
    //提取
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
    char ip[16] = "";
    printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
    //读写
    char buf[1024] = "";
    while (1)
    {
        bzero(buf, sizeof(buf));
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(cfd, buf, n);
        n = read(cfd, buf, sizeof(buf));
        printf("%s\n", buf);
    }
    //关闭
    close(lfd);
    close(cfd);

    return 0;
}

Una vez iniciados el cliente y el servidor, puede utilizar el comando netstat para ver el estado del enlace:

netstat-apn|grep 6666

3.2 Versión multiproceso

servidor.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include "wrap.h"

#define SERVER_PORT 8000
#define SERVER_IP "192.168.0.106" 
#define BACKLOG 128

void free_process(int sig)
{
    pid_t pid;
    while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
    {
        printf("child pid = %d has exited\n", pid);
    }
}
void handle_client(int cfd)
{
    char buf[1024];
    ssize_t n;

    while ((n = read(cfd, buf, sizeof(buf))) > 0)
    {
        printf("from clent :%s\n", buf);
        if (write(cfd, buf, n) < 0)
        {
            perror("Fail to sedn response to client");
            Close(cfd);
            exit(1);
        }
    }

    if (n < 0)
    {
        perror("Fail to read from client");
    }
    printf("Client closed connection\n");
    Close(cfd);
    exit(0);
}
int main()
{
    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = free_process;
    if (sigaction(SIGCHLD, &act, NULL) < 0)
    {
        perror("fail to sigaction");
        exit(1);
    }
    //创建套接字
    int lfd = tcp4bind(SERVER_PORT, NULL);
    //监听
    Listen(lfd, BACKLOG);
    //提前
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);

    while (1)
    {
        char ip[16] = "";
        //提取连接
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
        printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
        //fork创建子进程
        pid_t pid;
        pid = fork();

        if (pid < 0)
        {
            perror("fail to fork");
            Close(cfd);
            continue;
        }
        else if (pid == 0)
        {
            Close(lfd);
            handle_client(cfd);
            break;
        }

        Close(cfd);
    }
    //关闭
    Close(lfd);

    return 0;
}

3.3 Versión multiproceso

servidor.c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include "wrap.h"
#include <arpa/inet.h>

typedef struct c_info {
    int cfd;
    struct sockaddr_in cliaddr;
}CINFO;


void* client_fun(void* arg)
{
    CINFO* info = (CINFO*)arg;
    char ip[16] = "";
    printf("new client ip =%s port =%d\n", inet_ntop(AF_INET, &(info->cliaddr.sin_addr.s_addr), ip, 16), ntohs(info->cliaddr.sin_port));

    while (1)
    {
        char buf[1024] = "";
        int count = 0;
        count = read(info->cfd, buf, sizeof(buf));
        if (count < 0)
        {
            perror("");
            break;
        }
        else if (count == 0)
        {
            printf("client close\n");
            break;
        }
        else
        {
            printf("%s\n", buf);
            write(info->cfd, buf, count);
        }
    }
    Close(info->cfd);
    free(info);
    pthread_exit(NULL);
}
int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        perr_exit("argc < 2\n ./a.out 8000\n");
    }
    pthread_attr_t attr;
    int s = pthread_attr_init(&attr);
    if (s != 0)
    {
        perr_exit("pthread_attr_init error");
    }
    s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (s != 0)
    {
        perr_exit("pthread_attr_setdetachstate error");
    }
    short port = atoi(argv[1]);
    int lfd = tcp4bind(port, NULL);

    Listen(lfd, 128);
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    CINFO* info;

    while (1)
    {
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
        char ip[16] = "";

        pthread_t pthid;
        info = (CINFO*)malloc(sizeof(CINFO));
        if (NULL == info)
        {
            perr_exit("malloc error");
        }
        info->cfd = cfd;
        info->cliaddr = cliaddr;
        pthread_create(&pthid, &attr, client_fun, info);
    }
    return 0;
}

4. servidor UDP

        En comparación con TCP, la comunicación UDP se parece más al envío de mensajes de texto. No es necesario establecer y mantener conexiones antes de la transmisión de datos. Solo concéntrate en obtener los datos. Al eliminar el proceso de protocolo de enlace de tres vías, la velocidad de comunicación se puede mejorar enormemente, pero no se puede garantizar la estabilidad y precisión de la comunicación adjunta. Por lo tanto, llamamos a UDP "entrega de mensajes no confiables sin conexión".

        Dado que no es necesario crear una conexión, la sobrecarga de UDP es pequeña, la velocidad de transmisión de datos es rápida y el rendimiento en tiempo real es sólido. Se utiliza principalmente en situaciones de comunicación con altos requisitos de tiempo real, como videoconferencias, conferencias telefónicas, etc. Sin embargo, también va acompañado de una transmisión de datos poco fiable y no se puede controlar ni garantizar la precisión, la secuencia de transmisión y el flujo de los datos transmitidos. Por lo tanto, en circunstancias normales, se utiliza el protocolo UDP para la transmisión de datos. Para garantizar la exactitud de los datos, necesitamos agregar un protocolo de verificación auxiliar en la capa de aplicación para compensar las deficiencias de UDP para lograr el propósito de transmisión de datos confiable.

        Al igual que TCP, UDP también puede experimentar pérdida de paquetes al recibir datos después de llenar el búfer. Dado que no tiene el mecanismo de ventana deslizante TCP, generalmente se utilizan los dos métodos siguientes para resolverlo:

  1. La capa de aplicación del servidor diseña el control de flujo para controlar la velocidad de envío de datos.
  2. Utilice la función setsockopt para cambiar el tamaño del búfer de recepción. como:

#include <sys/socket.h>
int setsockopt(int sockfd, int nivel, int optname, const void* optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

Modelo C/S UDP:

 

servidor.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_PORT 8001
#define MAXLINE 1024

int main()
{
    int sockfd;
    char buf[MAXLINE];
    struct sockaddr_in seraddr, cliaddr;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    memset(&seraddr, 0, sizeof(seraddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERVER_PORT);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (const struct sockaddr*)&seraddr, sizeof(seraddr)) < 0)
    {
        perror("fail to bind");
        exit(1);
    }
    socklen_t len = sizeof(cliaddr);
    int n;

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        n = recvfrom(sockfd, buf, MAXLINE, MSG_WAITALL, (struct sockaddr*)&cliaddr, &len);
        if (n < 0)
        {
            perror("fail to recvfrom");
            break;
        }
        else
        {
            printf("From Client data:%s\n", buf);
            if (sendto(sockfd, buf, n, 0, (const struct sockaddr*)&cliaddr, len) == -1)
            {
                perror("fail to sendto");
                break;
            }
        }
    }

    close(sockfd);
    return 0;
}

 cliente.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_PORT 8001
#define MAXLINE 1024    

int main()
{
    int sockfd;
    char buf[MAXLINE];
    struct sockaddr_in seraddr;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    memset(&seraddr, 0, sizeof(seraddr));

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);

    int n;
    socklen_t len = sizeof(seraddr);

    while (1)
    {
        n = read(STDIN_FILENO, buf, sizeof(buf));
        if(sendto(sockfd, buf, n, 0, (const struct sockaddr*)&seraddr, len) == -1)
        {
            perror("fail to sendto");
            break;
        }

        memset(buf, 0, sizeof(buf));
        n = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&seraddr, &len);
        if (n < 0)
        {
            perror("fail to recvfrom");
            break;
        }
        else
        {
            printf("From Server data:%s\n", buf);
        }
    }
    close(sockfd);

    return 0;
}

5. Enchufe local

        La API de socket se diseñó originalmente para la comunicación de red, pero luego se desarrolló un mecanismo IPC basado en el marco de socket, que es UNIX Domain Socket. Aunque los sockets de red también se pueden utilizar para la comunicación entre procesos en el mismo host (a través de la dirección de bucle invertido 127.0.0.1), los sockets de dominio UNIX son más eficientes para IPC: no es necesario pasar por la pila de protocolos de red, empaquetar y desempacar, y cálculo de suma de verificación, mantenga números de secuencia y respuestas, etc., simplemente copie los datos de la capa de aplicación de un proceso a otro. Esto se debe a que el mecanismo IPC es esencialmente una comunicación confiable, mientras que los protocolos de red están diseñados para una comunicación no confiable. UNIX Domain Socket también proporciona dos interfaces API orientadas a flujos y paquetes, similares a TCP y UDP, pero UNIX Domain Socket orientado a mensajes también es confiable y los mensajes no se perderán ni desordenarán.

        UNIX Domain Socket es full-duplex y tiene una rica semántica de interfaz API. Tiene ventajas obvias sobre otros mecanismos IPC. Se ha convertido en el mecanismo IPC más utilizado. Por ejemplo, la comunicación entre el servidor X Window y el programa GUI se realiza a través de UNIX Domain Socket. . .

        El proceso de uso de UNIX Domain Socket es muy similar al de un socket de red. Primero debe llamar a socket() para crear un descriptor de archivo de socket. La familia de direcciones se especifica como AF_UNIX, el tipo puede ser SOCK_DGRAM o SOCK_STREAM, y el protocolo El parámetro todavía está especificado como 0.

        La diferencia más obvia entre la programación de socket de dominio UNIX y socket de red es que el formato de dirección es diferente, representado por la estructura sockaddr_un. La dirección del socket de la programación de red es la dirección IP más el número de puerto, mientras que la dirección de Socket de dominio UNIX es un archivo de tipo socket en el sistema de archivos. La ruta de este archivo de socket se crea mediante la llamada bind(). Si el archivo ya existe cuando se llama a bind(), bind() devuelve un error.

        Compare la estructura de direcciones del socket de red y la estructura de direcciones del socket local:

struct sockaddr_in {     __kernel_sa_family_t sin_family; /* Familia de direcciones */ Tipo de estructura de direcciones         __be16 sin_port; /* Número de puerto */ Número de puerto         struct in_addr sin_addr; /* Dirección de Internet */ Dirección IP }; struct sockaddr_un {     __kernel_sa_family_t sun_family; /* AF_UNIX * / Tipo de estructura de dirección         char sun_path[UNIX_PATH_MAX]; /* nombre de ruta */ nombre de archivo de socket (incluida la ruta) };







El siguiente programa vincula un socket de dominio UNIX a una dirección. 

tamaño = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#definir desplazamiento de (tipo, miembro) ((int)&((tipo *)0)->miembro) 

 servicio:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

char *socket_path = "/tmp/demo_socket";

int main(void) {
    struct sockaddr_un addr;
    char buf[100];
    int fd,cl,rc;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

    unlink(socket_path);

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind error");
        exit(-1);
    }

    if (listen(fd, 5) == -1) {
        perror("listen error");
        exit(-1);
    }

    while (1) {
        if ((cl = accept(fd, NULL, NULL)) == -1) {
            perror("accept error");
            continue;
        }

        while ((rc=read(cl,buf,sizeof(buf))) > 0) {
            printf("read %u bytes: %.*s\n", rc, rc, buf);
            write(cl, buf, rc);
        }
        if (rc == -1) {
            perror("read");
            exit(-1);
        }
        else if (rc == 0) {
            printf("EOF\n");
            close(cl);
        }
    }

    return 0;
}

cliente:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

char *socket_path = "/tmp/demo_socket";

int main(void) {
    struct sockaddr_un addr;
    char buf[100];
    int fd,rc;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect error");
        exit(-1);
    }

    while(1) {
        printf("Enter message to send: ");
        fgets(buf, sizeof(buf), stdin);
        if ((rc = write(fd, buf, strlen(buf))) > 0) {
            printf("Message sent\n");
            read(fd, buf, sizeof(buf));
            printf("Server replied : %s\n", buf);
        }
        else {
            printf("Error or connection closed\n");
            break;
        }
    }

    return 0;
}

Resumir:

        Estos son códigos implementados en lenguaje C. Se recomienda comprenderlos y escribirlos usted mismo.

Supongo que te gusta

Origin blog.csdn.net/crr411422/article/details/131680574
Recomendado
Clasificación