Skip to content

Instantly share code, notes, and snippets.

@pgorczak
Last active June 8, 2016 19:39
Show Gist options
  • Save pgorczak/b134bfe1cf308b64891da7e967e0ad6a to your computer and use it in GitHub Desktop.
Save pgorczak/b134bfe1cf308b64891da7e967e0ad6a to your computer and use it in GitHub Desktop.
Text based socket connection, using a UNIX socket in C++. Looks up hostname and port, checks if it can connect, sets timeouts. Corner cases of data spanning across socket reads should be covered.
#ifndef CONNECTION_H
#define CONNECTION_H
#include <netdb.h>
#include <stdexcept>
#include <string>
#include <sstream>
#include <sys/socket.h>
#include <deque>
#define BUFSIZE 128
class Connection {
public:
Connection(std::string host, int port) : delimiter_('\n') {
// Resolve host
addrinfo* matching_ai;
if(getaddrinfo(host.c_str(), std::to_string(port).c_str(), NULL, &matching_ai) != 0) {
throw std::runtime_error("Could not resolve address "+host+":"+std::to_string(port));
}
while(matching_ai->ai_family != AF_INET && matching_ai->ai_socktype != SOCK_STREAM) {
matching_ai = matching_ai->ai_next;
if(matching_ai == NULL) {
throw std::runtime_error("Can not connect to "+host+":"+std::to_string(port)+" using TCP over IPv4.");
}
}
// Create socket
socket_fd_ = socket(matching_ai->ai_family, matching_ai->ai_socktype, matching_ai->ai_protocol);
if(socket_fd_ == -1) {
throw std::runtime_error("Could not open socket.");
}
// Connect
if(connect(socket_fd_, matching_ai->ai_addr, matching_ai->ai_addrlen) == -1) {
throw std::runtime_error("Could not connect socket."+std::to_string(errno));
}
// Set timeout
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 500000; // 0.5 seconds
setSocketOption(SO_RCVTIMEO, timeout);
setSocketOption(SO_SNDTIMEO, timeout);
}
~Connection() {
close(socket_fd_);
}
template<class T>
void setSocketOption(int option, T value) {
int result = setsockopt(socket_fd_, SOL_SOCKET, option, (const void *)&value , sizeof(value));
if(result == -1) {
throw std::runtime_error("Error during setsockopt.");
}
}
void send(std::string buffer) {
if(buffer.back() != '\n') {
buffer += '\n';
}
ssize_t sent = ::send(socket_fd_, buffer.c_str(), buffer.size(), 0);
if(sent == -1) {
throw std::runtime_error("Send error.");
}
}
std::string recv()
{
if(!messages_.empty()) {
std::string msg(messages_.front());
messages_.pop_front();
return msg;
} else {
memset(buffer_, 0, BUFSIZE);
ssize_t received = ::recv(socket_fd_, buffer_, BUFSIZE, 0);
if(received == -1) {
throw std::runtime_error("Receive error.");
}
std::stringstream lines(buffer_);
for(std::string msg; getline(lines, msg, delimiter_); messages_.push_back(msg));
if(!last_stub_.empty()) {
std::string next_msg(messages_.front());
messages_.pop_front();
next_msg = last_stub_+next_msg;
messages_.push_front(next_msg);
}
last_stub_.clear();
// A final delimiter is not parsed as a line
// check if the buffer ends on a delimiter
if(buffer_[received - 1] != delimiter_) {
last_stub_ = messages_.back();
messages_.pop_back();
}
return recv();
}
}
private:
int socket_fd_;
char buffer_[BUFSIZE];
char delimiter_;
std::deque<std::string>messages_;
std::string last_stub_;
};
#endif // CONNECTION_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment