1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 23:37:35 +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

@ -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)