Проблема
У меня возникла ошибка сегментации, когда я отправил поле ввода после создания класса интерфейса. Перед созданием класса я использовал несколько функций в своем файле main.cpp
(теперь это функции класса Interface). После долгих отладок я проследил проблему до Client::Send
, но не могу найти источник проблемы. В моем коде много ловушек ошибок, но ни один из них не срабатывает при запуске кода.
Соответствующие файлы
src/main.cpp
src/client.cpp
inc/client/client.h
Структура проекта
bin/
obj/
inc/
interface/
interface.h
server/
server.h
client/
client.h
lib/
src/
main.cpp
interface.cpp
server.cpp
client.cpp
Makefile
вкл/интерфейс/interface.h
#ifndef INTERFACE_H_
#define INTERFACE_H_
#include <thread>
#include <ncurses.h>
class Client;
class Server;
class Interface {
public:
Interface(Client *client) : client_(client) {
initscr();
cbreak();
echo();
output_ = newwin(LINES - 3, COLS, 0, 0);
input_ = newwin(3, COLS, LINES - 3, 0);
std::thread input_thread([this]() { this->Input(); });
input_thread.join();
}
~Interface() {
endwin();
}
void Draw();
void Input();
void Output(const char *);
private:
WINDOW *output_;
WINDOW *input_;
int output_lines_;
Client *client_;
};
#endif
вк/сервер/server.h
#ifndef SERVER_H_
#define SERVER_H_
#include <thread>
#include <vector>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
class Server {
public:
Server(const in_addr_t addr, const in_port_t port) {
sock_ = socket(AF_INET, SOCK_STREAM, 0);
addr_.sin_family = AF_INET;
addr_.sin_addr.s_addr = addr;
addr_.sin_port = port;
bind(sock_, (const sockaddr *) &addr_, sizeof(addr_));
std::thread accept_thread([this]() { this->Accept(); });
accept_thread.detach();
}
~Server() {
close(sock_);
}
void Accept();
void Receive(const int) const;
void Send(const int, const char *) const;
int GetAddress() const;
int GetPort() const;
private:
int sock_;
sockaddr_in addr_;
std::vector<int> clients_;
};
#endif
вк/клиент/client.h
#ifndef CLIENT_H_
#define CLIENT_H_
#include <thread>
#include <chrono>
#include <vector>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <ncurses.h>
class Interface;
class Client {
public:
Client(const in_addr_t addr, const in_port_t port, Interface *interface) : interface_(interface) {
sock_ = socket(AF_INET, SOCK_STREAM, 0);
if (sock_ == -1) {
std::cout << "Failed to initialize socket."<< "\n";
exit(-1);
}
addr_.sin_family = AF_INET;
addr_.sin_addr.s_addr = addr;
addr_.sin_port = port;
int attempt = 0;
if (connect(sock_, (sockaddr *) &addr_, sizeof(addr_))== -1) {
std::cerr << "Failed to connect to server." << "\n";
exit(-1);
}
std::thread receive_thread(std::thread([this]() { this->Receive(); }));
receive_thread.detach();
}
~Client() {
std::cout << "Client deleted." << "\n";
close(sock_);
}
void Receive();
void Send(const char *) const;
private:
int sock_;
sockaddr_in addr_;
Interface *interface_;
};
#endif
источник/main.cpp
#include <thread>
#include <chrono>
#include <iostream>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ncurses.h>
#include "interface/interface.h"
#include "server/server.h"
#include "client/client.h"
void Create(in_addr_t addr, in_port_t port, Server*& server, Client*& client, Interface *interface) {
server = new Server(addr, port);
std::this_thread::sleep_for(std::chrono::seconds(1));
client = new Client(addr, port, interface);
}
void Join(in_addr_t addr, in_port_t port, Client*& client, Interface *interface) {
client = new Client(addr, port, interface);
}
int main(int argc, char **argv) {
const in_addr_t kAddr = inet_addr("127.0.0.1");
const in_port_t kPort = htons(8080);
Server *server = nullptr;
Client *client = nullptr;
Interface *interface = new Interface(client);
Create(kAddr, kPort, server, client, interface);
return 0;
}
источник/interface.cpp
#include <ncurses.h>
#include "client/client.h"
#include "interface/interface.h"
void Interface::Draw() {
wrefresh(input_);
werase(input_);
box(input_, 0, 0);
}
void Interface::Input() {
while (true) {
Draw();
char message[1024];
wgetstr(input_, message);
client_->Send(message); // I do believe I've traced the error to here.
}
}
void Interface::Output(const char *message) {
box(output_, 0, 0);
wmove(output_, output_lines_, 1);
wprintw(output_, message);
wrefresh(output_);
wmove(input_, 1, 1);
wrefresh(input_);
output_lines_++;
}
источник/server.cpp|
#include <thread>
#include <unordered_map>
#include <cstring>
#include <sys/socket.h>
#include "server/server.h"
void Server::Accept() {
while(true) {
listen(sock_, 5);
int client_sock = accept(sock_, nullptr, nullptr);
clients_.push_back(client_sock);
std::thread receive_thread([this, client_sock]() { this->Receive(client_sock); });
receive_thread.detach();
}
}
void Server::Receive(const int client_sock) const {
while (true) {
char buffer[1024] = {0};
int received = recv(client_sock, buffer, sizeof(buffer), 0);
if (received <= 0) {
break;
}
for (int client : clients_) {
Send(client, buffer);
}
}
}
void Server::Send(const int client_sock, const char *message) const {
send(client_sock, message, strlen(message), 0);
}
int Server::GetAddress() const {
return addr_.sin_addr.s_addr;
}
int Server::GetPort() const {
return addr_.sin_port;
}
источник/client.cpp
#include <cstring>
#include <iostream>
#include <sys/socket.h>
#include "interface/interface.h"
#include "client/client.h"
void Client::Receive() {
while (true) {
char buffer[1024] = {0};
int received = recv(sock_, buffer, sizeof(buffer), 0);
if (received <= 0) {
break;
}
buffer[received] = '\0';
if (interface_ != nullptr) {
interface_->Output(buffer);
} else {
std::cerr << "Interface is `NULL`." << "\n";
exit(-1);
}
}
}
void Client::Send(const char *message) const {
std::cout << "Message sent." << "\n";
if (send(sock_, message, strlen(message), 0) == -1) {
std::cerr << "Message failed to send." << "\n";
exit(-1);
}
}
Makefile
CC := g++
LIB := -lncurses
INC := -Iinc
SRC := $(wildcard src/*.cpp)
OBJ := $(patsubst src/%.cpp, obj/%.o, $(SRC))
BIN := bin/main
.PHONY: all clean
all: $(BIN)
$(BIN): $(OBJ)
$(CC) $^ $(INC) $(LIB) -o $@
obj/%.o: src/%.cpp
$(CC) -c $< $(INC) -o $@
clean:
rm -rf $(OBJ) $(BIN)
Я попытался поставить точки останова в функции Client::Send
и функции Client::~Client
, чтобы увидеть, удаляется ли клиент до того, как интерфейс сможет отправить сообщение, но это не тот случай. Я проверил переменные, связанные с сокетами и классами, чтобы увидеть, есть ли среди них NULL
или -1
, но это тоже было не так.
В будущем не пишите большие куски кода и уж точно не целые программы без какого-либо тестирования. В основном начните с пустой main
функции, создайте код с большим количеством дополнительных предупреждений, включенных и рассматриваемых как ошибки (я рекомендую как минимум -Wall -Wextra -Werror
). Когда код будет построен правильно, протестируйте программу всеми возможными способами. Только когда все работает, вы продолжаете добавлять совсем небольшой кусочек кода, возможно, даже одну-две строчки. Так станет намного проще находить ошибки, отлаживать и исправлять их.
Мало или вообще ничего общего с классом , ой или системой. Не отмечайте без разбора.
Я здесь новичок ~ следует ли мне воздерживаться от использования больших кусков кода, даже если я считаю, что это тоже уместно? Например, в моем примере ошибка сегментации на самом деле имела мало общего с функцией Client::Send(char const *)
, хотя мой отладчик сообщил, что она вызвала ее.
Вероятная причина вашей проблемы - эти три строки:
Client *client = nullptr;
Interface *interface = new Interface(client);
Create(kAddr, kPort, server, client, interface);
Проблема в том, что конструктор Interface
копирует указатель, который вы ему передаете. Это означает, что interface->client_
будет нулевым указателем, независимо от того, что делает функция Create
.
Но поскольку объект Interfaceconstructor depends on a
Clientobject, and the
Clientobject also depends on the
Interface существует, у вас возникает невозможный цикл зависимостей, который необходимо исправить.
Возможно, вы захотите переосмыслить свой дизайн, чтобы решить проблему зависимостей, а также проблему указателя и сбоя (моя рекомендация). Или измените свой код на какую-то двухфазную конструкцию, где объект Interface
может быть создан без объекта Client
, и вы инициализируете объект Interface
после создания объекта Client
.
Гораздо более общий и лучший способ решения проблем с указателями — вообще не использовать указатели.
В вашем коде я не вижу никакой необходимости в том, чтобы server
, client
или interface
были указателями и создавались с помощью new
.
Вы запускали это в отладчике, чтобы точно узнать, где возникает ошибка сегментации?