[Comunicación de red] programación de socket - socket TCP

TCP todavía usa códigos para familiarizarse con los sockets correspondientes.Muchas interfaces se han usado en udp,
por lo que no se tomarán por separado como título, y solo se usará como título la interfaz que aparece por primera vez.

A través del socket TCP, los datos se entregan a la capa de aplicación de la otra parte y se completa la comunicación entre los dos procesos.

servidor tcp_servidor

tcpserver.hpp (encapsulación)

En tcpServer.hpp, cree un espacio de nombres yzq para la encapsulación
En el espacio de nombres, defina una clase TcpServer

Esta clase contiene construcción, destrucción, inicialización (initServer) inicio (inicio)


Inicializar initServer

1. Crea un zócalo

Establezca el número de puerto de escucha (se explica más adelante), el número de puerto es necesario para identificar la unicidad del proceso


Establezca un número de puerto predeterminado 8888 fuera de la clase como el valor predeterminado del puerto del parámetro del constructor


crear zócalo


Ingrese el zócalo del hombre

El primer dominio de parámetros se utiliza para distinguir entre comunicación de red y comunicación local.
Si desea comunicarse a través de la red, use AF_INET;
si desea comunicarse localmente, use AF_UNIX

El segundo tipo de parámetro, el tipo de servicio correspondiente al socket


SOCK_STREAM stream socket
SOCK_DGRAM comunicación no confiable sin conexión (datagrama de usuario)

El tercer parámetro, protocol, indica qué protocolo desea utilizar. El protocolo predeterminado es 0.
Si es un socket de flujo, el sistema considerará que es el protocolo TCP. Si es un datagrama de usuario, el sistema lo considerará. ser el protocolo UDP.

El valor de retorno del socket: el descriptor de archivo se devuelve si tiene éxito y se devuelve -1 si falla


Indica comunicación de red, socket de transmisión y el sistema considera que es un protocolo TCP


Cree una enumeración de err.hpp para almacenar información de error


Si falla la creación, termine el programa

2. atar atar

Ingrese man 2 bind para ver el enlace

Vincular un nombre a un socket
El primer parámetro sockfd es el socket
El segundo parámetro addr es el tipo de estructura general
El tercer parámetro addrlen es la longitud real del segundo parámetro

enlazar valor devuelto: si tiene éxito, devuelve 0, si falla, devuelve -1


El uso de bind debe realizarse con la ayuda de una estructura general,
así que defina una estructura de tipo de comunicación de red local

En el último blog describí en detalle la composición interna de la estructura sockaddr_in,
si no entiendes puedes ir a ver: Comprensión de la estructura sockaddr_in


htons - convierte la secuencia del host en la secuencia de la red

Ingrese man htons, que representa la secuencia de enteros cortos de host a red

Por lo tanto, debe convertir el port_ del host y luego transferirlo al sin_port local (número de puerto)


INADDR_ANY significa cualquier IP de enlace


Devuelve -1 si el enlace falla


3. Supervisar

escuchar - establecer en estado de escucha

Ingrese man 2 listen
para establecer el estado actual del socket en el estado de escucha

El primer parámetro sockfd es el socket.
El segundo parámetro no se explica temporalmente y generalmente se establece en un número entero.
Si tiene éxito, devuelve 0, y si falla, devuelve -1.


Si la supervisión falla, devuelve -1 y finaliza el programa.


Establezca un número entero predeterminado en 32 fuera de la clase

comenzar

Establezca una variable booleana quit_, si es verdadero, significa que el servidor está iniciado, si es falso, significa que el servidor no está iniciado


Si el servidor no se inicia, ingrese un ciclo while

1. Obtener conexión, aceptar

aceptar

Escriba hombre 2 aceptar

Necesita saber quién está conectado con usted, por lo que necesita obtener información relevante sobre el cliente

El primer parámetro sockfd es el socket,
el segundo parámetro addr es una estructura de tipo de estructura general. Esta estructura se utiliza para registrar el número de puerto, la dirección IP, el tipo de dirección de 16 bits y otra información en el cliente. El tercer parámetro addrlen
es el tamaño de la estructura

Valor devuelto:
si tiene éxito, devuelve un entero legal que es el descriptor del archivo
; si falla, devuelve -1 y establece el código de error

La relación entre el descriptor de archivo devuelto por aceptar y el descriptor de archivo devuelto correctamente por configuración de socket

Por ejemplo: hay una pescadería, el negocio no es muy bueno, por lo que hay un hombre llamado Zhang San parado afuera para atraer clientes.
Un día, usted y sus amigos se encuentran con Zhang San afuera, y Zhang San le dice cuántos peces granjas que tienen. , Recomiendo ir a donde comen pescado.
Ustedes dos también tienen hambre, así que fui a Yuzhuang a comer pescado con Zhang San, pero solo usted entró en la aldea de pescado, Zhang San no entró, Zhang San
solo gritó adentro, hay invitados, y luego continuó encontrando a alguien.
En este momento, un mesero Li Si vino a preguntarle qué desea comer y brindarle varios servicios.

Cada vez que Zhang San da la bienvenida a los invitados a Yuzhuang, habrá un camarero para brindar servicio a los invitados.
Cuando Zhang San termina su trabajo, regresa inmediatamente a su trabajo y continúa atrayendo invitados.

Zhang San no brinda servicios específicos a los usuarios, sino que solo es responsable de llevar
a los clientes de la calle al restaurante para su consumo. sockfd, que se denomina enchufe de escucha en el primer parámetro de accept , actúa como Li Si, que es equivalente para aceptar devolver un descriptor de archivo.Este descriptor de archivo realmente proporciona servicios IO a los usuarios.



Si Zhang San continúa solicitando clientes, se encuentra con una persona en el camino y le pregunta si quiere ir a cenar a Yuzhuang, pero la persona niega con la cabeza y expresa que no quiere ir a cenar a Yuzhuang. tiempo,
Zhang San será rechazado, pero esto no afecta a Zhang San continuó solicitando clientes para ir a Yuzhuang
, por lo que la aceptación falló, solo continúe ejecutando

2. Obtenga una nueva conexión con éxito e inicie el procesamiento comercial

Proporcione una función de servicio, el parámetro es el nuevo calcetín de descriptor de archivo
utilizado para implementar servicios básicos de lectura y escritura, es decir, el cliente envía un mensaje y el mensaje debe devolverse


TCP es un servicio de transmisión
ingrese man 2 read

Leer los datos que queremos del descriptor de archivo fd en forma de bloques de datos.Cuántos
bytes representa el valor devuelto.Cuando se lee el final del archivo, es 0, y si falla, es -1.


Lea los datos en el calcetín en el búfer del búfer
Si la lectura es exitosa, asigne el siguiente bit del último bit a 0


Si el valor de retorno de read es 0, la otra parte cerrará la conexión, por lo que el calcetín también se puede cerrar


Si el valor devuelto es menor que 0, la lectura falla y se devuelve un código de error


Después de recibir el mensaje, debe realizar un procesamiento en el mensaje y luego transferir el mensaje de vuelta,
así que use el procesamiento funcional de envoltura

Establezca un tipo de función fuera de la clase, el valor devuelto es una cadena y el parámetro es un contenedor de cadena


Defina el tipo de función como una función de variable privada



Devuelve el mensaje procesado
Enter man 2 write
para escribir información en un archivo

fd representa el descriptor de archivo
buf representa el
conteo del búfer representa el tamaño del búfer
write escribe los datos del tamaño de conteo del búfer en fd


Escriba los datos en res en el descriptor del archivo sock

tcpserver.cc (función principal implementación principal)

Solo quiero ingresar ./tcp_server más el número de puerto , así que agregue dos parámetros de
la función principal del parámetro de línea de comando a la función principal , char* argv[] es una matriz de punteros, y argv es una tabla que contiene punteros, que apuntan a cadenas int argc, argc es el número de elementos en la matriz


Cuando la entrada del parámetro no es 2, el programa terminará y los parámetros de entrada correspondientes se imprimirán al mismo tiempo.


Sabiendo a través del constructor, si desea usar el nuevo TcpServer, debe pasar la devolución de llamada y el número de puerto


cliente tcp_cliente

tcpclient.cc (no encapsulado, implementado directamente)

Para usar el cliente, debe ingresar el programa ejecutable correspondiente serverip serverport,
por lo que debe usar los parámetros de la línea de comando en la función principal


Si los parámetros de entrada son inferiores a 3, termine el programa e imprima los parámetros de entrada correspondientes


Asigne la dirección IP de la entrada del segundo parámetro al
número de puerto de la entrada del tercer parámetro por serverip, use atoi para convertir la cadena en un número entero y luego asígnela a serverport

1. Crea un zócalo

Comunicación de red, y es un socket de flujo, el valor predeterminado es 0, porque es un flujo, por lo que es un protocolo TCP
. Si falla la creación del socket, el programa finalizará


2. Iniciar enlace

Escriba hombre aceptar

El cliente inicia una solicitud de conexión a un servidor específico a través del socket sockfd
sockfd: socket
addr: la estructura de tipo público contiene la dirección IP del servidor y el número de puerto
addrlen: el tamaño de la estructura

Valor de retorno: si tiene éxito, devolverá 0, si falla, devolverá -1 y un código de error.
Cuando se inicia la conexión por primera vez, el sistema operativo vinculará automáticamente el puerto al cliente.


Entonces, primero debe definir un servidor de estructura

