mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 12:32:43 +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:
		
							parent
							
								
									d466f2634d
								
							
						
					
					
						commit
						8e3d0a23d5
					
				
					 12 changed files with 267 additions and 86 deletions
				
			
		|  | @ -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) { | ||||
|     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); | ||||
|             } | ||||
|     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; | ||||
|             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); | ||||
|     } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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) | ||||
|     { | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -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"; } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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()) | ||||
|     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; | ||||
| } | ||||
|  |  | |||
|  | @ -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
									
								
							
							
						
						
									
										64
									
								
								LibGUI/GSocket.cpp
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										68
									
								
								LibGUI/GSocket.h
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										19
									
								
								LibGUI/GTCPSocket.cpp
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										10
									
								
								LibGUI/GTCPSocket.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,10 @@ | |||
| #include <LibGUI/GSocket.h> | ||||
| 
 | ||||
| class GTCPSocket final : public GSocket { | ||||
| public: | ||||
|     explicit GTCPSocket(GObject* parent); | ||||
|     virtual ~GTCPSocket() override; | ||||
| 
 | ||||
| private: | ||||
| }; | ||||
| 
 | ||||
|  | @ -40,6 +40,8 @@ LIBGUI_OBJS = \ | |||
|     GStackWidget.o \
 | ||||
|     GEvent.o \
 | ||||
|     GScrollableWidget.o \
 | ||||
|     GSocket.o \
 | ||||
|     GTCPSocket.o \
 | ||||
|     GWindow.o | ||||
| 
 | ||||
| OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling