У меня был код, который реализовал несколько потоков в C ++, и он работал нормально. Одним из таких потоков является сервер UDP, который получает сообщения от клиента UDP. Так хорошо.
Теперь я хотел реализовать TCP-сервер в другом потоке, чтобы и UDP-клиент, и TCP-клиент могли отправлять сообщения на соответствующий сервер (они работают на разных портах). После этого сервер UDP сошел бы с ума … (я действительно не знаю, как объяснить орехи). Пожалуйста, попробуйте следовать за мной:
Минимальный код:
// How to compile using mysql.h
// g++ -o aserver aserver.cpp $(mysql_config --libs) -lpthread
//
//// to operate with I/O functions
#include <iostream>
#include <fstream>
// to operate with strings
#include <string>
// to operate with string streams
#include <sstream>
// to opereta with time
#include <time.h>
// to operate with directories
#include <dirent.h>
// to operate with sleep function
#include <unistd.h>
// to operate with threads
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
// to operate with sockets
#include <sys/socket.h>
#include <sys/types.h>
// Defines the structure of the socket
#include <netinet/in.h>
// Uses memset to clear the structure
#include <string.h>
#include <cerrno>
using namespace std;
// **************************************************************
// * GLOBAL VARIABLES *
// **************************************************************
int logto_id;
int udp_port;
int tcp_port;
int sock;
const int success = 0;
const int general_error = -1;
const string general_error_str = "Error";
void logto(string text, int debug_id) {
int append_status;
switch (debug_id) {
case 1:
cout << text + "\n";
break;
case 2:
break;
case 3:
break;
default:
cout << "";
}
}
int create_udp_socket() {
// UDP Socket Variables
unsigned int serverlen;
sockaddr_in udpServer;
int bind_status = 0;
string function_name="create_udp_socket: ";
/* Create the UDP socket */
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
cout << function_name + "Could not create UDP socket...\n";
return general_error;
}
/* Construct the server sockaddr_in structure */
memset(&udpServer, 0, sizeof(udpServer)); /* Clear struct */
udpServer.sin_family = AF_INET; /* Internet/IP */
udpServer.sin_addr.s_addr = htonl(INADDR_ANY); /* Any IP address */
udpServer.sin_port = htons(udp_port); /* server port */
/* Bind the socket */
serverlen = sizeof(udpServer);
bind_status= bind(sock, (struct sockaddr *) &udpServer, serverlen);
if (bind_status < 0) {
cout << function_name + "Could not bind UDP socket...\n";
return general_error;
} else {
cout << function_name + "UDP Socket created and binded...\n";
return success;
}
}
int create_tcp_socket() {
// TCP Socket Variables
unsigned int serverlen;
sockaddr_in tcpServer;
int bind_status = 0;
int listen_status = 0;
string function_name="create_tcp_socket: ";
/* Create the TCP socket */
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock < 0) {
cout << function_name + "Could not create TCP socket...\n";
return general_error;
}
/* Construct the server sockaddr_in structure */
memset(&tcpServer, 0, sizeof(tcpServer)); /* Clear struct */
tcpServer.sin_family = AF_INET; /* Internet/IP */
tcpServer.sin_addr.s_addr = htonl(INADDR_ANY); /* Any IP address */
tcpServer.sin_port = htons(tcp_port); /* server port */
/* Bind the socket */
serverlen = sizeof(tcpServer);
bind_status = bind(sock, (struct sockaddr *) &tcpServer, serverlen);
if (bind_status < 0) {
cout << function_name + "Could not bind TCP socket...\n";
return general_error;
} else {
cout << function_name + "TCP Socket created and binded...\n";
/* Listen */
listen_status = listen(sock,10);
if (listen_status < 0) {
cout << function_name + "Could not listen on the TCP socket...\n";
return general_error;
} else {
cout << function_name + "TCP Socket listening...\n";
return success;
}
}
}
void *thread_udp_server(void *arg) {
// **************************************************************
// * LOCAL VARIABLES *
// * we define this internal variables that before were Global *
// **************************************************************
/* here we store the SQL INSERT query */
string node_query;
/* here we find the data to build the query
* this variable is always passed by reference to all the functions
*/
string node_line;
/* UDP Socket related variables */
char udp_buffer[255];
int received = 0;
unsigned int echolen, clientlen;
sockaddr_in udpClient;
// Name of thread
string thread_name = (char*)arg;
// We start the whole thing ...
if (create_udp_socket()==success) {
/* Endless loop */
//for(;;) {
while(1) {
logto(udp_buffer,logto_id);
/* Receive a message from the client */
clientlen = sizeof(udpClient);
received = recvfrom(sock, udp_buffer, 255, 0, (struct sockaddr *) &udpClient, &clientlen);
if (received < 0) {
logto(thread_name + " Failed to receive message",logto_id);
std::cout << "Something went wrong! errno " << errno << ": ";
std::cout << strerror(errno) << std::endl;
} else {
logto("\n---------\n" + thread_name,logto_id);
/* We now copy the content of the buffer into 'node_line' */
node_line=udp_buffer;
logto(thread_name + node_line,logto_id);
}
}
} else {
logto(thread_name + " Could not bring up UDP socket...",logto_id);
std::cout << "Something went wrong! errno " << errno << ": ";
std::cout << strerror(errno) << std::endl;
return NULL;
}
}
void *thread_tcp_server(void *arg) {
// **************************************************************
// * LOCAL VARIABLES *
// * we define this internal variables that before were Global *
// **************************************************************
/* here we store the SQL INSERT query */
string node_query;
/* here we find the data to build the query
* this variable is always passed by reference to all the functions
*/
string node_line;
/* TCP Socket related variables */
char tcp_buffer[255];
int recTcp = 0;
unsigned int echolen, clientlen;
sockaddr_in tcpClient;
// Name of thread
string thread_name = (char*)arg;
// We start the whole thing ...
if (create_tcp_socket()==success) {
/* Endless loop */
for(;;) {
logto(tcp_buffer,logto_id);
/* Receive a message from the client */
clientlen = sizeof(tcpClient);
recTcp = accept(sock, (struct sockaddr *) &tcpClient, &clientlen);
if (recTcp < 0) {
logto(thread_name + " Failed to receive message",logto_id);
std::cout << "Something went wrong! errno " << errno << ": ";
std::cout << strerror(errno) << std::endl;
} else {
logto("\n---------\n" + thread_name,logto_id);
/* We now copy the content of the buffer into 'node_line' */
node_line=tcp_buffer;
logto(thread_name + node_line,logto_id);
}
}
} else {
logto(thread_name + " Could not bring up TCP socket...",logto_id);
std::cout << "Something went wrong! errno " << errno << ": ";
std::cout << strerror(errno) << std::endl;
return NULL;
}
}
// -----------------
// - main function -
// -----------------
int main () {
// **************************************************************
// * VARIABLES *
// **************************************************************
// Labels of the threads
string label_udp = "UDP_thread";
string label_tcp = "TCP_thread";
// We define the threads...
pthread_t udp_server_id=20;
pthread_t tcp_server_id=50;
udp_port = 10101;
tcp_port = 10102;
logto_id = 1;
// **************************************************************
// * START *
// **************************************************************
if ( pthread_create( &udp_server_id, NULL, thread_udp_server, (void*) label_udp.c_str()) ) {
logto("Error creating thread_udp_server...",logto_id);
return general_error;
}
if ( pthread_create( &tcp_server_id, NULL, thread_tcp_server, (void*) label_tcp.c_str()) ) {
logto("Error creating thread_tcp_server...",logto_id);
return general_error;
}
if ( pthread_join ( udp_server_id, NULL ) ) {
logto("UDP_thread couldn't join the main thread...",logto_id);
return general_error;
}
if ( pthread_join ( tcp_server_id, NULL ) ) {
logto("TCP_thread couldn't join the main thread...",logto_id);
return general_error;
}
}
После запуска программы в зависимости от того, какой сокет был задействован, появляются следующие ошибки:
ПТС ок!
./aserver
create_tcp_socket: TCP Socket created and binded...
create_tcp_socket: TCP Socket listening...
create_udp_socket: Could not bind UDP socket...
UDP_thread Could not bring up UDP socket...
Something went wrong! errno 22: Invalid argument
UDP хорошо!
./aserver
create_udp_socket: UDP Socket created and binded...
create_tcp_socket: TCP Socket created and binded...
create_tcp_socket: Could not listen on the TCP socket...
TCP_thread Could not bring up TCP socket...
Something went wrong! errno 95: Operation not supported
Существует также третий случай, когда UDP работает (сокет TCP остается выключенным), и для некоторого повторения я получаю прокрутку этих сообщений по всему окну …
./aserver
create_tcp_socket: Could not bind TCP socket...
TCP_thread Could not bring up TCP socket...
Something went wrong! errno create_udp_socket: UDP Socket created and binded...
22: UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connectedInvalid argument
UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connected
UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connected
UDP_thread Failed to receive message
Something went wrong! errno 107: Transport endpoint is not connected
Тем не менее, если я закомментирую какой-либо поток (TCP или UDP), оставшийся работает нормально …
Итог: я не могу заставить оба потока (UDP и TCP) жить вместе …
Может ли кто-нибудь дать мне подсказку на это. Я действительно заблудился от того, почему оба потока одновременно ломают мое приложение … 🙁
Заранее спасибо,
Лукас
Похоже, вы используете один и тот же глобальный сокет для двух потоков.
int sock;
Если create_udp_socket
функция запускается первой, сокет, который она создает, будет перезаписан create_tcp_socket
, и наоборот.
Возможные решения, либо использовать два глобальных сокета:
int tcp_sock;
int udp_sock;
или (лучше) сделать create_xxx_socket
функции возвращают сокеты непосредственно вызывающим, избегая использования глобальных переменных.
Вот пример последнего (обработка ошибок опущена для ясности).
int create_tcp_socket()
{
int sock;
/* Create the TCP socket */
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
/* Bind and listen... */
return sock;
}
TCP поток будет вызывать create_tcp_socket
как это.
void *thread_tcp_server(void *arg)
{
/* ... */
int sock = create_tcp_socket();
if(sock < 0)
{
logto(thread_name + " Could not bring up TCP socket...", logto_id);
return NULL;
}
/* Socket created, start accept'ing connections */
}
Глобальные переменные являются плохими по ряду причин.
В частности, в многопоточном коде, сохраняя данные (sock
в данном случае) личное значит меньше сомнений владение.
Код может делать предположения о том, кому принадлежит глобальная переменная, но с ростом размера программ на практике это становится невозможным.
Сравните это с возвращением sock
от одного из методов создания; Легко увидеть, что изначально, sock
принадлежит методу создания. Когда метод создания возвращается, владение сокетом передается вызывающей стороне. Никогда не существует более одной функции или потока с доступом к сокету, поэтому одновременный доступ к сокету никогда не является проблемой.
Знание того, кому принадлежат данные, также облегчает освобождение или освобождение ресурсов, когда они больше не нужны. В этом случае, если серверный поток должен был выйти, он, являясь владельцем сокета, будет нести ответственность за его закрытие при выходе. И это может сделать это безопасно, потому что никто другой не может использовать сокет.
Других решений пока нет …