(Notas de programação de rede): programação SOCKET

índice

Programação SOCKET

Ordem de bytes da rede

Função de conversão de endereço IP

Uma estrutura importante usada na programação de socket: struct sockaddr

Introdução às principais funções da API de programação de socket

Etapas da função Socket API para escrever programas de servidor e cliente

Processo de desenvolvimento de servidor

Processo de desenvolvimento de cliente

Exemplo de programa de servidor

Exemplo de programa cliente

Programação SOCKET

  • A comunicação tradicional entre processos é realizada com a ajuda do mecanismo IPC fornecido pelo kernel, mas pode ser limitada apenas à comunicação da máquina local.Se você quiser se comunicar entre as máquinas, deve usar a comunicação de rede. (Essencialmente por meio do kernel-kernel fornece um mecanismo de pseudo-arquivo de soquete para alcançar a comunicação - na verdade, usando descritores de arquivo ), isso requer o uso da biblioteca API de soquete fornecida pelo kernel para o usuário
  • Como o pseudo arquivo de socket é mencionado, você pode usar a função read write relacionada ao descritor de arquivo
  • Usar soquete irá criar um par de soquetes
  • Conforme mostrado na figura abaixo, um descritor de arquivo opera dois buffers , que é diferente de um pipe, que é dois descritores de arquivo operando um buffer de kernel.

Ordem de bytes da rede

  • O conceito de big endian e little endian
    • Big endian: endereço de baixa ordem armazena dados de alta ordem, endereço de alta ordem armazena dados de baixa ordem
    • Little endian: endereços de baixa ordem armazenam dados de baixa ordem, endereços de alta ordem armazenam dados de alta ordem
  • Use ocasiões de big-endian e little-endian:
    • Big-endian e little-endian têm apenas dois ou mais tipos de dados de comprimento, como int short. Não há restrição para um único byte. Na rede, muitas vezes é necessário considerar o IP e a porta de big-endian e little-endian.
  • Big-endian é usado para transmissão de rede. Se a máquina usa little-endian, ela precisa ser convertida entre big-endian e big-endian.
  • As quatro funções a seguir são as funções para realizar a conversão endian grande e pequena:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  • O h no nome da função significa host, n significa rede, s significa curto, l significa longo
  • As várias funções acima não serão convertidas se não precisarem ser convertidas dentro da função .

Função de conversão de endereço IP

  • p-> representa a forma de string da notação decimal com pontos
  • para-> para
  • n-> significa rede
int inet_pton(int af, const char *src, void *dst);
  • Descrição da função : converter IP decimal pontuado em formato de string para IP de rede no modo big-endian (formato de 4 bytes)
  • Descrição do parâmetro:
    • por: AF_INET
    • src: endereço IP decimal pontuado em formato de string
    • dst: armazena o endereço da variável convertida
  • Por exemplo: inet_pton (AF_INET, "127.0.0.1", & serv.sin_addr.s_addr);    
    • Também pode ser calculado manualmente: como 192.168.232.145, primeiro converta os 4 números positivos em números hexadecimais, 
    • 192 ---> 0xC0 168 ---> 0xA8 232 ---> 0xE8 145 ---> 0x91
    • Finalmente, ele é armazenado na ordem de bytes big-endian: 0x91E8A8C0, este é o valor inteiro de 4 bytes.
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • Descrição da função: converter IP de rede em IP decimal pontuado na forma de string
  • Descrição do parâmetro:
    • por: AF_INET
    • src: o endereço IP formatado da rede
    • dst: endereço IP convertido, geralmente uma matriz de string
    • tamanho: comprimento de dst
  • valor de retorno: 
    • Sucesso - retorna um ponteiro para dst
    • Failure-return NULL, e set errno
  • Por exemplo: o endereço IP é 010aa8c0, convertido para o formato decimal com pontos:
    • 01 ----> 1 0a ----> 10 a8 ----> 168 c0 ----> 192
    • Como o endereço IP na rede escrava está no modo high-end, ele deve ser convertido para decimal pontuado: 192.168.10.1