Utilice htons para convertir el número de puerto serie del host mencionado anteriormente en un número de puerto serie de red

inet_addr - dirección IP de cadena a dirección IP de secuencia de red

Introduzca man inet_addr

El primer parámetro es la dirección IP del estilo de cadena
y el segundo parámetro es la dirección IP de la secuencia de red.
Convierta la dirección IP del estilo de cadena en la dirección IP de la secuencia de red.


Luego, convierta la dirección IP serverip de la secuencia de host en la dirección IP de la secuencia de red


cnt indica el número de reconexiones
. Establezca el ciclo while. Cuando la conexión falla cuando no es igual a 0, el valor de cnt se reducirá en 1 y la conexión se volverá a conectar. Si el valor de cnt es 0, break terminará la conexión. Si el bucle while sale y cont es menor o
igual a 0, el programa terminará.


3. El enlace es exitoso

Cree una línea de tipo cadena, pase los parámetros de entrada a la línea,
use escribir, pase el contenido de la línea al descriptor de archivo,
use lectura, pase los datos del calcetín al búfer
y juzgue por el valor de retorno de lectura, si el valor de retorno Si es mayor que 0, genera el contenido
. Si el valor de retorno es igual a 0, significa que el enlace está cerrado y luego sale del ciclo while.
Si el valor de retorno es menor que, significa que la creación falló y se devuelve un código de error.

Implementación de código específico

err.hpp (utilizado para almacenar mensajes de error)

#pragma once 

enum
{
    
    
  USAGE_ERR=1,
  SOCKET_ERR,//2
  BIND_ERR,//3
  LISTEN_ERR//4
};


archivo MAKE

.PHONY:all
all: tcp_client tcp_server

tcp_client:tcpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
tcp_server:tcpServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f tcp_client tcp_server


tcpServer.hpp (paquete de servidor)

#pragma once

#include<iostream>
#include<cstdlib>
#include<string.h>
#include<unistd.h>
#include"err.hpp"
#include <sys/types.h>          
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<functional>



namespace yzq
{
    
    
    
    static uint16_t defaultport=8888;//默认端口号
    static const int backlog=32;//默认整数为32
    using func_t=std::function<std::string(const std::string&)>;

    class TcpServer;
    class ThreadData//该类用于存放客户端的IP port 套接字
    {
    
     
        public:
        ThreadData(int fd,const std::string&ip,const uint16_t &port,TcpServer*ts)//构造
        :sock(fd),clientip(ip),clientport(port),current(ts)
        {
    
    }
      public:
       int sock;//套接字
       std::string clientip;//客户端IP
       uint16_t clientport;//客户端端口号
       TcpServer*current;
    };
    
   class TcpServer
   {
    
    
    public:
    TcpServer(func_t func,uint16_t port=defaultport)
    :func_(func),port_(port),quit_(true)//表示默认启动
    {
    
    }
    void initServer()//初始化
    {
    
    
        //1.创建socket
        listensock_=socket(AF_INET,SOCK_STREAM,0);
        if(listensock_<0)//创建失败
        {
    
    
            std::cout<<" create socket errno"<<std::endl;
            exit(SOCKET_ERR);//终止程序
        }
        //2. bind 绑定
        struct sockaddr_in local;//网络通信类型
        //清空
        memset(&local,'\0',sizeof(local));
        local.sin_family=AF_INET;//网络通信
        //htons 主机转网络
        local.sin_port=htons(port_);//端口号
        local.sin_addr.s_addr=INADDR_ANY ; //IP地址

        if(bind(listensock_,(struct sockaddr*)&local,sizeof(local))<0)
        //失败返回-1
        {
    
    
           std::cout<<" bind socket errno"<<std::endl;
            exit(BIND_ERR);//终止程序
        }

        // 3.监听
        if(listen(listensock_,backlog)<0)
        {
    
    
            //监听失败返回-1
             std::cout<<" listen socket errno"<<std::endl;
            exit(LISTEN_ERR);//终止程序
        }
       
    }
    void start()//启动
    {
    
     
      quit_=false;//服务器没有启动
      while(!quit_)
      {
    
    
        //4.获取连接,accept
        struct sockaddr_in client;//网络通信类型
        socklen_t len=sizeof(client);//结构体大小
        int sock=accept(listensock_,(struct sockaddr*)&client,&len);    
        if(sock<0)
        {
    
    
            //获取失败
            std::cout<<" accept  errno"<<std::endl;
            continue;//继续执行
        }
        //提取客户端信息
        std::string clientip=inet_ntoa(client.sin_addr);//客户端ip
        uint16_t clientport=ntohs(client.sin_port);//客户端端口号
        //5.获取新连接成功,开始进行业务处理
        std::cout<<"获取新连接成功: "<<sock<<"from "<<listensock_<<std::endl; 
        //service(sock);//多线程版本没有调用函数

       //多线程版本
         pthread_t tid;
         ThreadData*td=new ThreadData(sock,clientip,clientport,this);
         pthread_create(&tid,nullptr,threadRoutine,td);
      }
    }
   static void *threadRoutine(void*args)
    {
    
       
        pthread_detach(pthread_self());//线程分离
        ThreadData*td=(ThreadData*)args;
        td->current->service(td->sock);
        delete td;
        return nullptr;
    }
    void service(int sock)
    {
    
    
        char buffer[1024];
        while(true)
        {
    
    
            //将sock中的数据读取到buffer中
            ssize_t s=read(sock,buffer,sizeof(buffer)-1);
             if(s>0)
             {
    
    
                //读取成功
                buffer[s]=0;
                //使用func 进行回调
                std::string res=func_(buffer);
                std::cout<<res<<std::endl;
                //将res中的数据写给sock中
                write(sock,res.c_str(),res.size());

             }
             else if(s==0)
             {
    
    
                //说明对方将连接关闭了
                close(sock);
                std::cout<<"client quit,me too"<<std::endl;
                break; 
             }
             else 
             {
    
    
                //读取失败返回-1
                std::cout<<"read errno"<<strerror(errno)<<std::endl;
                break;
             }
        } 
    }
    ~TcpServer()
    {
    
    }
      private:
      func_t func_;//函数类型
      int listensock_;//监听套接字 
      bool quit_;//表示服务器是否启动
      uint16_t port_;//端口号
   };
}


