#c #multithreading #networking #sfml
#c #многопоточность #сеть #sfml
Вопрос:
Проблема в том, что он подключается только к одному клиенту вместо двух. Кто-нибудь может помочь мне выяснить, почему?
Сервер:
#include <SFML/System.hpp>
#include <SFML/Network.hpp>
#include <iostream>
void sendInfo(void *UserData)
{
sf::IPAddress* ip = static_cast<sf::IPAddress*>(UserData);
// Print something...
while(true){
// Create the UDP socket
sf::SocketUDP Socket;
// Create bytes to send
char Buffer[] = "sending info.";
// Send data to "192.168.0.2" on port 4567
if (Socket.Send(Buffer, sizeof(Buffer), *ip, 4444) != sf::Socket::Done)
{
// Error...
}
}
}
void receiveInfo(void *userData)
{
// Print something...
while(true){
// Create the UDP socket
sf::SocketUDP Socket;
// Bind it (listen) to the port 4567
if (!Socket.Bind(4444))
{
// Error...
}
char Buffer[128];
std::size_t Received;
sf::IPAddress Sender;
unsigned short Port;
if (Socket.Receive(Buffer, sizeof(Buffer), Received, Sender, Port) != sf::Socket::Done)
{
// Error...
}
// Show the address / port of the sender
std::cout << Buffer << std::endl;
Socket.Close();
}
}
int main()
{
sf::IPAddress client[2];
int connected = 0;
while(connected < 2){
// Create the UDP socket
sf::SocketUDP Socket;
// Bind it (listen) to the port 4567
if (!Socket.Bind(4444))
{
// Error...
}
char Buffer[128];
std::size_t Received;
sf::IPAddress Sender;
unsigned short Port;
if (Socket.Receive(Buffer, sizeof(Buffer), Received, Sender, Port) != sf::Socket::Done)
{
// Error...
}
// Show the address / port of the sender
client[connected] = Sender;
Socket.Close();
sf::Thread* send = new sf::Thread(amp;sendInfo, amp;client[connected]);
sf::Thread* receive = new sf::Thread(amp;receiveInfo, amp;client[connected]);
// Start it !
send->Launch();
receive->Launch();
connected ;
}
while(true){
}
return EXIT_SUCCESS;
}
Клиент:
#include <SFML/System.hpp>
#include <SFML/Network.hpp>
#include <iostream>
void sendInfo(void *UserData)
{
// Print something...
while(true){
// Create the UDP socket
sf::SocketUDP Socket;
// Create bytes to send
char Buffer[] = "client sending info.";
// Send data to "192.168.0.2" on port 4567
if (Socket.Send(Buffer, sizeof(Buffer), "127.0.0.1", 4444) != sf::Socket::Done)
{
// Error...
}
}
}
void receiveInfo(void *userData)
{
// Print something...
while(true){
// Create the UDP socket
sf::SocketUDP Socket;
// Bind it (listen) to the port 4567
if (!Socket.Bind(4444))
{
// Error...
}
char Buffer[128];
std::size_t Received;
sf::IPAddress Sender;
unsigned short Port;
if (Socket.Receive(Buffer, sizeof(Buffer), Received, Sender, Port) != sf::Socket::Done)
{
// Error...
}
// Show the address / port of the sender
std::cout << Buffer << std::endl;
Socket.Close();
}
}
int main()
{
// Create the UDP socket
sf::SocketUDP Socket;
// Create bytes to send
char Buffer[] = "Client Joined.";
// Send data to "192.168.0.2" on port 4567
if (Socket.Send(Buffer, sizeof(Buffer), "127.0.0.1", 4444) != sf::Socket::Done)
{
// Error...
}
sf::Thread* send = new sf::Thread(amp;sendInfo);
sf::Thread* receive = new sf::Thread(amp;receiveInfo);
// Start it !
send->Launch();
receive->Launch();
while(true){
}
return EXIT_SUCCESS;
}
Комментарии:
1. Кажется, ваш код использует UDP-сокеты с TCP-подобной логикой. На стороне сервера у вас должен быть только 1 сокет для обоих клиентов. Я подозреваю, что происходят ошибки, но вы ничего не диагностируете своими комментариями «Ошибка …»!
2. Почему вы используете разные сокеты для каждого вызова Send и Recv?
Ответ №1:
Перво-наперво: это сервер чата или «более типичный» сервер?
Если это сервер чата, то вам нужно либо иметь список сокетов, которые подключены к клиентам (вы можете подключать сокеты UDP с помощью connect()
вызова, что очень удобно, и это также помогает снизить вероятность подделки одноранговых узлов), либо список всех адресов клиентов, которые вы можете указать sendto()
или sendmsg()
.
Более «типичные» серверы не будут пытаться отправлять сообщения какому-либо клиенту, кроме того, который последним отправил запрос: эти серверы обычно не сохраняют ничего от клиентов, а вместо этого используют recvfrom()
or recvmsg()
для получения адреса однорангового узла для использования в последующих вызовах sendto()
or sendmsg()
.
Кроме того, большинство протоколов полагаются только на один хорошо известный порт; сервер использует один конкретный порт по соглашению, но клиенты выбирают любой порт, который открыт и свободен. FTP также сильно зависит от хорошо известных портов на стороне клиента, и в результате туннелирование через брандмауэры с трансляцией сетевых адресов сопряжено с огромными трудностями.
Это не просто академический вопрос: и ваш клиент, и ваш сервер пытаются bind()
выполнить перенос 4444
. Это означает, что вам нужно по крайней мере два IP-адреса на одной машине для тестирования, или использовать программное обеспечение виртуализации для запуска совершенно отдельной машины на том же оборудовании, или просто иметь в наличии две машины. Это требует больше работы, чем нужно, и у клиентов нет причин заботиться о номерах своих локальных портов:
Сервер:
// Bind it (listen) to the port 4567
if (!Socket.Bind(4444))
{
// Error...
}
Клиент:
// Bind it (listen) to the port 4567
if (!Socket.Bind(4444))
{
// Error...
}
Пуф!Эти два устройства никогда не будут запускаться на одном хосте без существенных ухищрений. Я ожидаю, что ваше «он подключается к одному», вероятно, просто сервер или клиент, подключающийся к самому себе, но без какого-либо кода для заполнения этих // Error
блоков, было бы трудно сказать наверняка.
(И пока мы здесь, я хотел бы отвести разговор в сторону о комментариях; комментарии, которые просто повторяют, что делает код, не очень полезны. Вы заметите, что большинство ваших комментариев на самом деле неверны, ссылаясь на неправильные IP-адреса или порты. Некоторые просто не добавляют никакой информации:
// Create the UDP socket
sf::SocketUDP Socket;
Я знаю, что нас учат добавлять комментарии, но, к сожалению, нас не всегда учат, какие виды комментариев добавлять. Единственным комментарием в обеих программах, который я бы рекомендовал сохранить, был бы этот, слегка измененный:
// udp doesn't require listen or accept
if (!Socket.Bind(4444))
Это не очевидно при чтении кода и не будет ошибкой, когда номер порта считывается из переменной среды, параметра командной строки, файла конфигурации или реестра. (Это может показаться излишним для команды людей, знакомых с sockets API, но может оказаться полезным для программиста, не слишком знакомого с различиями между UDP и TCP.)
Хорошие названия функций, имена переменных и т.д. Почти каждый раз будут вызывать комментарии. Конец в сторону. 🙂
И теперь, более мелкая придирка: ваши обработчики потоков выполняют некоторые задачи, подобные этой:
while(1) {
socket s;
bind s;
r = recv s;
print r;
close s;
}
Все эти ненужные процессы создания, привязки и закрытия являются пустой тратой энергии, как энергии компьютера, так и (что гораздо важнее) вашей энергии. Рассмотрите следующие два варианта перезаписи:
recv_thread() {
socket s;
bind s;
while (1) {
r = recv s;
print r;
}
close s;
}
или
recv_thread(s) {
while (1) {
r = recv s;
print r;
}
}
/* ... */
socket s;
bind s;
sf::Thread* rt = new sf::Thread(amp;recv_thread);
rt->Launch(s);
Первый вариант — это простой рефакторинг вашего существующего кода; он сохраняет создание и уничтожение сокета в функции потока, но выводит инварианты цикла из цикла. Код внутри цикла теперь выполняет только то, что необходимо.
Второй вариант — это более радикальная переработка: он переносит создание сокета в основной поток, где обработка ошибок, вероятно, намного проще, а функция thread выполняет только то, что требуется от удаленного узла, чтобы этот поток выполнял. (Если бы вы хотели перейти с UDP на TCP, второй вариант был бы намного проще — ваш многопоточный код мог бы вообще не нуждаться в каких-либо изменениях.)
Надеюсь, это поможет. 🙂