Last active
February 28, 2026 06:38
-
-
Save vlaleli/ef0ad9442cdd702faf2ac26b03dcdbbc to your computer and use it in GitHub Desktop.
socket server/client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <arpa/inet.h> | |
| #include <netinet/in.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #include <iostream> | |
| #include <string> | |
| #include <thread> | |
| static const int SERVER_PORT = 5555; | |
| static void ReaderThread(int fd) { | |
| char buf[2048]; | |
| while (true) { | |
| ssize_t n = recv(fd, buf, sizeof(buf) - 1, 0); | |
| if (n > 0) { | |
| buf[n] = 0; | |
| std::cout << buf; | |
| std::cout.flush(); | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| int main() { | |
| int fd = socket(AF_INET, SOCK_STREAM, 0); | |
| if (fd < 0) { | |
| std::cerr << "socket failed\n"; | |
| return 1; | |
| } | |
| sockaddr_in addr{}; | |
| addr.sin_family = AF_INET; | |
| addr.sin_port = htons(SERVER_PORT); | |
| inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr); | |
| if (connect(fd, (sockaddr*)&addr, sizeof(addr)) < 0) { | |
| std::cerr << "connect failed\n"; | |
| close(fd); | |
| return 1; | |
| } | |
| std::thread t(ReaderThread, fd); | |
| std::cout << "TIME\nDATE\nWEATHER Odesa\nEUR UAH\nBTC USD\n"; | |
| std::string line; | |
| while (std::getline(std::cin, line)) { | |
| line += "\n"; | |
| send(fd, line.c_str(), line.size(), 0); | |
| } | |
| shutdown(fd, SHUT_RDWR); | |
| close(fd); | |
| t.join(); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <arpa/inet.h> | |
| #include <fcntl.h> | |
| #include <netinet/in.h> | |
| #include <sys/select.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #include <signal.h> | |
| #include <cctype> | |
| #include <cstring> | |
| #include <ctime> | |
| #include <iomanip> | |
| #include <iostream> | |
| #include <mutex> | |
| #include <sstream> | |
| #include <string> | |
| #include <thread> | |
| #include <vector> | |
| #include <curl/curl.h> | |
| static const int SERVER_PORT = 5555; | |
| struct ClientState { | |
| int fd; | |
| std::string inbuf; | |
| std::mutex send_mtx; | |
| }; | |
| static size_t CurlWriteCb(void* contents, size_t size, size_t nmemb, void* userp) { | |
| size_t total = size * nmemb; | |
| std::string* s = (std::string*)userp; | |
| s->append((const char*)contents, total); | |
| return total; | |
| } | |
| static bool HttpGet(const std::string& url, std::string& out_body) { | |
| CURL* curl = curl_easy_init(); | |
| if (!curl) return false; | |
| out_body.clear(); | |
| curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); | |
| curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); | |
| curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCb); | |
| curl_easy_setopt(curl, CURLOPT_WRITEDATA, &out_body); | |
| curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); | |
| CURLcode res = curl_easy_perform(curl); | |
| long code = 0; | |
| curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); | |
| curl_easy_cleanup(curl); | |
| return (res == CURLE_OK && code >= 200 && code < 300); | |
| } | |
| static bool JsonFindNumber(const std::string& json, const std::string& key, double& out) { | |
| std::string pat = "\"" + key + "\":"; | |
| size_t p = json.find(pat); | |
| if (p == std::string::npos) return false; | |
| p += pat.size(); | |
| while (p < json.size() && (json[p] == ' ' || json[p] == '\n' || json[p] == '\r' || json[p] == '\t')) p++; | |
| size_t e = p; | |
| while (e < json.size() && (std::isdigit((unsigned char)json[e]) || json[e] == '-' || json[e] == '.' || json[e] == 'e' || json[e] == 'E' || json[e] == '+')) e++; | |
| if (e == p) return false; | |
| out = std::stod(json.substr(p, e - p)); | |
| return true; | |
| } | |
| static void SendLine(ClientState* c, const std::string& line) { | |
| std::lock_guard<std::mutex> lk(c->send_mtx); | |
| std::string msg = line + "\n"; | |
| const char* buf = msg.c_str(); | |
| size_t left = msg.size(); | |
| while (left > 0) { | |
| ssize_t n = send(c->fd, buf, left, 0); | |
| if (n > 0) { | |
| buf += n; | |
| left -= (size_t)n; | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| static std::string NowTimeStr() { | |
| std::time_t t = std::time(nullptr); | |
| std::tm tm{}; | |
| localtime_r(&t, &tm); | |
| std::ostringstream oss; | |
| oss << std::put_time(&tm, "%H:%M:%S"); | |
| return oss.str(); | |
| } | |
| static std::string NowDateStr() { | |
| std::time_t t = std::time(nullptr); | |
| std::tm tm{}; | |
| localtime_r(&t, &tm); | |
| std::ostringstream oss; | |
| oss << std::put_time(&tm, "%Y-%m-%d"); | |
| return oss.str(); | |
| } | |
| static void HandleTIME(ClientState* c) { | |
| SendLine(c, "OK TIME " + NowTimeStr()); | |
| } | |
| static void HandleDATE(ClientState* c) { | |
| SendLine(c, "OK DATE " + NowDateStr()); | |
| } | |
| static void HandleEUR(ClientState* c, const std::string& to) { | |
| std::string url = "https://api.frankfurter.dev/v1/latest?base=EUR&symbols=" + to; | |
| std::string body; | |
| if (!HttpGet(url, body)) { SendLine(c, "ERR EUR fetch_failed"); return; } | |
| double rate = 0.0; | |
| if (!JsonFindNumber(body, to, rate)) { SendLine(c, "ERR EUR parse_failed"); return; } | |
| SendLine(c, "OK EUR " + to + " " + std::to_string(rate)); | |
| } | |
| static void HandleBTC(ClientState* c, std::string to) { | |
| for (auto& ch : to) ch = (char)std::tolower((unsigned char)ch); | |
| std::string url = "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=" + to; | |
| std::string body; | |
| if (!HttpGet(url, body)) { SendLine(c, "ERR BTC fetch_failed"); return; } | |
| double price = 0.0; | |
| if (!JsonFindNumber(body, to, price)) { SendLine(c, "ERR BTC parse_failed"); return; } | |
| SendLine(c, "OK BTC " + to + " " + std::to_string(price)); | |
| } | |
| static void HandleWEATHER(ClientState* c, std::string city) { | |
| std::string q = city; | |
| for (auto& ch : q) if (ch == ' ') ch = '+'; | |
| std::string geoUrl = "https://geocoding-api.open-meteo.com/v1/search?name=" + q + "&count=1&language=en&format=json"; | |
| std::string geoBody; | |
| if (!HttpGet(geoUrl, geoBody)) { SendLine(c, "ERR WEATHER geocode_failed"); return; } | |
| double lat = 0.0, lon = 0.0; | |
| if (!JsonFindNumber(geoBody, "latitude", lat) || !JsonFindNumber(geoBody, "longitude", lon)) { | |
| SendLine(c, "ERR WEATHER city_not_found"); | |
| return; | |
| } | |
| std::ostringstream wurl; | |
| wurl << "https://api.open-meteo.com/v1/forecast?latitude=" << lat << "&longitude=" << lon << "¤t_weather=true"; | |
| std::string wBody; | |
| if (!HttpGet(wurl.str(), wBody)) { SendLine(c, "ERR WEATHER forecast_failed"); return; } | |
| double temp = 0.0, wcode = 0.0; | |
| if (!JsonFindNumber(wBody, "temperature", temp) || !JsonFindNumber(wBody, "weathercode", wcode)) { | |
| SendLine(c, "ERR WEATHER parse_failed"); | |
| return; | |
| } | |
| std::ostringstream out; | |
| out << "OK WEATHER " << city << " " << temp << "C code=" << (int)wcode; | |
| SendLine(c, out.str()); | |
| } | |
| static void DispatchCommand(ClientState* c, const std::string& line) { | |
| std::istringstream iss(line); | |
| std::string cmd; | |
| iss >> cmd; | |
| if (cmd.empty()) return; | |
| if (cmd == "TIME") { std::thread(HandleTIME, c).detach(); return; } | |
| if (cmd == "DATE") { std::thread(HandleDATE, c).detach(); return; } | |
| if (cmd == "WEATHER") { | |
| std::string city; | |
| std::getline(iss, city); | |
| if (!city.empty() && city[0] == ' ') city.erase(0, 1); | |
| if (city.empty()) { SendLine(c, "ERR WEATHER need_city"); return; } | |
| std::thread(HandleWEATHER, c, city).detach(); | |
| return; | |
| } | |
| if (cmd == "EUR") { | |
| std::string to; | |
| iss >> to; | |
| if (to.empty()) { SendLine(c, "ERR EUR need_currency"); return; } | |
| std::thread(HandleEUR, c, to).detach(); | |
| return; | |
| } | |
| if (cmd == "BTC") { | |
| std::string to; | |
| iss >> to; | |
| if (to.empty()) { SendLine(c, "ERR BTC need_currency"); return; } | |
| std::thread(HandleBTC, c, to).detach(); | |
| return; | |
| } | |
| SendLine(c, "ERR unknown_command"); | |
| } | |
| static bool SetNonBlocking(int fd) { | |
| int flags = fcntl(fd, F_GETFL, 0); | |
| if (flags < 0) return false; | |
| return (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0); | |
| } | |
| int main() { | |
| signal(SIGPIPE, SIG_IGN); | |
| curl_global_init(CURL_GLOBAL_DEFAULT); | |
| int listen_fd = socket(AF_INET, SOCK_STREAM, 0); | |
| if (listen_fd < 0) { std::cerr << "socket failed\n"; return 1; } | |
| int opt = 1; | |
| setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| sockaddr_in addr{}; | |
| addr.sin_family = AF_INET; | |
| addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
| addr.sin_port = htons(SERVER_PORT); | |
| if (bind(listen_fd, (sockaddr*)&addr, sizeof(addr)) < 0) { close(listen_fd); return 1; } | |
| if (listen(listen_fd, 16) < 0) { close(listen_fd); return 1; } | |
| SetNonBlocking(listen_fd); | |
| std::vector<ClientState*> clients; | |
| std::cout << "Server started on port " << SERVER_PORT << "\n"; | |
| while (true) { | |
| fd_set rfds; | |
| FD_ZERO(&rfds); | |
| FD_SET(listen_fd, &rfds); | |
| int maxfd = listen_fd; | |
| for (auto* c : clients) { | |
| FD_SET(c->fd, &rfds); | |
| if (c->fd > maxfd) maxfd = c->fd; | |
| } | |
| timeval tv{}; | |
| tv.tv_sec = 1; | |
| tv.tv_usec = 0; | |
| int ready = select(maxfd + 1, &rfds, nullptr, nullptr, &tv); | |
| if (ready < 0) continue; | |
| if (FD_ISSET(listen_fd, &rfds)) { | |
| sockaddr_in cli{}; | |
| socklen_t len = sizeof(cli); | |
| int cfd = accept(listen_fd, (sockaddr*)&cli, &len); | |
| if (cfd >= 0) { | |
| SetNonBlocking(cfd); | |
| auto* cs = new ClientState{cfd, "", {}}; | |
| clients.push_back(cs); | |
| SendLine(cs, "OK connected"); | |
| } | |
| } | |
| for (size_t i = 0; i < clients.size();) { | |
| ClientState* c = clients[i]; | |
| bool remove = false; | |
| if (FD_ISSET(c->fd, &rfds)) { | |
| char buf[2048]; | |
| while (true) { | |
| ssize_t n = recv(c->fd, buf, sizeof(buf), 0); | |
| if (n > 0) { | |
| c->inbuf.append(buf, (size_t)n); | |
| while (true) { | |
| size_t pos = c->inbuf.find('\n'); | |
| if (pos == std::string::npos) break; | |
| std::string line = c->inbuf.substr(0, pos); | |
| c->inbuf.erase(0, pos + 1); | |
| if (!line.empty() && line.back() == '\r') line.pop_back(); | |
| DispatchCommand(c, line); | |
| } | |
| } else if (n == 0) { | |
| remove = true; | |
| break; | |
| } else { | |
| if (errno == EWOULDBLOCK || errno == EAGAIN) break; | |
| remove = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (remove) { | |
| close(c->fd); | |
| delete c; | |
| clients.erase(clients.begin() + (long)i); | |
| } else { | |
| i++; | |
| } | |
| } | |
| } | |
| curl_global_cleanup(); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment