índice
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
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 ++