Zappy GUI
C++ graphic client: renderer, camera, network
Loading...
Searching...
No Matches
TcpSocket.cpp
Go to the documentation of this file.
1#include "TcpSocket.hpp"
2
3#include <arpa/inet.h>
4#include <fcntl.h>
5#include <netdb.h>
6#include <sys/socket.h>
7#include <unistd.h>
8
9#include <cstring>
10
12{
13 if (_fd != -1) close(_fd);
14}
15
16void TcpSocket::connect(const std::string& host, int port)
17{
18 // Create socket
19 _fd = socket(AF_INET, SOCK_STREAM, 0);
20 if (_fd == -1) {
21 throw TcpException("Failed to create socket: " + std::string(strerror(errno)));
22 }
23
24 // Set non-blocking mode
25 int flags = fcntl(_fd, F_GETFL, 0);
26 if (flags == -1 || fcntl(_fd, F_SETFL, flags | O_NONBLOCK) == -1) {
27 close(_fd);
28 _fd = -1;
29 throw TcpException("Failed to set non-blocking mode: " + std::string(strerror(errno)));
30 }
31
32 // Resolve hostname
33 struct addrinfo hints = {};
34 struct addrinfo* result = nullptr;
35
36 hints.ai_family = AF_INET;
37 hints.ai_socktype = SOCK_STREAM;
38
39 std::string portStr = std::to_string(port);
40 int err = getaddrinfo(host.c_str(), portStr.c_str(), &hints, &result);
41 if (err != 0) {
42 close(_fd);
43 _fd = -1;
44 throw TcpException("Failed to resolve host: " + std::string(gai_strerror(err)));
45 }
46
47 // Try to connect (non-blocking will return immediately)
48 int connectResult = ::connect(_fd, result->ai_addr, result->ai_addrlen);
49 freeaddrinfo(result);
50
51 if (connectResult == -1 && errno != EINPROGRESS) {
52 close(_fd);
53 _fd = -1;
54 throw TcpException("Failed to connect: " + std::string(strerror(errno)));
55 }
56
57 // Wait for connection to complete (with timeout)
58 fd_set writefds;
59 FD_ZERO(&writefds);
60 FD_SET(_fd, &writefds);
61
62 struct timeval timeout;
63 timeout.tv_sec = 5; // 5 second timeout
64 timeout.tv_usec = 0;
65
66 int selectResult = select(_fd + 1, nullptr, &writefds, nullptr, &timeout);
67 if (selectResult <= 0) {
68 close(_fd);
69 _fd = -1;
70 throw TcpException(selectResult == 0 ? "Connection timeout" : "Connection failed");
71 }
72
73 // Check if connection succeeded
74 int error = 0;
75 socklen_t len = sizeof(error);
76 if (getsockopt(_fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1 || error != 0) {
77 close(_fd);
78 _fd = -1;
79 throw TcpException("Connection failed: " + std::string(strerror(error)));
80 }
81}
82
83void TcpSocket::send(const std::string& data)
84{
85 if (_fd == -1) {
86 throw TcpException("Socket not connected");
87 }
88
89 ssize_t sent = ::send(_fd, data.c_str(), data.size(), 0);
90 if (sent == -1) {
91 throw TcpException("Failed to send data");
92 }
93}
94
95std::optional<std::string> TcpSocket::recvLine()
96{
97 if (_fd == -1) {
98 throw TcpException("Socket not connected");
99 }
100
101 // Check if we already have a complete line in the buffer
102 size_t newlinePos = _recvBuffer.find('\n');
103 if (newlinePos != std::string::npos) {
104 std::string line = _recvBuffer.substr(0, newlinePos + 1); // include \n
105 _recvBuffer.erase(0, newlinePos + 1);
106 return line;
107 }
108
109 // Try to read more data (non-blocking)
110 char buffer[4096];
111 ssize_t received = recv(_fd, buffer, sizeof(buffer), MSG_DONTWAIT);
112
113 if (received == -1) {
114 if (errno == EAGAIN || errno == EWOULDBLOCK) {
115 // No data available right now, not an error
116 return std::nullopt;
117 }
118 throw TcpException("Failed to receive data: " + std::string(strerror(errno)));
119 } else if (received == 0) {
120 // Connection closed
121 return std::nullopt;
122 }
123
124 // Append new data to buffer
125 _recvBuffer.append(buffer, received);
126
127 // Check again for complete line after appending
128 newlinePos = _recvBuffer.find('\n');
129 if (newlinePos != std::string::npos) {
130 std::string line = _recvBuffer.substr(0, newlinePos + 1); // include \n
131 _recvBuffer.erase(0, newlinePos + 1);
132 return line;
133 }
134
135 // Still no complete line
136 return std::nullopt;
137}
138
139bool TcpSocket::poll(int timeout_ms)
140{
141 if (_fd == -1) {
142 throw TcpException("Socket not connected");
143 }
144
145 // If we have buffered data with a newline, return true immediately
146 if (_recvBuffer.find('\n') != std::string::npos) {
147 return true;
148 }
149
150 fd_set readfds;
151 FD_ZERO(&readfds);
152 FD_SET(_fd, &readfds);
153
154 struct timeval timeout;
155 timeout.tv_sec = timeout_ms / 1000;
156 timeout.tv_usec = (timeout_ms % 1000) * 1000;
157
158 int result = select(_fd + 1, &readfds, nullptr, nullptr, &timeout);
159 if (result == -1) {
160 throw TcpException("Failed to poll socket: " + std::string(strerror(errno)));
161 }
162 return result > 0; // data is ready
163}
Exception class for TCP errors.
Definition TcpSocket.hpp:10
~TcpSocket()
Wraps close().
Definition TcpSocket.cpp:11
std::string _recvBuffer
Definition TcpSocket.hpp:61
std::optional< std::string > recvLine()
Receives a line of text from the socket.
Definition TcpSocket.cpp:95
void send(const std::string &data)
Sends data through the socket.
Definition TcpSocket.cpp:83
bool poll(int timeout_ms)
Polls the socket for incoming data.
void connect(const std::string &host, int port)
Connects to a remote host.
Definition TcpSocket.cpp:16