1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 15:28:11 +00:00

LibGUI: Add GTCPSocket and base class GSocket (inherits from GIODevice.)

And use these to do the line-by-line reading automagically instead of having
that logic in IRCClient. This will definitely come in handy.
This commit is contained in:
Andreas Kling 2019-03-18 14:09:58 +01:00
parent d466f2634d
commit 8e3d0a23d5
12 changed files with 267 additions and 86 deletions

View file

@ -26,6 +26,7 @@ IRCClient::IRCClient(const String& address, int port)
, m_nickname("anon")
, m_log(IRCLogBuffer::create())
{
m_socket = new GTCPSocket(this);
m_client_window_list_model = new IRCWindowListModel(*this);
}
@ -35,37 +36,15 @@ IRCClient::~IRCClient()
bool IRCClient::connect()
{
if (m_socket_fd != -1) {
if (m_socket->is_connected())
ASSERT_NOT_REACHED();
}
m_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket_fd < 0) {
perror("socket");
exit(1);
}
IPv4Address ipv4_address(127, 0, 0, 1);
bool success = m_socket->connect(GSocketAddress(ipv4_address), m_port);
if (!success)
return false;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(m_port);
int rc = inet_pton(AF_INET, m_hostname.characters(), &addr.sin_addr);
if (rc < 0) {
perror("inet_pton");
exit(1);
}
printf("Connecting to %s...", m_hostname.characters());
fflush(stdout);
rc = ::connect(m_socket_fd, (struct sockaddr*)&addr, sizeof(addr));
if (rc < 0) {
perror("connect");
exit(1);
}
printf("ok!\n");
m_notifier = make<GNotifier>(m_socket_fd, GNotifier::Read);
m_notifier = make<GNotifier>(m_socket->fd(), GNotifier::Read);
m_notifier->on_ready_to_read = [this] (GNotifier&) { receive_from_server(); };
send_user();
@ -78,35 +57,20 @@ bool IRCClient::connect()
void IRCClient::receive_from_server()
{
char buffer[4096];
int nread = recv(m_socket_fd, buffer, sizeof(buffer) - 1, 0);
if (nread < 0) {
perror("recv");
exit(1);
}
if (nread == 0) {
printf("IRCClient: Connection closed!\n");
exit(1);
}
buffer[nread] = '\0';
#if 0
printf("Received: '%s'\n", buffer);
#endif
for (int i = 0; i < nread; ++i) {
char ch = buffer[i];
if (ch == '\r')
continue;
if (ch == '\n') {
process_line();
m_line_buffer.clear_with_capacity();
continue;
while (m_socket->can_read_line()) {
auto line = m_socket->read_line(4096);
if (line.is_null()) {
if (!m_socket->is_connected()) {
printf("IRCClient: Connection closed!\n");
exit(1);
}
ASSERT_NOT_REACHED();
}
m_line_buffer.append(ch);
process_line(move(line));
}
}
void IRCClient::process_line()
void IRCClient::process_line(ByteBuffer&& line)
{
Message msg;
Vector<char> prefix;
@ -121,7 +85,12 @@ void IRCClient::process_line()
InTrailingParameter,
} state = Start;
for (char ch : m_line_buffer) {
for (int i = 0; i < line.size(); ++i) {
char ch = line[i];
if (ch == '\r')
continue;
if (ch == '\n')
break;
switch (state) {
case Start:
if (ch == ':') {
@ -175,8 +144,7 @@ void IRCClient::process_line()
void IRCClient::send(const String& text)
{
int rc = ::send(m_socket_fd, text.characters(), text.length(), 0);
if (rc < 0) {
if (!m_socket->send(ByteBuffer::wrap((void*)text.characters(), text.length()))) {
perror("send");
exit(1);
}

View file

@ -4,6 +4,7 @@
#include <AK/HashMap.h>
#include <AK/CircularQueue.h>
#include <AK/Function.h>
#include <LibGUI/GTCPSocket.h>
#include "IRCLogBuffer.h"
#include "IRCWindow.h"
@ -12,12 +13,12 @@ class IRCQuery;
class IRCWindowListModel;
class GNotifier;
class IRCClient {
class IRCClient final : public GObject {
friend class IRCChannel;
friend class IRCQuery;
public:
IRCClient(const String& address, int port = 6667);
~IRCClient();
virtual ~IRCClient() override;
bool connect();
@ -59,6 +60,8 @@ public:
IRCQuery& ensure_query(const String& name);
IRCChannel& ensure_channel(const String& name);
const char* class_name() const override { return "IRCClient"; }
private:
struct Message {
String prefix;
@ -72,7 +75,7 @@ private:
void send_nick();
void send_pong(const String& server);
void send_privmsg(const String& target, const String&);
void process_line();
void process_line(ByteBuffer&&);
void handle_join(const Message&);
void handle_part(const Message&);
void handle_ping(const Message&);
@ -84,8 +87,9 @@ private:
void handle_user_command(const String&);
String m_hostname;
int m_port { 0 };
int m_socket_fd { -1 };
int m_port { 6667 };
GTCPSocket* m_socket { nullptr };
String m_nickname;
Vector<char> m_line_buffer;

View file

@ -4,7 +4,6 @@
#include <AK/Assertions.h>
#include <AK/Types.h>
#include <Kernel/NetworkOrdered.h>
#include <Kernel/StdLib.h>
enum class IPv4Protocol : word {
ICMP = 1,
@ -19,7 +18,10 @@ public:
IPv4Address() { }
IPv4Address(const byte data[4])
{
memcpy(m_data, data, 4);
m_data[0] = data[0];
m_data[1] = data[1];
m_data[2] = data[2];
m_data[3] = data[3];
}
IPv4Address(byte a, byte b, byte c, byte d)
{

View file

@ -40,17 +40,3 @@ bool GFile::open(GIODevice::OpenMode mode)
set_mode(mode);
return true;
}
bool GFile::close()
{
if (fd() < 0 || mode() == NotOpen)
return false;
int rc = ::close(fd());
if (rc < 0) {
set_error(rc);
return false;
}
set_fd(-1);
set_mode(GIODevice::NotOpen);
return true;
}

View file

@ -13,7 +13,6 @@ public:
void set_filename(const String& filename) { m_filename = filename; }
virtual bool open(GIODevice::OpenMode) override;
virtual bool close() override;
virtual const char* class_name() const override { return "GFile"; }

View file

@ -1,5 +1,7 @@
#include <LibGUI/GIODevice.h>
#include <unistd.h>
#include <sys/select.h>
#include <stdio.h>
GIODevice::GIODevice(GObject* parent)
: GObject(parent)
@ -44,26 +46,63 @@ ByteBuffer GIODevice::read(int max_size)
return buffer;
}
bool GIODevice::can_read() const
{
// FIXME: Can we somehow remove this once GSocket is implemented using non-blocking sockets?
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(m_fd, &rfds);
struct timeval timeout { 0, 0 };
int rc = select(m_fd + 1, &rfds, nullptr, nullptr, &timeout);
if (rc < 0) {
// NOTE: We don't set m_error here.
perror("GIODevice::can_read: select");
return false;
}
return FD_ISSET(m_fd, &rfds);
}
bool GIODevice::can_read_line()
{
if (m_eof && !m_buffered_data.is_empty())
return true;
if (m_buffered_data.contains_slow('\n'))
return true;
if (!can_read())
return false;
populate_read_buffer();
return m_buffered_data.contains_slow('\n');
}
ByteBuffer GIODevice::read_line(int max_size)
{
if (m_fd < 0)
return { };
if (!max_size)
return { };
auto line = ByteBuffer::create_uninitialized(max_size);
int line_index = 0;
while (line_index < line.size()) {
if (line_index >= m_buffered_data.size()) {
if (!populate_read_buffer())
return { };
if (!can_read_line())
return { };
if (m_eof) {
if (m_buffered_data.size() > max_size) {
dbgprintf("GIODevice::read_line: At EOF but there's more than max_size(%d) buffered\n", max_size);
return { };
}
auto buffer = ByteBuffer::copy(m_buffered_data.data(), m_buffered_data.size());
m_buffered_data.clear();
return buffer;
}
auto line = ByteBuffer::create_uninitialized(max_size + 1);
int line_index = 0;
while (line_index < max_size) {
byte ch = m_buffered_data[line_index];
line[line_index++] = ch;
if (ch == '\n') {
Vector<byte> new_buffered_data;
new_buffered_data.append(m_buffered_data.data() + line_index, m_buffered_data.size() - line_index);
m_buffered_data = move(new_buffered_data);
line.trim(line_index);
line[line_index] = '\0';
line.trim(line_index + 1);
dbgprintf("GIODevice::read_line: '%s'\n", line.pointer());
return line;
}
}
@ -84,6 +123,21 @@ bool GIODevice::populate_read_buffer()
set_eof(true);
return false;
}
buffer.trim(nread);
m_buffered_data.append(buffer.pointer(), buffer.size());
return true;
}
bool GIODevice::close()
{
if (fd() < 0 || mode() == NotOpen)
return false;
int rc = ::close(fd());
if (rc < 0) {
set_error(rc);
return false;
}
set_fd(-1);
set_mode(GIODevice::NotOpen);
return true;
}

View file

@ -29,8 +29,13 @@ public:
ByteBuffer read(int max_size);
ByteBuffer read_line(int max_size);
// FIXME: I would like this to be const but currently it needs to call populate_read_buffer().
bool can_read_line();
bool can_read() const;
virtual bool open(GIODevice::OpenMode) = 0;
virtual bool close() = 0;
virtual bool close();
virtual const char* class_name() const override { return "GIODevice"; }

64
LibGUI/GSocket.cpp Normal file
View file

@ -0,0 +1,64 @@
#include <LibGUI/GSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
GSocket::GSocket(Type type, GObject* parent)
: GIODevice(parent)
, m_type(type)
{
}
GSocket::~GSocket()
{
}
bool GSocket::connect(const GSocketAddress& address, int port)
{
ASSERT(!is_connected());
ASSERT(address.type() == GSocketAddress::Type::IPv4);
ASSERT(port > 0 && port <= 65535);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
auto ipv4_address = address.ipv4_address();
memcpy(&addr.sin_addr.s_addr, &ipv4_address, sizeof(IPv4Address));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
printf("Connecting to %s...", address.to_string().characters());
fflush(stdout);
int rc = ::connect(fd(), (struct sockaddr*)&addr, sizeof(addr));
if (rc < 0) {
perror("connect");
exit(1);
}
printf("ok!\n");
m_destination_address = address;
m_destination_port = port;
m_connected = true;
return true;
}
ByteBuffer GSocket::receive(int max_size)
{
auto buffer = read(max_size);
if (eof()) {
dbgprintf("GSocket{%p}: Connection appears to have closed in receive().\n", this);
m_connected = false;
}
return buffer;
}
bool GSocket::send(const ByteBuffer& data)
{
int nsent = ::send(fd(), data.pointer(), data.size(), 0);
if (nsent < 0) {
set_error(nsent);
return false;
}
ASSERT(nsent == data.size());
return true;
}

68
LibGUI/GSocket.h Normal file
View file

@ -0,0 +1,68 @@
#pragma once
#include <LibGUI/GIODevice.h>
#include <AK/AKString.h>
#include <Kernel/IPv4.h>
class GSocketAddress {
public:
enum class Type { Invalid, IPv4, Local };
GSocketAddress() { }
GSocketAddress(const IPv4Address& address)
: m_type(Type::IPv4)
, m_ipv4_address(address)
{
}
Type type() const { return m_type; }
bool is_valid() const { return m_type != Type::Invalid; }
IPv4Address ipv4_address() const { return m_ipv4_address; }
String to_string() const
{
switch (m_type) {
case Type::IPv4: return m_ipv4_address.to_string();
default: return "[GSocketAddress]";
}
}
private:
Type m_type { Type::Invalid };
IPv4Address m_ipv4_address;
};
class GSocket : public GIODevice {
public:
enum class Type { Invalid, TCP, UDP };
virtual ~GSocket() override;
bool connect(const GSocketAddress&, int port);
ByteBuffer receive(int max_size);
bool send(const ByteBuffer&);
bool is_connected() const { return m_connected; }
GSocketAddress source_address() const { return m_source_address; }
int source_port() const { return m_source_port; }
GSocketAddress destination_address() const { return m_source_address; }
int destination_port() const { return m_destination_port; }
virtual const char* class_name() const override { return "GSocket"; }
protected:
GSocket(Type, GObject* parent);
GSocketAddress m_source_address;
GSocketAddress m_destination_address;
int m_source_port { -1 };
int m_destination_port { -1 };
bool m_connected { false };
private:
virtual bool open(GIODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
Type m_type { Type::Invalid };
};

19
LibGUI/GTCPSocket.cpp Normal file
View file

@ -0,0 +1,19 @@
#include <LibGUI/GTCPSocket.h>
#include <sys/socket.h>
GTCPSocket::GTCPSocket(GObject* parent)
: GSocket(GSocket::Type::TCP, parent)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
set_error(fd);
} else {
set_fd(fd);
set_mode(GIODevice::ReadWrite);
set_error(0);
}
}
GTCPSocket::~GTCPSocket()
{
}

10
LibGUI/GTCPSocket.h Normal file
View file

@ -0,0 +1,10 @@
#include <LibGUI/GSocket.h>
class GTCPSocket final : public GSocket {
public:
explicit GTCPSocket(GObject* parent);
virtual ~GTCPSocket() override;
private:
};

View file

@ -40,6 +40,8 @@ LIBGUI_OBJS = \
GStackWidget.o \
GEvent.o \
GScrollableWidget.o \
GSocket.o \
GTCPSocket.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)