Diretório de artigos
Prefácio
No artigo anterior, simplesmente implementamos uma sala de chat online usando Udp. Hoje aprenderemos como usar soquetes TCP.
Eco baseado em TCP
Variáveis de membro
//端口号
uint16_t _port;
//要执行的回调
func_t _func;
//listen套接字
int _socklisten;
função de membro
- Init
completa a criação, vinculação e monitoramento do soquete
void initServer()
{
// 1 创建socket接口,打开网络文件
_socklisten = socket(AF_INET, SOCK_STREAM, 0);
if (_socklisten < 0)
{
logMessage(Error, "create socket error");
exit(SOCKED_ERR);
}
logMessage(Info, "create socket success");
// 2 给服务器指明IP地址和Port
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port); // host to network short
local.sin_addr.s_addr = INADDR_ANY; // bind本主机任意ip
if (bind(_socklisten, (sockaddr*)&local, sizeof(local)) < 0)
{
logMessage(Error, "bind socket error code:%d info:%s", errno, strerror(errno));
exit(BIND_ERR);
}
logMessage(Info, "bind socket success");
// 3 监听
if (listen(_socklisten, backlog) < 0)
{
logMessage(Error, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(Info, "listen socket success");
}
- começar
dividimos em quatro versões para chegar ao negócio final:-
1. Uso de teste de serviço, sem muita explicação
-
2. Versão multiprocesso
Usando o método multiprocesso, vários processos podem lidar com diferentes solicitações de serviço, mas há duas questões a serem consideradas ao usar este método:-
a. O processo filho deve esperar? Quem vai esperar?
É claro que o processo filho precisa esperar, mas não queremos que seja o processo pai, porque o processo pai tem que lidar com assuntos de outros clientes. Aqui estão duas maneiras de resolver esse problema:- Use sinais para ignorar
sinal SIGCHLD(SIGCHLD,SIG_IGN); - Quando
o processo pai é encerrado, o processo filho se torna um processo órfão.
- Use sinais para ignorar
-
b. Os fds que não são necessários ao processo filho e ao processo pai precisam ser fechados?
O fd do processo pai deve ser fechado, enquanto o fd do processo filho pode optar por ser fechado. Se o processo pai não fechar o fd usado para comunicação, isso logo levará a mais e mais fds e, então, ocorrerão vazamentos no descritor de arquivo.
-
-
3. Versão nativa do thread.
A coisa mais importante a observar aqui é que a tarefa do thread precisa passar muitos parâmetros, então precisamos construir uma classe ThreadData.ThreadDate *td = new ThreadData(sock, clientip, clientport, this);
Na tarefa thread, encontre uma forma de executar a função a ser executada na classe por meio da conversão de tipo.
Se você não quiser reciclar o thread, você pode usar pthread_detach(pthread_self()); -
4. Versão do pool de threads
O pool de threads resolve principalmente solicitações curtas e frequentes. No capítulo multithreading, falaremos detalhadamente sobre o design do pool de threads.
-
void start()
{
while (true)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
// 4 获取连接,accept
int sock = accept(_socklisten, (struct sockaddr *)&client, &len);
if (sock < 0)
{
logMessage(Warning, "获取连接失败,code:%d,error string:%s", errno, strerror(errno));
continue;
}
std::string clientip = inet_ntoa(client.sin_addr);
uint16_t clientport = ntohs(client.sin_port);
// 5 获取连接成功开始进行业务处理
logMessage(Info, "获取连接成功:%d from %d,client:%s-%d", sock, _socklisten, clientip.c_str(), clientport);
// 1 test
// service(sock,clientip,clientport);
// 2 多进程
// pid_t id = fork();
// if(id < 0)
// {
// logMessage(Warning,"创建线程失败 code: %d error:%s",errno,strerror(errno));
// continue;
// }
// else if(id == 0)
// {
// //子进程可以选择关闭不需要的fd
// close(_socklisten);
// if(fork() > 0) exit(0);
// //现在是孙子进程被bash1领养了,不需要等待了
// service(sock,clientip,clientport);
// exit(0);
// }
// //父进程必须关闭不用的fd防止fd泄露
// close(sock);
// 3 源生线程
// pthread_t tid;
// ThreadDate *td = new ThreadDate(sock, clientip, clientport, this);
// pthread_create(&tid, nullptr, threadRoutine, td);
// 4 线程池版本
Task t(sock,clientip,clientport,std::bind(&TcpServer::service,this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
ThreadPool<Task>::GetInstance()->pushTask(t);
}
}
Classe SOCK encapsulada
Variáveis de membro
Apenas uma variável de membro é necessária. No lado do servidor, pode ser um fd de escuta e, no lado do cliente, pode ser usado como um fd de comunicação.
int _sock;
função de membro
- Soquete
é igual a udp. O soquete precisa ser criado primeiro.
void Socket()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
logMessage(Error, "create socket error id:%d info:%s", errno, strerror(errno));
exit(SOCKET_ERR);
}
}
- Bind
é o mesmo que udp. O soquete precisa do número da porta ip + correspondente ao bind.
void Bind(const uint16_t port)
{
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_sock, (sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(Error, "Bind error id:%d info:%s", errno, strerror(errno));
exit(BIND_ERR);
}
logMessage(Info, "Bind success");
}
- listen
define o soquete TCP para o estado de escuta para que possa receber solicitações de conexão de clientes. gbacklog é o número máximo de solicitações de conexão aguardando aceitação, não o número máximo de conexões. De modo geral, o comprimento da fila é gbacklog+1.
void Listen()
{
if (listen(_sock, gbacklog) < 0)
{
logMessage(Error, "Listen error id:%d info:%s", errno, strerror(errno));
exit(LISTEN_ERR);
}
logMessage(Info, "Bind success");
}
- aceitar
é usado para aceitar a solicitação de conexão do cliente e criar um novo soquete para se comunicar com o cliente
// 将客户端信息存起来
int Accept(std::string *clientip, uint16_t *clientport)
{
sockaddr_in temp;
socklen_t len = sizeof(temp);
int sock = accept(_sock, (sockaddr *)&temp, &len);
if (sock < 0)
{
logMessage(Error, "Accept error id:%d info:%s", errno, strerror(errno));
}
else
{
*clientip = inet_ntoa(temp.sin_addr);
*clientport = ntohs(temp.sin_port);
}
logMessage(Info, "accept success");
return sock;
}
- O cliente Connect
inicia uma solicitação de conexão com o servidor
int Connect(const std::string serverip,const uint16_t& serverport)
{
sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());
return connect(_sock,(sockaddr*)&server,sizeof(server));
}
- Fd、Fechar
int Fd()
{
return _sock;
}
void Close()
{
close(_sock);
}
Conclusão
Neste ponto, concluímos o encapsulamento Sock do Tcp.