#c #sockets #server #pthreads #file-descriptor
Вопрос:
Я написал простой сервер сокетов TCP на языке C, который создает новый дочерний рабочий поток, когда он принимает новый запрос на подключение от клиента и просто подсчитывает и отправляет номера. Если клиент завершает работу, соответствующий дочерний рабочий поток также должен завершиться, в то время как другие потоки не должны.
Если все клиенты написаны на Python, то при завершении работы клиента на сервере печатается «Соединение сброшено одноранговым узлом», но все остальное в порядке, то есть другие потоки и клиенты все еще работают.
Но если клиент написан на языке Си, то когда какие-либо клиенты и соответствующие им дочерние рабочие потоки завершаются, другие потоки также завершаются, чего не ожидается. Почему это происходит? Я переписал сервер на Python, но этого не произошло, независимо от того, на каком языке написан клиент.
Затем я прокомментировал, close(*client_fd);
и проблема решена. Я понятия не имею, так как он отлично работает на сервере с использованием fork()
.
Код C для использования сервером pthread
выглядит следующим образом:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT 9000
#define CONNECTIONS 2
#define MAX_BUFFER_SIZE 1024
struct sockaddr_in socket_address;
socklen_t socket_address_size = sizeof(socket_address);
int server_fd;
void *handle_request(void *fd) {
int *client_fd = (int *) fd;
char buffer[MAX_BUFFER_SIZE] = {0};
for (int i = INT_MAX; send(*client_fd, buffer, strlen(buffer), 0) >= 0 amp;amp; i >= 0; i--) {
printf("%drn", i);
sprintf(buffer, "%d", i);
}
if (close(*client_fd) < 0) {
perror("close client_fd");
}
return NULL;
}
int main(int argc, char *argv[]) {
int option = 1;
char buffer[MAX_BUFFER_SIZE] = {0};
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, amp;option, sizeof(option))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
socket_address.sin_family = AF_INET;
socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
socket_address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *) amp;socket_address, socket_address_size) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_fd, CONNECTIONS) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
pthread_t threads[CONNECTIONS];
int client_fds[CONNECTIONS];
for (int i = 0; i < CONNECTIONS; i ) {
client_fds[i] = accept(server_fd, (struct sockaddr *) amp;socket_address, amp;socket_address_size);
if (client_fds[i] < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
if (pthread_create(amp;threads[i], NULL, handle_request, amp;client_fds[i]) < 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}
}
for (int i = 0; i < CONNECTIONS; i ) {
if (pthread_join(threads[i], NULL) < 0) {
perror("pthread_join");
}
}
close(server_fd);
return EXIT_SUCCESS;
}
Код C для использования сервером fork()
выглядит следующим образом:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#define PORT 9000
#define CONNECTIONS 2
#define MAX_BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
struct sockaddr_in socket_address;
socklen_t socket_address_size = sizeof(socket_address);
int server_fd, client_fd, option = 1;
char buffer[MAX_BUFFER_SIZE] = {0};
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, amp;option, sizeof(option))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
socket_address.sin_family = AF_INET;
socket_address.sin_addr.s_addr = htonl(INADDR_ANY);
socket_address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *) amp;socket_address, socket_address_size) < 0) {
perror("bind");
exit(EXIT_FAILURE);
}
if (listen(server_fd, CONNECTIONS) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
pid_t pids[CONNECTIONS];
for (int i = 0; i < CONNECTIONS; i ) {
pids[i] = fork();
if (pids[i] < 0) {
perror("fork");
exit(EXIT_FAILURE);
} else if (pids[i] == 0) {
if ((client_fd = accept(server_fd, (struct sockaddr *) amp;socket_address, amp;socket_address_size)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
for (int i = INT_MAX; send(client_fd, buffer, strlen(buffer), 0) >= 0 amp;amp; i >= 0; i--) {
printf("%drn", i);
sprintf(buffer, "%d", i);
}
close(client_fd);
return EXIT_SUCCESS;
}
}
for (int i = 0; i < CONNECTIONS; i ) {
int wstatus;
if (waitpid(0, amp;wstatus, WUNTRACED) < 0) {
perror("waitpid");
}
}
close(server_fd);
return EXIT_SUCCESS;
}
The C code for client is as follows:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define IP_ADDRESS "127.0.0.1"
#define PORT 9000
#define MAX_BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
int socket_fd;
struct sockaddr_in socket_address;
socklen_t socket_address_size = sizeof(socket_address);
ssize_t message_len;
char buffer[MAX_BUFFER_SIZE] = {0};
if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket");
exit(EXIT_FAILURE);
}
socket_address.sin_family = AF_INET;
socket_address.sin_addr.s_addr = inet_addr(IP_ADDRESS);
socket_address.sin_port = htons(PORT);
if (connect(socket_fd, (struct sockaddr *) amp;socket_address, socket_address_size) < 0) {
perror("connect");
exit(EXIT_FAILURE);
}
while ((message_len = recv(socket_fd, buffer, MAX_BUFFER_SIZE, 0)) > 0) {
buffer[message_len] = '';
puts(buffer);
}
close(socket_fd);
return EXIT_SUCCESS;
}
Код Python для сервера выглядит следующим образом:
import socket
import threading
HOST = ''
PORT = 9000
CONNECTIONS = 2
TRUNK_SIZE = 1024
def handle_request(connection):
with connection:
count = 0
while True:
state = connection.send(f'{count}rn'.encode('utf-8'))
if not state:
print(f"Connection closed from {address}.")
break
print(count)
count = 1
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR | socket.SO_REUSEPORT, 1)
s.bind((HOST, PORT))
s.listen(CONNECTIONS)
threads = []
for c in range(CONNECTIONS):
connection, address = s.accept()
print(f'Connected by {address}.')
thread = threading.Thread(target=handle_request, args=(connection,), daemon=True)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
Код Python для клиента выглядит следующим образом:
import socket
HOST = '127.0.0.1'
PORT = 9000
TRUNK_SIZE = 1024
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
while True:
data = s.recv(TRUNK_SIZE).decode('utf-8')
if not data:
print("Connection closed.")
break
print(data)
Комментарии:
1. Возможно, потому, что ресурсы (включая сокеты) в потоках совместно используются одним процессом? Закрытие сокета в одном потоке закрывает его на весь процесс. Если используются рабочие процессы, то сокеты отделены от сокетов в других процессах, даже если процессы были разветвлены от одного и того же родительского процесса.
2. @Someprogrammerdude, но почему это проблема здесь? Таково ожидаемое поведение.
3. В заголовке спрашивалось «почему не закрываются потоки, а закрываются процессы», и в этом, в основном, причина. Да, это ожидаемое поведение, но не все еще знают об этом.
4. accept создает новый сокет. connection_handler должен закрыть этот сокет. Должно быть, в коде есть какая-то другая ошибка.
5. @Someprogrammerdude, Но
client_fd
в каждом потоке детского рабочего процесса он отличается. Я думаю, что закрытие сокета в одном потоке не должно закрывать его на весь процесс?
Ответ №1:
Если весь ваш процесс внезапно выходит из строя во время записи в сокет, то это происходит из-за SIGPIPE
полученного вами сигнала (на который указывает сообщение об ошибке EPIPE). Стандартное действие SIGPIPE
состоит в том, чтобы завершить процесс. Реализуйте обработчик сигналов, который будет улавливать SIGPIPE
сигналы (и, вероятно, игнорировать их или иметь с ними дело).
Сообщение «Соединение сброшено одноранговым узлом» указывает на перехваченный сигнал SIGPIPE.
Комментарии:
1. Одна из многих проблем с systemd заключается в том, что по умолчанию для параметра IgnoreSIGPIPE установлено значение true.
2. @старк Ну, я не знаю, есть ли в операционной systemd в ее системе, но я все еще думаю, что его завершение процесса связано с этим. Утверждается, что с раздвоенной версией нет никаких «проблем», потому что завершается только этот дочерний процесс, а не, как в многопоточной версии, единственный существующий процесс.
3. @старк, Но, тем не менее, спасибо, я этого не знал.