[La evolución del servidor TCP] Escribir el primer servidor TCP: lograr una comunicación de conexión uno a uno

I. Introducción

Enseñarle paso a paso cómo escribir un programa de servidor TCP desde cero y experimentar cómo se puede construir un edificio con un solo ladrillo al principio .

Para evitar ser demasiado largo y aburrir al lector, [el desarrollo del servidor TCP] se implementa en etapas y se optimiza y actualiza paso a paso.
Insertar descripción de la imagen aquí

2. API que deben utilizarse

2.1 Función socket()

Prototipo de función:

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

Esta función crea un descriptor de archivo de socket con familia de protocolo, tipo de protocolo y número de protocolo. Si la llamada a la función tiene éxito, devolverá un descriptor de archivo que identifica el socket. Si falla, devolverá -1 y establecerá errno.

Significado del valor del parámetro de dominio:

nombre significado
PF_UNIX, PF_LOCAL comunicación local
AF_INET,PF_INET protocolo IPv4
PF_INET6 protocolo IPv6
PF_NETENLACE dispositivo de interfaz de usuario del kernel
PAQUETE_PF Acceso a paquetes de bajo nivel

Escriba el significado del valor del parámetro:

nombre significado
SOCK_STREAM La conexión TCP proporciona un flujo de bytes de conexión bidireccional, confiable y serializado. Admite transferencia de datos fuera de banda
calcetín_dgram conexión UDP
SOCK_SEQPACKET El paquete de serialización proporciona un canal de transmisión de datos bidireccional, confiable y serializado con una longitud de datos constante. Cada vez que se llama a la llamada al sistema de lectura, es necesario leer todos los datos.
:SOCK_PACKET Tipo especial
calcetín_rdm Proporciona paquetes de datos confiables, pero no garantiza que los datos estén en orden.
calcetín_raw Proporciona acceso al protocolo de red sin formato

Significado del parámetro de protocolo:
por lo general, solo hay un tipo específico en un protocolo, por lo que el parámetro de protocolo solo se puede configurar en 0; si el protocolo tiene varios tipos específicos, debe configurar este parámetro para seleccionar un tipo específico.

2.2 función enlazar()

Prototipo de función:

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

Descripción de parámetros:

  • El primer parámetro sockfd es el descriptor de archivo creado con la función socket().
  • El segundo parámetro my_addr es un puntero a una estructura de parámetros de sockaddr. sockaddr contiene información de dirección, puerto y dirección IP.
  • El tercer parámetro addrlen es la longitud de la estructura my_addr, que se puede establecer en sizeof (struct sockaddr).

Cuando el valor de retorno de la función bind() es 0, significa que la vinculación se realizó correctamente y -1 significa que la vinculación falló y se establece errno.

2.3 función escuchar ()

Prototipo de función:

#include<sys/socket.h>
int listen(int sockfd, int backlog);

Descripción de parámetros:

  • El primer parámetro sockfd es el descriptor de archivo creado con la función socket().
  • El segundo parámetro backlog especifica el número máximo de conexiones que el kernel debe poner en cola para el socket correspondiente.

2.4 función aceptar ()

Prototipo de función:

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

Descripción de parámetros:

  • sockefd: descriptor de socket que escucha las conexiones después de escuchar().
  • addr: puntero (opcional). Puntero a un búfer que recibe las direcciones de entidades conectadas conocidas por la capa de comunicación. El formato real del parámetro Addr está determinado por la familia de direcciones generada cuando se creó el socket.
  • addrlen: puntero (opcional). El parámetro de entrada, utilizado junto con addr, apunta a un número entero que contiene la longitud de la dirección addr.

2.5 Función recv()

Prototipo de función:

#include<sys/types.h>
#include<sys/socket.h>
int recv( int fd, char *buf, int len, int flags);

Descripción de parámetros:

  • El primer parámetro especifica el descriptor del socket receptor;
  • El segundo parámetro especifica un búfer, que se utiliza para almacenar los datos recibidos por la función recv;
  • El tercer parámetro especifica la longitud de buf;
  • El cuarto parámetro generalmente se establece en 0.

valor de retorno:

  • Devuelve un número mayor que 0, que indica el tamaño de los datos introducidos.
  • Devuelve 0, lo que indica que la conexión está desconectada.
  • Devuelve -1, lo que indica un error al aceptar datos.

2.6 Función enviar()

Prototipo de función:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Descripción de parámetros:

  • sockfd: envía datos al socket
  • buf: la primera dirección de los datos a enviar
  • len: bytes de datos a enviar
  • Banderas int: cuando se establece en MSG_DONTWAITMSG, significa sin bloqueo. Cuando se establece en 0, la función es la misma que escribir.

Valor de retorno: el número real de bytes enviados se devuelve en caso de éxito, -1 se devuelve en caso de error y se establece errno.

2.7 Función strerror()

strerror()La función devuelve un puntero a una cadena que describe el código de error pasado en el parámetro errnum, posiblemente usando la parte LC_MESSAGES de la configuración regional actual para seleccionar el idioma apropiado. (Por ejemplo, si errnum es EINVAL, la descripción devuelta será "Parámetro no válido".) La aplicación no puede modificar esta cadena, pero sí mediante llamadas posteriores a strerror()o strerror_l(). perror()Esta cadena no será modificada por ninguna otra función de biblioteca, incluida