Uma estrutura importante usada na programação de socket: struct sockaddr

  • descrição da estrutura de struct sockaddr:
struct sockaddr {
        sa_family_t sa_family;
        char     sa_data[14];
}
  • estrutura de struct sockaddr_in:
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr {
    uint32_t  s_addr;     /* address in network byte order */
};	 //网络字节序IP--大端模式
  • Você pode ver as instruções relacionadas através do man 7 ip

Introdução às principais funções da API de programação de socket

int socket(int domain, int type, int protocol);
  • Descrição da função : Criar soquete
  • Descrição do parâmetro:
    • domínio: versão do protocolo
      • AF_INET IPV4
      • AF_INET6 IPV6
      • AF_UNIX AF_LOCAL uso de soquete local
    • tipo: tipo de protocolo
      • Streaming SOCK_STREAM, o protocolo padrão usado é o protocolo TCP
      • Formato de relatório SOCK_DGRAM, protocolo UDP é usado por padrão
    • protocal: 
      • Geralmente, preencha 0, o que significa usar o protocolo padrão do tipo correspondente.
  • valor de retorno: 
    • Sucesso: Retorne um descritor de arquivo maior que 0
    • Falha: retornar -1 e definir errno
  • Quando a função de soquete é chamada, um descritor de arquivo é retornado, e o kernel irá fornecer os buffers de leitura e gravação correspondentes ao descritor de arquivo. Ao mesmo tempo, existem duas filas, ou seja, a fila de conexão de solicitação e a fila conectada

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • Descrição da função: Vincule o descritor de arquivo de socket com IP e PORT
  • Descrição do parâmetro:
    • socket: o descritor de arquivo retornado chamando a função socket
    • addr : o endereço IP e a PORTA do servidor local, 
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);
//INADDR_ANY: 表示使用本机任意有效的可用IP

inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
  • addrlen: o tamanho da memória da variável addr 
  • valor de retorno: 
    • Sucesso: retorna 0
    • Falha: retornar -1 e definir errno
int listen(int sockfd, int backlog);
  • Descrição da função: mude o soquete de dinâmico principal para dinâmico
  • Descrição do parâmetro:
    • sockfd: o descritor de arquivo retornado ao chamar a função de soquete
    • backlog: O número máximo de solicitações simultâneas (conexões ainda não estabelecidas) 
  • valor de retorno:
    • Sucesso: retorna 0
    • Falha: retornar -1 e definir errno                
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);    
  • Descrição da função: Obter uma conexão, se não houver nenhuma conexão no momento, irá bloquear a espera.
  • Parâmetros de função:
    • sockfd: o descritor de arquivo retornado ao chamar a função de soquete
    • addr : parâmetros de saída, salvar as informações de endereço do cliente
    • addrlen : parâmetros de entrada e saída , o espaço de memória ocupado por variáveis ​​addr
  • valor de retorno:
    • Sucesso: Retorne um novo descritor de arquivo para comunicação com o cliente
    • Falha: Retorne -1 e defina o valor errno.
  • A função de aceitação é uma função de bloqueio, se não houver uma nova solicitação de conexão, ela sempre bloqueará.
  • Obtenha uma nova conexão da fila conectada e obtenha um novo descritor de arquivo, que é usado para se comunicar com o cliente . (O kernel será responsável por obter a conexão na fila de solicitações para a fila conectada)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • Descrição da função: conectar servidor
  • Parâmetros de função:
    • sockfd: o descritor de arquivo retornado ao chamar a função de soquete
    • addr: informações de endereço do servidor
    • addrlen: o tamanho da memória da variável addr
  • valor de retorno:
    • Sucesso: retorna 0
    • Falha: retornar -1 e definir o valor errno
  • Em seguida, você pode usar as funções de gravação e leitura para realizar operações de leitura e gravação.
  • Além de usar funções de leitura / gravação, você também pode usar funções de recepção e envio
  • Leia e envie dados:
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);    
  • Correspondendo aos dois sinalizadores de função de recv e send, basta preencher 0 diretamente .
  • Nota : Se o buffer de gravação estiver cheio, a gravação também bloqueará. Durante uma operação de leitura, se não houver dados no buffer de leitura, isso causará o bloqueio.

