Здравсвуйте.
Совсем недавно начал серьезно программировать на C++ под Линукс. Ну в общем сначала обычное баловство и все такое. Потом решил подковаться в теории и около 3 месяцев штудировал Страуструпа и, дополнительно, книги по объктно-ориентированному программированию. Сейчас написал свой класс для создания сетевых соединений. То есть сокетов. Есть просьба к профессионалам — проверьте код. Нет, вы не поняли, он работает, но мне нужно знать все ли я правильно сделал с идеологической точки зрения и правильно ли понял эти книги. Поэтому и прошу. Там всего около 150 строк. Если кто-то согласиться — пишите в почту anton.ermolenko@gmail.com или в аську 455-922-455. В принципе могу код здесь выложить.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Так выкладывай.
Выкладываю. С указанием структуры. Есть каталог server. В нем лежит main.cpp. В каталоге server есть каталог include и в нем лежит socketClass.cpp — объявление класса Socket. В каталоге server есть также каталог src — в нем лежит socketClass.cpp — определение класса Socket. Для сборки этого в каталоге server лежит также файл Makefile. То есть структура такая:
server =>
include =>
socketClass.h
<=
src =>
socketClass.cpp
<=
main.cpp
Makefile
<=
Итак socketClass.h:
/*
******Socket******
Class, which contains IPv4 sockets
*/#ifndef SOCKET_H_
#define SOCKET_H_
#include <sys/socket.h>
#include <netinet/in.h>
#include <string>
class Socket
{
public:
Socket();
Socket(int);
Socket(int, int);
~Socket();
int createSocket();
int bind();
int listen();
void accept(Socket*);
void setPort(int port) {this->port = port;};
void setBacklogSize(int backlog) {this->backlog = backlog;};
void showIp();
int getSocketDesk();
std::string getIp();
private:
int socketDesk;
int port;
int backlog;
sockaddr_in *factAddr; // This is the fact addr
sockaddr *usingAddr; // This is the automatic link to soclkladdr_in addr
socklen_t factAddrLen;
std::string ip;
bool toggleMemory();
void setDefaults();
};
#endif/*SOCKET_H_*/
Файл main.cpp (который этот класс использует — создает сокет, ждест соединения, когда есть соединение, выводит ip адрес подсоеденившегося и отрубается)
вопрос первый.
почему везде бросается std::string ?
мало того, что этого будет мало кто ожидать при использовании Вашего кода, так еще и собственно информации для приложения никакой нет.
тоесть, я бы предложил — создать наследника std::exception, возможно с некоторым контекстом исключения, хранящем информацию о произошедшем.
далее, зачем постоянно ловить по значению ?
в случае std::string это запросто может привести к аллокации памяти, которая в свою очередь, может вызвать тот же std::bad_alloc, который Вы сами же ловите и сразу же создаете еще один объект std::string в одном из методов.
std::string Socket::getIp()
{
char ipaddr[INET_ADDRSTRLEN];
const char *ipAddr = inet_ntop(AF_INET, &(factAddr->sin_addr), ipaddr, sizeof(ipaddr));
std::string *result = new std::string(ipAddr); /* можно спросить, зачем так ? да еще и нет проверки на (NULL == ipAddr) */
ip = *result;
return ip; /* в этом месте, как Вы понимаете, никто за Вас result освобождать не будет, поэтому тут утечка памяти */
}
к тому же, функция не совсем логична. getIp() есть функция от factAddr, более она ни отчего не зависит. поэтому логичнее было бы формировать строку ip в момент изменения factAddr, это у Вас один только метод bind(). далее просто возвращать сформированную строку.
почему некоторые методы объявлены как 'int methodName()', а в теле идет 'return true /* false */ ;' ?
если кодов возврата всего 2, так и объявить соответственно, если же больше, то выделить их скажем в набор констант.
многие параметры, вроде INADDR_ANY, AF_INET, а так же волшебные цифры 8008 и 16 забиты прямо в коде.
это как минимум неудобно.
зачем память для 'sockaddr_in *factAddr;' выделять динамически ?
храните структуру как 'sockaddr_in factAddr;', не нужно будет следить за выделением\освобождением памяти.
не очень красиво, что класс завязан на std::cout.
перегрузить operator << (std::ostream&, const Socket&), или как то еще.
но метод void Socket::showIp() не нужен.
Здравствуйте, DiKeert, Вы писали:
DK> throw(std::string("Cannot allocate memory!")); DK> return false;
Вторая строка лишняя
DK>int Socket::bind() DK>{ DK> ... DK> factAddr->sin_addr.s_addr = htonl(INADDR_ANY);
Поддерживаю других ораторов: вместо INADDR_ANY нужно передавать параметр в функцию или добавить атрибут в класс
DK>void Socket::accept(Socket *parent) DK>{ DK> if((this->socketDesk = ::accept(parent->getSocketDesk(), usingAddr, &factAddrLen)) < 0) DK> { DK> throw(std::string("Cannot accept incoming connection!")); DK> } DK>}
Я бы сделал по-другому: метод accept должен возвращать новый объект который соотвествует принятому соединению.
В функции main это отразится так:
Здравствуйте, De Bug, Вы писали:
DK>>int Socket::bind() DK>>{ DK>> ... DK>> factAddr->sin_addr.s_addr = htonl(INADDR_ANY); DB>Поддерживаю других ораторов: вместо INADDR_ANY нужно передавать параметр в функцию или добавить атрибут в класс
Спасибо большое за критику. Все совету учту и применю.
Но вот с этим INADDR_ANY небольшая неясность.
Может кто не знает, но htonl — эта функция преобразует порядок байтов из обычного в сетевой. Тоже предопрелена в системном файле.
INADDR_ANY — оно предопределено в заголовочном файле с системным вызовом.
Здравствуйте, DiKeert, Вы писали:
DK>Здравствуйте, Roman Odaisky, Вы писали:
RO>>Так выкладывай.
DK>Выкладываю. С указанием структуры. Есть каталог server. В нем лежит main.cpp. В каталоге server есть каталог include и в нем лежит socketClass.cpp — объявление класса Socket. В каталоге server есть также каталог src — в нем лежит socketClass.cpp — определение класса Socket. Для сборки этого в каталоге server лежит также файл Makefile. То есть структура такая:
— не используется const
— не проверяются указатели на NULL (хотя бы с помощью assert)
— в методах, которые должны возвращать int, по факту возвращается bool
— левые исключения, ловятся по значению
— не все параметры методов в хедере имеют имена ( Socket(int) Socket(int, int) )
— зачем-то прикручен вывод в cout
Здравствуйте, DiKeert, Вы писали:
DK>А вот Makefile что бы это собрать. Для GNU Make ессною
Нет проверки зависимостей, хотя бы примитивной. При изменении заголовочного файла не произойдет пересборка зависящих от него объектников.
Затруднено добавление новых файлов к проекту — придется каждый раз править Makefile.
Нет возможности передать опции компилятору из командной строки, и вообще изменить компилятор.
Обязательно бахнем! И не раз. Весь мир в труху! Но потом. (ДМБ)
Здравствуйте, DiKeert, Вы писали:
DK> Socket(int);
У одноаргументных конструкторов в 90% случаев должен быть explicit. В данном случае эта ошибка компенсируется тем, что конструктор всё равно нигде не определен :-)
Здравствуйте, Roman Odaisky, Вы писали:
RO>У одноаргументных конструкторов в 90% случаев должен быть explicit. В данном случае эта ошибка компенсируется тем, что конструктор всё равно нигде не определен
=)))) Это конечно все очень здорово, но я не знаю что такое explicit.
Здравствуйте, DiKeert, Вы писали:
DK>Здравствуйте, Roman Odaisky, Вы писали:
RO>>У одноаргументных конструкторов в 90% случаев должен быть explicit. В данном случае эта ошибка компенсируется тем, что конструктор всё равно нигде не определен
DK>=)))) Это конечно все очень здорово, но я не знаю что такое explicit.
это служебное слово, используется при объявлении конструктора, запрещает использование конструктора для неявных преобразований таких как, например:
struct Test
{
Test() {};
Test(int) {};
};
Test t;
int i=0;
t=i;
Много чего было сказано , согласен со всеми .Попытаюсь вставить свои 5 копеек.
Надеюсь тоже поможет:
1. Следующии методы
int createSocket();
int bind();
int listen();
считаю необходимо перенести в закрытую часть класса. В твоей программе ты вызываешь их
из конструктора. То есть они вспомогательные. Если же ты оставляешь их в видимой части класса и даешь возможность
пользователю напрямую их вызывать — то нарушается принцип инкапсуляции. Ведь именно для этого ты создавал класс , чтобы скрыть низкоуровневые ньюансы имплементации сокета. А кроме того , что делать если вызвали listen до bind ит.д ит.п.
не вижу смысла. Если ты передаешь их в конструкторе и сокет уже создается. После того как сокет создан конструктором — эти методы не имеют смысла. Может идея такова , что создаешь сокет контруктором без аргументов , а потом все инициализируешь ? Но тогда снова сталкиваешься с проблемой порядка вызова низкоуровневых методов , о которых я говорил в части 1.
3. Методы :
void showIp();
int getSocketDesk();
std::string getIp();