Prototipo de función:

#include <string.h>

char *strerror(int errnum);

int strerror_r(int errnum, char *buf, size_t buflen);
/* XSI-compliant */
char *strerror_r(int errnum, char *buf, size_t buflen);
/* GNU-specific */
char *strerror_l(int errnum, locale_t locale);

3. Pasos de implementación

Diseño de servidor uno a uno:
Insertar descripción de la imagen aquí

(1) Crear socket.

int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
    return SOCKET_CREATE_FAILED;
}

(2) Dirección vinculante.

struct sockaddr_in server;
memset(&server,0,sizeof(server));

server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);

if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_BIND_FAILED;
}

(3) Configurar el monitoreo.

if(-1==listen(listenfd,BLOCK_SIZE)){
    
    
   printf("errno = %d, %s\n",errno,strerror(errno));
   close(listenfd);
   return SOCKET_LISTEN_FAILED;
}

(4) Recibir conexión.

struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
    
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_ACCEPT_FAILED;
}

(5) Recibir datos.

char buf[BUFFER_LENGTH]={
    
    0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
    
    
     printf("connection dropped\n");

}
printf("recv --> %s\n",buf);

(6) Enviar datos.

if(-1==send(clientfd,buf,ret,0))
{
    
    
    printf("errno = %d, %s\n",errno,strerror(errno));
}

(7) Cierre el descriptor de archivo.

close(clientfd);
close(listenfd);

4. Código completo

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

#include <errno.h>
#include <string.h>
#include <unistd.h>


#define LISTEN_PORT     8888
#define BLOCK_SIZE      10
#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    
    
    SOCKET_CREATE_FAILED=-1,
    SOCKET_BIND_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};



int main(int argc,char **argv)
{
    
    
    // 1.
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    if(listenfd==-1){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;
    }

    // 2.
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(LISTEN_PORT);

    if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_BIND_FAILED;
    }

    // 3.
    if(-1==listen(listenfd,BLOCK_SIZE)){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_LISTEN_FAILED;
    }

    // 4.
    struct sockaddr_in client;
    memset(&client,0,sizeof(client));
    socklen_t len=sizeof(client);
    
    int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
    if(clientfd==-1){
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_ACCEPT_FAILED;
    }

    printf("client fd = %d\n",clientfd);
    int ret=1;
    while(ret>0){
    
    
        // 5.
        char buf[BUFFER_LENGTH]={
    
    0};
        ret=recv(clientfd,buf,BUFFER_LENGTH,0);
        if(ret==0) {
    
    
            printf("connection dropped\n");
            break;

        }
        
        printf("recv --> %s\n",buf);
        if(-1==send(clientfd,buf,ret,0))
        {
    
    
            printf("errno = %d, %s\n",errno,strerror(errno));
        }
        
    }
    close(clientfd);
    close(listenfd);

    return 0;
}

Comando de compilación:

gcc -o server server.c

5. Cliente TCP

5.1 Implemente usted mismo un cliente TCP

Implemente el código para que un cliente TCP se conecte usted mismo al servidor TCP:

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

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>

#include <unistd.h>
#include <stdlib.h>

#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    
    
    SOCKET_CREATE_FAILED=-1,
    SOCKET_CONN_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};

int main(int argc,char** argv)
{
    
    
    if(argc<3)
    {
    
    
        printf("Please enter the server IP and port.");
        return 0;
    }
    printf("connect to %s, port=%s\n",argv[1],argv[2]);

    int connfd=socket(AF_INET,SOCK_STREAM,0);
    if(connfd==-1)
    {
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;

    }
    struct sockaddr_in serv;
    serv.sin_family=AF_INET;
    serv.sin_addr.s_addr=inet_addr(argv[1]);
    serv.sin_port=htons(atoi(argv[2]));
    socklen_t len=sizeof(serv);
    int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
    if(rwfd==-1)
    {
    
    
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(rwfd);
        return SOCKET_CONN_FAILED;
    }
    int ret=1;
    while(ret>0)
    {
    
    
        char buf[BUFFER_LENGTH]={
    
    0};
        printf("Please enter the string to send:\n");
        scanf("%s",buf);
        send(connfd,buf,strlen(buf),0);

        memset(buf,0,BUFFER_LENGTH);
        printf("recv:\n");
        ret=recv(connfd,buf,BUFFER_LENGTH,0);
        printf("%s\n",buf);
        
    }
    close(rwfd);
    return 0;
}

Compilar:

gcc -o client client.c

5.2. Puede utilizar la herramienta de asistente de red NetAssist en Windows

Insertar descripción de la imagen aquí

Dirección de descarga: http://old.tpyboard.com/downloads/NetAssist.exe

resumen

En este punto, hemos logrado una conexión de servidor uno a uno. El código del servidor TCP en esta etapa solo puede recibir acceso de un cliente. Si el cliente se desconecta, saldrá directamente. La atención se centra en dominar el proceso básico de desarrollo de un servidor TCP. El próximo capítulo presentará la actualización sobre esta base para aceptar el acceso simultáneo de múltiples clientes.
Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/Long_xu/article/details/135118661
Recomendado
Clasificación