Created
October 20, 2022 13:04
-
-
Save iTrooz/91591fb00b1bc558fbf1abe1612a4339 to your computer and use it in GitHub Desktop.
TCP socket server with multi-client support with poll()
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
// 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