Linux環(huán)境下實(shí)現(xiàn)多進(jìn)程Socket通信功能
需求分析
我們的目標(biāo)是實(shí)現(xiàn)以下功能:
- 服務(wù)器能夠啟動(dòng)多個(gè)客戶(hù)端進(jìn)程,客戶(hù)端數(shù)量可配置
- 每個(gè)客戶(hù)端在不同的端口上監(jiān)聽(tīng)
- 服務(wù)器能夠連接到所有客戶(hù)端
- 服務(wù)器能夠同時(shí)向所有客戶(hù)端發(fā)送消息
- 客戶(hù)端接收到消息后發(fā)送確認(rèn)
- 程序能夠優(yōu)雅地處理終止信號(hào)
系統(tǒng)架構(gòu)
整個(gè)系統(tǒng)由兩個(gè)主要組件組成:
客戶(hù)端程序(Client):
- 在指定端口上監(jiān)聽(tīng)連接
- 接收服務(wù)器發(fā)送的消息
- 發(fā)送確認(rèn)消息給服務(wù)器
服務(wù)器程序(Server):
- 從配置文件讀取設(shè)置
- 啟動(dòng)多個(gè)客戶(hù)端進(jìn)程
- 連接到每個(gè)客戶(hù)端
- 向所有客戶(hù)端發(fā)送消息
- 接收客戶(hù)端的確認(rèn)消息
實(shí)現(xiàn)細(xì)節(jié)
客戶(hù)端實(shí)現(xiàn)(client.cpp)
客戶(hù)端程序需要?jiǎng)?chuàng)建一個(gè)Socket,在指定端口上監(jiān)聽(tīng)連接,接收服務(wù)器發(fā)送的消息,并發(fā)送確認(rèn)。
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define BUFFER_SIZE 1024
#define DEFAULT_PORT 8888
bool running = true;
void signalHandler(int signum) {
std::cout << "Interrupt signal (" << signum << ") received.\n";
running = false;
}
int main(int argc, char* argv[]) {
// 注冊(cè)信號(hào)處理函數(shù),用于優(yōu)雅關(guān)閉
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
// 解析命令行參數(shù)
int port = DEFAULT_PORT;
if (argc > 1) {
port = std::stoi(argv[1]);
}
// 客戶(hù)端ID(進(jìn)程ID)
pid_t pid = getpid();
std::cout << "Client started with PID: " << pid << std::endl;
// 創(chuàng)建Socket
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket < 0) {
std::cerr << "Error creating socket" << std::endl;
return 1;
}
// 設(shè)置服務(wù)器地址
struct sockaddr_in serverAddr;
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = INADDR_ANY;
// 綁定Socket到地址
if (bind(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
std::cerr << "Error binding socket to port " << port << std::endl;
close(clientSocket);
return 1;
}
// 監(jiān)聽(tīng)連接
if (listen(clientSocket, 5) < 0) {
std::cerr << "Error listening on socket" << std::endl;
close(clientSocket);
return 1;
}
std::cout << "Client listening on port " << port << std::endl;
// 接受服務(wù)器連接
struct sockaddr_in serverConnAddr;
socklen_t serverLen = sizeof(serverConnAddr);
int serverConnection = accept(clientSocket, (struct sockaddr*)&serverConnAddr, &serverLen);
if (serverConnection < 0) {
std::cerr << "Error accepting connection" << std::endl;
close(clientSocket);
return 1;
}
std::cout << "Connected to server at "
<< inet_ntoa(serverConnAddr.sin_addr)
<< ":" << ntohs(serverConnAddr.sin_port)
<< std::endl;
// 接收服務(wù)器消息
char buffer[BUFFER_SIZE];
while (running) {
memset(buffer, 0, BUFFER_SIZE);
int bytesReceived = recv(serverConnection, buffer, BUFFER_SIZE - 1, 0);
if (bytesReceived > 0) {
std::cout << "Message received: " << buffer << std::endl;
// 發(fā)送確認(rèn)
std::string ack = "Client " + std::to_string(pid) + " received message";
send(serverConnection, ack.c_str(), ack.length(), 0);
}
else if (bytesReceived == 0) {
std::cout << "Server disconnected" << std::endl;
break;
}
else {
if (errno != EINTR) { // 忽略信號(hào)中斷
std::cerr << "Error receiving data: " << strerror(errno) << std::endl;
break;
}
}
}
// 清理資源
close(serverConnection);
close(clientSocket);
std::cout << "Client terminated" << std::endl;
return 0;
}
服務(wù)器實(shí)現(xiàn)(server.cpp)
服務(wù)器程序需要啟動(dòng)多個(gè)客戶(hù)端進(jìn)程,連接到每個(gè)客戶(hù)端,并向所有客戶(hù)端發(fā)送消息。
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <thread>
#include <chrono>
#include <fstream>
#include <sstream>
#include <signal.h>
#include <sys/wait.h>
#include <mutex>
#include <condition_variable>
#define BUFFER_SIZE 1024
#define DEFAULT_BASE_PORT 8888
#define DEFAULT_NUM_CLIENTS 3
#define CONFIG_FILE "server_config.txt"
std::mutex mtx;
std::condition_variable cv;
bool allClientsConnected = false;
int connectedClients = 0;
int totalClients = 0;
std::vector<pid_t> clientPids;
std::vector<int> clientSockets;
// 從配置文件讀取配置
bool readConfig(int& numClients, int& basePort) {
std::ifstream configFile(CONFIG_FILE);
if (!configFile.is_open()) {
std::cout << "Config file not found, using defaults" << std::endl;
return false;
}
std::string line;
while (std::getline(configFile, line)) {
std::istringstream iss(line);
std::string key;
if (std::getline(iss, key, '=')) {
std::string value;
if (std::getline(iss, value)) {
if (key == "NUM_CLIENTS") {
numClients = std::stoi(value);
} else if (key == "BASE_PORT") {
basePort = std::stoi(value);
}
}
}
}
configFile.close();
return true;
}
// 寫(xiě)入默認(rèn)配置到文件
void writeDefaultConfig() {
std::ofstream configFile(CONFIG_FILE);
if (configFile.is_open()) {
configFile << "NUM_CLIENTS=" << DEFAULT_NUM_CLIENTS << std::endl;
configFile << "BASE_PORT=" << DEFAULT_BASE_PORT << std::endl;
configFile.close();
std::cout << "Created default configuration file" << std::endl;
} else {
std::cerr << "Unable to create configuration file" << std::endl;
}
}
// 啟動(dòng)客戶(hù)端進(jìn)程
pid_t launchClient(int port) {
pid_t pid = fork();
if (pid == 0) {
// 子進(jìn)程
std::string portStr = std::to_string(port);
execl("./Client", "Client", portStr.c_str(), nullptr);
// 如果execl返回,說(shuō)明出錯(cuò)了
std::cerr << "Error launching client: " << strerror(errno) << std::endl;
exit(1);
} else if (pid < 0) {
// fork失敗
std::cerr << "Fork failed: " << strerror(errno) << std::endl;
return -1;
}
// 父進(jìn)程
return pid;
}
// 連接到客戶(hù)端
bool connectToClient(int& clientSocket, int port) {
clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket < 0) {
std::cerr << "Error creating socket for client on port " << port << std::endl;
return false;
}
struct sockaddr_in clientAddr;
memset(&clientAddr, 0, sizeof(clientAddr));
clientAddr.sin_family = AF_INET;
clientAddr.sin_port = htons(port);
clientAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 嘗試連接,帶重試
int retries = 10;
while (retries > 0) {
if (connect(clientSocket, (struct sockaddr*)&clientAddr, sizeof(clientAddr)) == 0) {
std::cout << "Connected to client on port " << port << std::endl;
return true;
}
std::cout << "Connection attempt failed, retrying in 1 second..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
retries--;
}
std::cerr << "Failed to connect to client on port " << port << std::endl;
close(clientSocket);
return false;
}
// 連接客戶(hù)端的線(xiàn)程函數(shù)
void clientConnectionThread(int port, int clientIndex) {
int clientSocket;
if (connectToClient(clientSocket, port)) {
std::lock_guard<std::mutex> lock(mtx);
clientSockets[clientIndex] = clientSocket;
connectedClients++;
if (connectedClients == totalClients) {
allClientsConnected = true;
cv.notify_one();
}
}
}
// 向所有已連接的客戶(hù)端發(fā)送消息
void sendToAllClients(const std::string& message) {
for (int socket : clientSockets) {
if (socket > 0) {
send(socket, message.c_str(), message.length(), 0);
// 接收確認(rèn)
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE);
int bytesReceived = recv(socket, buffer, BUFFER_SIZE - 1, 0);
if (bytesReceived > 0) {
std::cout << "Acknowledgment: " << buffer << std::endl;
}
}
}
}
// 清理資源
void cleanup() {
// 關(guān)閉所有客戶(hù)端Socket
for (int socket : clientSockets) {
if (socket > 0) {
close(socket);
}
}
// 終止所有客戶(hù)端進(jìn)程
for (pid_t pid : clientPids) {
if (pid > 0) {
kill(pid, SIGTERM);
waitpid(pid, nullptr, 0);
}
}
}
// 信號(hào)處理函數(shù)
void signalHandler(int signum) {
std::cout << "Interrupt signal (" << signum << ") received.\n";
cleanup();
exit(signum);
}
int main() {
// 注冊(cè)信號(hào)處理函數(shù)
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
// 讀取配置
int numClients = DEFAULT_NUM_CLIENTS;
int basePort = DEFAULT_BASE_PORT;
if (!readConfig(numClients, basePort)) {
writeDefaultConfig();
}
std::cout << "Starting server with " << numClients << " clients, base port: " << basePort << std::endl;
// 初始化客戶(hù)端向量
totalClients = numClients;
clientPids.resize(numClients, -1);
clientSockets.resize(numClients, -1);
// 啟動(dòng)客戶(hù)端進(jìn)程
for (int i = 0; i < numClients; i++) {
int port = basePort + i;
pid_t pid = launchClient(port);
if (pid > 0) {
clientPids[i] = pid;
std::cout << "Launched client " << i + 1 << " with PID " << pid << " on port " << port << std::endl;
} else {
std::cerr << "Failed to launch client " << i + 1 << std::endl;
}
}
// 給客戶(hù)端啟動(dòng)的時(shí)間
std::cout << "Waiting for clients to start..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
// 連接到客戶(hù)端
std::vector<std::thread> connectionThreads;
for (int i = 0; i < numClients; i++) {
if (clientPids[i] > 0) {
int port = basePort + i;
connectionThreads.push_back(std::thread(clientConnectionThread, port, i));
}
}
// 等待所有連接建立
{
std::unique_lock<std::mutex> lock(mtx);
if (!allClientsConnected) {
std::cout << "Waiting for all clients to connect..." << std::endl;
cv.wait(lock, []{ return allClientsConnected; });
}
}
// 等待所有連接線(xiàn)程結(jié)束
for (auto& thread : connectionThreads) {
thread.join();
}
std::cout << "All clients connected. Ready to send messages." << std::endl;
// 主循環(huán)
std::string message;
while (true) {
std::cout << "Enter message to send to all clients (or 'exit' to quit): ";
std::getline(std::cin, message);
if (message == "exit") {
break;
}
std::cout << "Sending message to all clients..." << std::endl;
sendToAllClients(message);
}
// 清理
cleanup();
std::cout << "Server terminated" << std::endl;
return 0;
}
編譯和運(yùn)行
為了方便編譯,我們可以創(chuàng)建一個(gè)Makefile:
CC = g++ CFLAGS = -std=c++11 -Wall -pthread LDFLAGS = -pthread all: Client Server Client: client.cpp $(CC) $(CFLAGS) -o Client client.cpp $(LDFLAGS) Server: server.cpp $(CC) $(CFLAGS) -o Server server.cpp $(LDFLAGS) clean: rm -f Client Server server_config.txt .PHONY: all clean
編譯和運(yùn)行的步驟:
# 編譯 make # 運(yùn)行服務(wù)器 ./Server
代碼解析
端口號(hào)傳遞機(jī)制
服務(wù)器如何將端口號(hào)傳遞給客戶(hù)端是本系統(tǒng)的一個(gè)關(guān)鍵點(diǎn)。整個(gè)過(guò)程如下:
- 服務(wù)器為每個(gè)客戶(hù)端分配一個(gè)唯一的端口號(hào)(基礎(chǔ)端口號(hào) + 索引)
- 服務(wù)器通過(guò)
fork()創(chuàng)建子進(jìn)程 - 子進(jìn)程通過(guò)
execl()執(zhí)行客戶(hù)端程序,將端口號(hào)作為命令行參數(shù)傳遞 - 客戶(hù)端程序解析命令行參數(shù)獲取端口號(hào)
- 客戶(hù)端使用該端口號(hào)創(chuàng)建和綁定Socket
- 服務(wù)器知道每個(gè)客戶(hù)端的端口號(hào),并使用這些端口號(hào)連接到客戶(hù)端
信號(hào)處理
程序使用信號(hào)處理機(jī)制來(lái)實(shí)現(xiàn)優(yōu)雅關(guān)閉:
signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler);
這兩行代碼注冊(cè)了信號(hào)處理函數(shù),當(dāng)程序接收到SIGINT(通常是按Ctrl+C)或SIGTERM(通常是系統(tǒng)發(fā)送的終止信號(hào))時(shí),會(huì)調(diào)用signalHandler函數(shù)。在這個(gè)函數(shù)中,程序會(huì)清理資源并正常退出。
多線(xiàn)程連接
服務(wù)器使用多線(xiàn)程來(lái)并行連接到所有客戶(hù)端:
std::vector<std::thread> connectionThreads;
for (int i = 0; i < numClients; i++) {
if (clientPids[i] > 0) {
int port = basePort + i;
connectionThreads.push_back(std::thread(clientConnectionThread, port, i));
}
}
這樣可以同時(shí)嘗試連接到所有客戶(hù)端,而不是一個(gè)接一個(gè)地連接,提高了效率。
條件變量同步
服務(wù)器使用條件變量來(lái)等待所有客戶(hù)端連接完成:
{
std::unique_lock<std::mutex> lock(mtx);
if (!allClientsConnected) {
std::cout << "Waiting for all clients to connect..." << std::endl;
cv.wait(lock, []{ return allClientsConnected; });
}
}
當(dāng)所有客戶(hù)端都連接成功后,allClientsConnected變量會(huì)被設(shè)置為true,條件變量會(huì)通知主線(xiàn)程繼續(xù)執(zhí)行。
總結(jié)
本文介紹了如何在Linux環(huán)境下實(shí)現(xiàn)一個(gè)服務(wù)器程序,該程序能夠啟動(dòng)多個(gè)客戶(hù)端進(jìn)程,并通過(guò)Socket與這些客戶(hù)端進(jìn)行通信。主要特點(diǎn)包括:
- 可配置的客戶(hù)端數(shù)量
- 動(dòng)態(tài)端口分配
- 并行連接
- 廣播消息
- 確認(rèn)機(jī)制
- 優(yōu)雅關(guān)閉
這個(gè)示例展示了多進(jìn)程、Socket通信、多線(xiàn)程和同步機(jī)制的綜合應(yīng)用,可以作為網(wǎng)絡(luò)編程的參考實(shí)現(xiàn)。
進(jìn)一步改進(jìn)
這個(gè)示例還可以進(jìn)一步改進(jìn),例如:
- 添加錯(cuò)誤恢復(fù)機(jī)制
- 實(shí)現(xiàn)客戶(hù)端自動(dòng)重連
- 添加消息隊(duì)列
- 實(shí)現(xiàn)更復(fù)雜的通信協(xié)議
- 添加安全機(jī)制(如TLS加密)
以上就是Linux環(huán)境下實(shí)現(xiàn)多進(jìn)程Socket通信功能的詳細(xì)內(nèi)容,更多關(guān)于Linux多進(jìn)程Socket通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Linux服務(wù)器出現(xiàn)崩潰或異常的故障排查和急救指南
當(dāng) Linux 服務(wù)器出現(xiàn)崩潰或異常時(shí),及時(shí)有效的故障排查是至關(guān)重要的,以下是一個(gè)綜合性的急救指南,幫助你在遇到問(wèn)題時(shí)進(jìn)行快速的故障排查和修復(fù),需要的朋友可以參考下2025-11-11
Ubuntu系統(tǒng)Python崩潰的終極復(fù)活指南
你是否也經(jīng)歷過(guò)python3 --version 像被施了靜音咒,毫無(wú)反應(yīng);apt install 報(bào)錯(cuò)如雪崩,全是 py3compile: import not found;系統(tǒng)16個(gè)包卡死,連重裝 python3.8 都失敗,別慌,本文將帶你一步步手動(dòng)拆彈、強(qiáng)制修復(fù)、重建系統(tǒng)Python核心,需要的朋友可以參考下2025-10-10
Linux CentOS 6.5 卸載、tar安裝MySQL的教程
這篇文章主要介紹了Linux CentOS 6.5 卸載、tar安裝MySQL的教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2019-11-11
Lamp環(huán)境下設(shè)置綁定apache域名的方法分析
這篇文章主要介紹了Lamp環(huán)境下設(shè)置綁定apache域名的方法,簡(jiǎn)單分析了Lamp環(huán)境下的Apache域名綁定設(shè)置相關(guān)屬性、功能與操作技巧,需要的朋友可以參考下2018-03-03

