tomada
Crie um arquivo de soquete:
#include <sys/socket.h>
// 成功返回非负套接字描述符,失败返回-1
int socket(int domain, int type, int protocol);
valor do domínio:
domínio | Descrição do produto |
---|---|
AF_INET | Protocolos de Internet IPv4 |
AF_INET6 | Protocolos de Internet IPv6 |
tipo valor:
tipo | Descrição do produto |
---|---|
SOCK_STREAM | Fornece fluxos de bytes sequenciais, confiáveis, bidirecionais e baseados em conexão. Um mecanismo de transmissão de dados fora de banda pode ser suportado. |
SOCK_DGRAM | Suporta datagramas (mensagens não confiáveis e sem conexão, de comprimento máximo fixo). |
valor do protocolo:
protocolo | Descrição do produto |
---|---|
IPPROTO_TCP | Protocolo TCP |
IPPROTO_UDP | Protocolo UDP |
Como não há macro definida no soquete, é necessária uma introdução adicional<netinet/in.h>
sockaddr
sockaddr é usado para registrar informações de ip e porta sockaddr é uma estrutura geral.Não existe um campo de informação específico para dividir ip e porta.Para o endereço ipv4, é necessária a estrutura sockaddr_in.Para ipv6, é necessária a estrutura sockaddr_in6:
<sys/socket.h>
// 通用地址信息结构体
struct sockaddr {
sa_family_t sa_family; // 地址簇
char sa_data[14]; // 填充字符
};
<netinet/in.h>
// 存储ipv4地址
struct in_addr {
in_addr_t s_addr; // ipv4地址(网络序4字节)
};
// ipv4地址和端口信息
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // 端口号(网络序2字节)
struct in_addr sin_addr; // ipv4地址
char sin_zero[8]; // 填充字符
};
// 存储ipv6地址
struct in6_addr {
uint8_t s6_addr[16]; // ipv6地址(网络序16字节)
};
// ipv6地址和端口信息
struct sockaddr_in6 {
sa_family_t sin6_family; // AF_INET6
in_port_t sin6_port; // 端口号(网络序2字节)
uint32_t sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // ipv6地址
uint32_t sin6_scope_id; // 作用域的接口集合
};
Antes de usar a estrutura sockaddr_in, é necessário limpar toda a estrutura.
Inet_pton pode ser usado para converter a string no tipo de endereço IP, e a conversão retorna 1:
<netinet/in.h>
// 转换成功返回1,ip字符串不合法返回0,地址簇不支持返回-1
int inet_pton(int af, const char *src, void *dst);
Além de converter o endereço IP por meio de uma sequência, o <netinet/in.h>
arquivo de cabeçalho é definido INADDR_ANY
para representar 0.0.0.0
esse endereço curinga.
ligar
Ligue o soquete ao endereço IP e à porta especificados:
#include <sys/socket.h>
// 失败返回-1,成功返回0
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
Para o servidor, se você quiser ouvir várias placas de rede ao mesmo tempo, precisará vincular a ele.Se INADDR_ANY
você aceitar apenas o acesso local, precisará vincular a 127.0.0.1
ele.
ouço
O estado descritor de socket do servidor CLOSED
passa a LISTEN
:
#include <sys/socket.h>
// 失败返回-1,成功返回0
int listen(int sockfd, int backlog);
backlog
Indica o tamanho da fila de conexão.Embora não esteja especificado no padrão, na implementação do BSD4.2, a fila de conexão inclui duas conexões de estado:
- Algumas conexões acabaram de receber um pacote SYN e estão em um
SYN_RCVD
estado. - Algumas conexões concluíram o processo de handshake de três vias e estão em um
ESTABLISHED
estado
Você não pode definir o backlog como 0, porque esse padrão de comportamento não está definido; se você não deseja que o cliente possa se conectar, precisará fechar o soquete de escuta. E o tamanho da fila de conexão real não é necessariamente igual backlog
, por exemplo, algumas implementações serão multiplicadas por um fator de 1,5.
aceitar
Usado para aceitar conexões em um ESTABLISHED
estado na fila de conexão:
#include <sys/socket.h>
// 成功返回非负的连接套接字描述符,失败返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
O cliaddr pode ser usado para obter o endereço e as informações da porta do cliente.
#include <arpa/inet.h>
// 转换成功返回dst指针,转换失败返回NULL
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
<netinet/in.h>
O arquivo de cabeçalho define o comprimento da matriz de caracteres necessária para salvar o endereço ipv4 e a sequência ipv6:
#define INET_ADDRSTRLEN 16 /* for IPv4 dotted-decimal */
#define INET6_ADDRSTRLEN 46 /* for IPv6 hex string */
conectar
O cliente é usado para conectar-se ao servidor:
#include <sys/socket.h>
// 失败返回-1,成功返回0
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Quando o cliente chama essa interface, ele inicia um handshake de três vias TCP e envia primeiro um pacote SYN, se:
- Se nenhuma resposta for recebida, o mecanismo de tempo limite (75 segundos) será acionado. Durante esse período, o pacote SYNC será retransmitido continuamente até que o erro ETIMEOUT seja retornado.
- Se o host de serviço retornar RST (redefinir), isso significa que não há serviço atendendo na porta no host de serviço e, em seguida, o cliente retornará um erro ECONNREFUSED
- Se um pacote ICMP for retornado, indicando que a rede ou o host está inacessível, um mecanismo de tempo limite ainda será acionado, durante o qual os pacotes SYNC serão retransmitidos continuamente até que seja retornado um erro EHOSTUNREACH
Código do cliente
#include <iostream>
#include <string>
#include <cstring> // memset strlen
#include <cstdint> // uint16_t
#include <cstdio> // snprintf
#include <sys/socket.h> // socket
#include <netinet/in.h> // IPPROTO_TCP htons
#include <arpa/inet.h> // inet_pton
#include <unistd.h> // write read
int main(int argc, char const *argv[])
{
// 创建连接套接字
int connectfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == connectfd) {
std::cout << "create connectfd failed" << std::endl;
return -1;
}
std::cout << "connectfd: " << connectfd << std::endl;
// 设置服务器地址(ipv4)
sockaddr_in servAddr;
std::string ipAddr = "127.0.0.1";
uint16_t portNum = 23333;
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(portNum);
int res = inet_pton(AF_INET, ipAddr.c_str(), &servAddr.sin_addr);
if (1 != res) {
std::cout << "presentation(" << ipAddr << ") to numeric failed" << std::endl;
return -1;
}
// 连接服务器
res = connect(connectfd, reinterpret_cast<sockaddr *>(&servAddr), sizeof(servAddr));
if (-1 == res) {
std::cout << "connect server failed" << std::endl;
return -1;
}
// 发送消息
constexpr int MAX = 1024;
char sendBuf[MAX + 1];
snprintf(sendBuf, sizeof(sendBuf), "hello");
write(connectfd, sendBuf, strlen(sendBuf));
// 接受回执
char recvBuf[MAX + 1];
read(connectfd, recvBuf, sizeof(recvBuf));
std::cout << recvBuf << std::endl;
// 关闭套接字
close(connectfd);
return 0;
}
Código do servidor:
#include <iostream>
#include <string>
#include <cstring> // memset
#include <cstdint> // uint16_t
#include <sys/socket.h> // socket
#include <netinet/in.h> // IPPROTO_TCP htons ntohs INET_ADDRSTRLEN
#include <arpa/inet.h> // inet_pton inet_ntop
#include <unistd.h> // write read
int main(int argc, char const *argv[])
{
// 创建监听socket
int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (-1 == listenfd) {
std::cout << "create listenfd failed" << std::endl;
return -1;
}
std::cout << "listenfd: " << listenfd << std::endl;
// 设置服务器地址(ipv4)
sockaddr_in servAddr;
std::string ipAddr = "0.0.0.0";
uint16_t portNum = 23333;
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(portNum);
int res = inet_pton(AF_INET, ipAddr.c_str(), &servAddr.sin_addr);
if (1 != res) {
std::cout << "presentation(" << ipAddr << ") to numeric failed" << std::endl;
return -1;
}
std::cout << "set ipv4 address and port number success" << std::endl;
// 绑定服务器地址
res = bind(listenfd, reinterpret_cast<sockaddr*>(&servAddr), sizeof(servAddr));
if (0 != res) {
std::cout << "bind listenfd failed" << std::endl;
return -1;
}
std::cout << "bind listenfd success" << std::endl;
// 套接字开启监听
res = listen(listenfd, 5);
if (0 != res) {
std::cout << "listen listenfd failed" << std::endl;
return -1;
}
std::cout << "listen listenfd success" << std::endl;
// 服务循环
while (true) {
// 接受远端连接
sockaddr_in clientAddr;
socklen_t len = sizeof(clientAddr);
int connectfd = accept(listenfd, reinterpret_cast<sockaddr*>(&clientAddr), &len);
if (-1 == connectfd) {
std::cout << "accept connectfd failed" << std::endl;
return -1;
}
char clientIpAddr[INET_ADDRSTRLEN];
uint16_t clientPortNum = ntohs(clientAddr.sin_port);
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIpAddr, sizeof(clientIpAddr));
std::cout << "accept connectfd(" << clientIpAddr << ":" << clientPortNum << ") success" << std::endl;
// 接受消息
constexpr int MAX = 1024;
char recvBuf[MAX + 1];
read(connectfd, recvBuf, sizeof(recvBuf));
// echo消息
char sendBuf[MAX + 1];
snprintf(sendBuf, sizeof(sendBuf), "%s", recvBuf);
write(connectfd, sendBuf, strlen(sendBuf));
// 本轮服务结束,关闭连接套接字
close(connectfd);
}
return 0;
}