Etapas da função Socket API para escrever programas de servidor e cliente

Processo de desenvolvimento de servidor

  • Crie um soquete e retorne um descritor de arquivo lfd --- socket () - o descritor de arquivo é usado para monitorar as conexões do cliente
  • Bind lfd e IP PORT ---- bind ()
  • Alterar lfd de monitoramento ativo para passivo ---- listen ()
  • Aceite uma nova conexão e obtenha um descritor de arquivo cfd ---- accept () --- O descritor de arquivo é usado para se comunicar com o cliente
  • enquanto (1)
    • {
    •     Receber dados --- ler ou recv
    •      Enviar dados --- escrever ou enviar
    •  }
  • Feche o descritor de arquivo-close (lfd) close (cfd);

Processo de desenvolvimento de cliente

  • Crie um soquete, retorne um descritor de arquivo cfd --- socket () - o descritor de arquivo é usado para se comunicar com o servidor
  • Conecte-se ao servidor --- conectar () 
  • enquanto (1)
    •  {
    •         // Enviar dados --- escrever ou enviar
    •         // Receber dados --- ler ou receber
    • }
  • fechar (cfd)

Exemplo de programa de servidor

//服务端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

int main()
{
	//创建socket
	//int socket(int domain, int type, int protocol);
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd<0)
	{
		perror("socket error");
		return -1;
	}
	
	//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	//绑定
	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY); //表示使用本地任意可用IP
	int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("bind error");	
		return -1;
	}

	//监听
	//int listen(int sockfd, int backlog);
	listen(lfd, 128);

	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	int cfd = accept(lfd, (struct sockaddr *)&client, &len);  //len是一个输入输出参数
	//const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
	
	//获取client端的IP和端口
	char sIP[16];
	memset(sIP, 0x00, sizeof(sIP));
	printf("client-->IP:[%s],PORT:[%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port));
	printf("lfd==[%d], cfd==[%d]\n", lfd, cfd);

	int i = 0;
	int n = 0;
	char buf[1024];

	while(1)
	{
		//读数据
		memset(buf, 0x00, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or client close, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);	

		for(i=0; i<n; i++)
		{
			buf[i] = toupper(buf[i]);
		}

		//发送数据
		write(cfd, buf, n);
	}

	//关闭监听文件描述符和通信文件描述符
	close(lfd);
	close(cfd);
	
	return 0;
}
  • Teste (usando a ferramenta de teste nc) :
nc ip 端口号

Exemplo de programa cliente

//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main()
{
	//创建socket---用于和服务端进行通信
	int cfd = socket(AF_INET, SOCK_STREAM, 0);
	if(cfd<0)
	{
		perror("socket error");
		return -1;
	}

	//连接服务端
	//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	struct sockaddr_in serv;
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
	printf("[%x]\n", serv.sin_addr.s_addr);
	int ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv));
	if(ret<0)
	{
		perror("connect error");
		return -1;
	}	

	int n = 0;
	char buf[256];
	while(1)
	{
		//读标准输入数据
		memset(buf, 0x00, sizeof(buf));
		n = read(STDIN_FILENO, buf, sizeof(buf));
		
		//发送数据
		write(cfd, buf, n);

		//读服务端发来的数据
		memset(buf, 0x00, sizeof(buf));
		n = read(cfd, buf, sizeof(buf));
		if(n<=0)
		{
			printf("read error or server closed, n==[%d]\n", n);
			break;
		}
		printf("n==[%d], buf==[%s]\n", n, buf);
	}

	//关闭套接字cfd
	close(cfd);

	return 0;
}
  • Teste (usando netstat):

  • [Nota]: Consulte o tutorial Dark Horse Linux C ++

Acho que você gosta

Origin blog.csdn.net/baidu_41388533/article/details/108943887
Recomendado
Clasificación