tcpServer.cc (implementación de la función principal del servidor)

#include"tcpServer.hpp"
#include<memory>//智能指针
using namespace std;
using namespace yzq;

static void usage(string proc)
{
    
    
    std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}

std::string echo(const std::string&message)
{
    
    
    return message;
}
// ./tcp_server port
int main(int argc,char*argv[])
{
    
    
    //输入两个参数 所以不等于2
    if(argc!=2)
    {
    
    
       usage(argv[0]);
       exit(USAGE_ERR);//终止程序
    }
    //将输入的端口号 转化为整数 
    uint16_t port=atoi(argv[1]);
   unique_ptr<TcpServer>tsvr(new TcpServer(echo,port));
   tsvr->initServer();//服务器初始化
   tsvr->start();//启动
    return 0;
}

tcpClient.cc (el cliente no está encapsulado)

#include<iostream>
#include<cstring>
#include<unistd.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"err.hpp"
using namespace std;

static void usage(string proc)
{
    
    
    std::cout<<"usage:\n\t"<<proc<<"port\n"<<std::endl;
}

//./tcp_client  serverip serverport
int main(int argc,char*argv[])
{
    
    
   if(argc!=3)
   {
    
    
       usage(argv[0]);
       exit(USAGE_ERR);//终止程序
   }
   std::string serverip=argv[1];//IP地址
   uint16_t serverport=atoi(argv[2]);//端口号
   
   //1.创建套接字
   int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
    
    
      //创建失败
      cout<<"socket errnr:"<<strerror(errno)<<endl;
      exit(SOCKET_ERR);//终止程序
    }

    //2.发起链接
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));//清空
    server.sin_family=AF_INET;//网络通信类型
    //htons 主机序列转为网络序列
    server.sin_port=htons(serverport);//网络端口号
    inet_aton(serverip.c_str(),&server.sin_addr);//网络IP地址

    int cnt=5;//重连次数
    while(connect(sock,(struct sockaddr*)&server,sizeof(server))!=0)
    {
    
    
      //不等于0则链接失败
      sleep(1);
      cout<<"正在尝试重连,重连次数还有:"<<cnt--<<endl;
      if(cnt<=0)
      {
    
    
        //没有重连次数
        break;
      }
    }
    if(cnt<=0)
    {
    
    
        //链接失败
        cout<<"链接失败.."<<endl;
        exit(SOCKET_ERR);//终止程序
    }

    char buffer[1024];
    //3.链接成功
    while(true)
    {
    
    
        string line;
        cout<<"enter>>";
        getline(cin,line);//从cin中获取内容 写入line中
        write(sock,line.c_str(),line.size());//将line中的内容写入到sock文件描述符中   
        ssize_t s=read(sock,buffer,sizeof(buffer)-1);
        if(s>0)
        {
    
    
            buffer[s]=0;
            cout<<"server echo"<<buffer<<endl;
        }
        else if(s==0)
        {
    
    
            cout<<"server quit"<<endl;
            break;
        }
        else 
        {
    
    
            cout<<"read errno"<<strerror(errno)<<endl;
            break;
        }
    }
    close(sock);
    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/qq_62939852/article/details/132198349
Recomendado
Clasificación