Skip to content

Instantly share code, notes, and snippets.

@iTrooz
Created October 20, 2022 13:04
Show Gist options
  • Save iTrooz/91591fb00b1bc558fbf1abe1612a4339 to your computer and use it in GitHub Desktop.
Save iTrooz/91591fb00b1bc558fbf1abe1612a4339 to your computer and use it in GitHub Desktop.
TCP socket server with multi-client support with poll()
// NOTE : you need the library fmt to format strings
// Command to use : `g++ server.cpp -o server -lfmt`
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>
#include <string>
#include <iostream>
#include <fmt/core.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <poll.h>
struct client_t {
int fd;
std::string strBuffer;
};
// send a message to every client, except `except_fd` if non-zero
void send_to_clients(const std::vector<client_t>& clients, const std::string& msg, int except_fd=0){
for(const auto& loop_client : clients){
if(loop_client.fd==except_fd)continue;
write(loop_client.fd, msg.c_str(), msg.size());
}
}
int main(){
int sock_server = socket(AF_INET, SOCK_STREAM, 0);
if(sock_server<0){
perror("socket() failed");
return EXIT_FAILURE;
}
int yes = 1;
if (setsockopt(sock_server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))<0){
perror("setsockopt() failed");
return EXIT_FAILURE;
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080);
if(bind(sock_server, (struct sockaddr*) &address, sizeof(struct sockaddr_in))<0){
perror("bind() failed");
return EXIT_FAILURE;
}
if(listen(sock_server, 3) < 0) {
perror("listen() failed");
return EXIT_FAILURE;
}
std::cout << "Server started and listening !" << std::endl;
std::vector<client_t> clients;
while(1){
// generate the structures for the fds we want to poll
std::vector<pollfd> descriptors;
pollfd pollfd_inst;
pollfd_inst.fd = sock_server;
pollfd_inst.events = POLLIN;
descriptors.push_back(pollfd_inst);
for(auto& client : clients){
pollfd_inst.fd = client.fd;
descriptors.push_back(pollfd_inst);
}
// blocking function, wait for a file descriptor to have an event
if(!poll(descriptors.data(), descriptors.size(), -1)){
perror("poll failed");
return EXIT_FAILURE;
}
int i=0;
for(auto pollfd_inst : descriptors){
if(pollfd_inst.revents & POLLIN){
if(pollfd_inst.fd==sock_server){
// means we have a connection
std::cout << "Server socket got a new client !" << std::endl;
struct sockaddr_in client_addr;
socklen_t socklen;
int sock_client = accept(sock_server, (struct sockaddr*) &client_addr, &socklen);
if(sock_client<0){
perror("accept() failed");
return EXIT_FAILURE;
}
client_t client;
client.fd = sock_client;
client.strBuffer = "";
clients.push_back(client);
std::cout << "Address : " << inet_ntoa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) << std::endl;
// send welcome message
std::string welcome_msg = fmt::format("Welcome to the server ! Your ID is: {}\n", sock_client);
write(sock_client, welcome_msg.c_str(), welcome_msg.size());
send_to_clients(clients, fmt::format("[SYSTEM] [{}] joined the server\n", sock_client), sock_client);
}else{
// means we have a message
#define READ_BLOCK_SIZE 1024
char buffer[READ_BLOCK_SIZE];
client_t& client = clients[i-1];
int bytes_read = read(pollfd_inst.fd, buffer, READ_BLOCK_SIZE-1);
if(bytes_read==-1){
// connection error
perror("read failed");
return -1;
}else if(bytes_read==0){
// connection finished
close(client.fd);
printf("Client ID=%i left\n", client.fd);
send_to_clients(clients, fmt::format("[SYSTEM] [{}] quit the server\n", client.fd), client.fd);
clients.erase(clients.begin()+i-1);
}else{
buffer[bytes_read] = 0;
std::string& strBuffer = client.strBuffer;
strBuffer.append(buffer);
size_t nlPos;
while((nlPos = strBuffer.find('\n')) != std::string::npos){
std::string msg = strBuffer.substr(0, nlPos+1);
strBuffer.erase(0, nlPos+1);
// format message for chat
std::string msg_with_id = fmt::format("[CHAT] [{}] {}", client.fd, msg);
std::cout << msg_with_id; // send to the console
send_to_clients(clients, msg_with_id, client.fd); // send to the clients
}
}
}
}
i++;
}
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment