首次推送

This commit is contained in:
sansen
2026-07-20 19:01:03 +08:00
parent ea01b9cf99
commit ef1bf61f9f
4484 changed files with 937163 additions and 1 deletions
@@ -0,0 +1,273 @@
/*
* TeamSpeak SDK client multi-connection sample
*
* Spawns multiple simultaneous server connection handlers from a single
* client application, all connecting to the same server. Intended to
* reproduce/diagnose the issue where secondary sessions are dropped after
* ~10 seconds with "10 resends of COMMAND packet" / ping timeout errors.
*
* Usage:
* ts_client_multi [host] [port] [serverPassword] [numConnections] [runSeconds] [staggerMs] [shareIdentity] [localPort]
*
* Defaults: localhost 9987 "secret" 3 60 0 0 0
*
* localPort: local UDP port passed to spawnNewServerConnectionHandler.
* 0 = ephemeral (a fresh port per connection handler). A fixed
* value makes all connection handlers request the SAME local port,
* mimicking applications that pin their source port.
*
* staggerMs: delay between issuing each connection (0 = all simultaneous).
* Use a value larger than the handshake time (e.g. 2000) to start
* each secondary session only after the previous one is fully
* established, matching the reported "secondary sessions initiated
* afterwards" scenario.
*
* Copyright (c) TeamSpeak-Systems
*/
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#else
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#endif
#include <stdio.h>
#include <time.h>
#include <teamspeak/public_definitions.h>
#include <teamspeak/public_errors.h>
#include <teamspeak/clientlib.h>
#ifdef _WIN32
#define SLEEP(x) Sleep(x)
#define strdup(x) _strdup(x)
#else
#define SLEEP(x) usleep((x)*1000)
#endif
#define MAX_CONNECTIONS 32
/* Track the connection handlers we created so the status callback can label them. */
static uint64 g_handlers[MAX_CONNECTIONS];
static int g_numHandlers = 0;
static time_t g_startTime;
static int handlerIndex(uint64 scHandlerID) {
for (int i = 0; i < g_numHandlers; ++i) {
if (g_handlers[i] == scHandlerID) return i;
}
return -1;
}
static double elapsed(void) {
return difftime(time(NULL), g_startTime);
}
static const char* statusName(int status) {
switch (status) {
case STATUS_DISCONNECTED: return "DISCONNECTED";
case STATUS_CONNECTING: return "CONNECTING";
case STATUS_CONNECTED: return "CONNECTED";
case STATUS_CONNECTION_ESTABLISHING:return "CONNECTION_ESTABLISHING";
case STATUS_CONNECTION_ESTABLISHED: return "CONNECTION_ESTABLISHED";
default: return "UNKNOWN";
}
}
/*
* Connection status change. This is where we expect to observe the secondary
* connections dropping back to DISCONNECTED with a non-zero error after ~10s.
*/
void onConnectStatusChangeEvent(uint64 serverConnectionHandlerID, int newStatus, unsigned int errorNumber) {
int idx = handlerIndex(serverConnectionHandlerID);
char* errormsg = NULL;
const char* errstr = "";
if (errorNumber != ERROR_ok && ts3client_getErrorMessage(errorNumber, &errormsg) == ERROR_ok)
errstr = errormsg;
printf("[%6.1fs] conn#%d (sch=%llu): %s (err=%u %s)\n",
elapsed(), idx, (unsigned long long)serverConnectionHandlerID,
statusName(newStatus), errorNumber, errstr);
fflush(stdout);
if (errormsg) ts3client_freeMemory(errormsg);
}
void onClientMoveTimeoutEvent(uint64 serverConnectionHandlerID, anyID clientID, uint64 oldChannelID, uint64 newChannelID, int visibility, const char* timeoutMessage) {
int idx = handlerIndex(serverConnectionHandlerID);
printf("[%6.1fs] conn#%d: clientID %u timed out: %s\n", elapsed(), idx, clientID, timeoutMessage ? timeoutMessage : "");
fflush(stdout);
}
void onServerErrorEvent(uint64 serverConnectionHandlerID, const char* errorMessage, unsigned int error, const char* returnCode, const char* extraMessage) {
int idx = handlerIndex(serverConnectionHandlerID);
printf("[%6.1fs] conn#%d: server error: %s %s\n", elapsed(), idx, errorMessage ? errorMessage : "", extraMessage ? extraMessage : "");
fflush(stdout);
}
char* programPath(char* programInvocation) {
char* path;
char* end;
int length;
char pathsep;
if (programInvocation == NULL) return strdup("");
#ifdef _WIN32
pathsep = '\\';
#else
pathsep = '/';
#endif
end = strrchr(programInvocation, pathsep);
if (!end) return strdup("");
length = (end - programInvocation) + 2;
path = (char*)malloc(length);
strncpy(path, programInvocation, length - 1);
path[length - 1] = 0;
return path;
}
int main(int argc, char** argv) {
unsigned int error;
char* version;
char* path;
/* --- arguments --- */
const char* host = (argc > 1) ? argv[1] : "localhost";
unsigned int port = (argc > 2) ? (unsigned int)atoi(argv[2]) : 9987;
const char* serverPassword = (argc > 3) ? argv[3] : "secret";
int numConnections = (argc > 4) ? atoi(argv[4]) : 3;
int runSeconds = (argc > 5) ? atoi(argv[5]) : 60;
int staggerMs = (argc > 6) ? atoi(argv[6]) : 0;
int shareIdentity = (argc > 7) ? atoi(argv[7]) : 0;
int localPort = (argc > 8) ? atoi(argv[8]) : 0;
if (numConnections < 1) numConnections = 1;
if (numConnections > MAX_CONNECTIONS) numConnections = MAX_CONNECTIONS;
printf("Multi-connection test: host=%s port=%u connections=%d runSeconds=%d staggerMs=%d shareIdentity=%d localPort=%d\n",
host, port, numConnections, runSeconds, staggerMs, shareIdentity, localPort);
/* Create struct for callback function pointers */
struct ClientUIFunctions funcs;
memset(&funcs, 0, sizeof(struct ClientUIFunctions));
funcs.onConnectStatusChangeEvent = onConnectStatusChangeEvent;
funcs.onClientMoveTimeoutEvent = onClientMoveTimeoutEvent;
funcs.onServerErrorEvent = onServerErrorEvent;
/* Initialize client lib with callbacks. Resource path points to the dir
* containing the soundbackends (next to the executable). */
path = programPath(argv[0]);
error = ts3client_initClientLib(&funcs, NULL, LogType_FILE | LogType_CONSOLE, NULL, path);
free(path);
if (error != ERROR_ok) {
char* errormsg;
if (ts3client_getErrorMessage(error, &errormsg) == ERROR_ok) {
printf("Error initializing clientlib: %s\n", errormsg);
ts3client_freeMemory(errormsg);
}
return 1;
}
if ((error = ts3client_getClientLibVersion(&version)) == ERROR_ok) {
printf("Client lib version: %s\n", version);
ts3client_freeMemory(version);
}
g_startTime = time(NULL);
/* When shareIdentity is set, all connections reuse a single identity (as a
* real application that stores and reuses one identity would). This mirrors
* "multiple sessions from the same client" and is a prime suspect for the
* handshake hardening rejecting secondary sessions from the same uid. */
char* sharedIdentity = NULL;
if (shareIdentity) {
if ((error = ts3client_createIdentity(&sharedIdentity)) != ERROR_ok) {
printf("Error creating shared identity: %u\n", error);
return 1;
}
}
/* Spawn N connection handlers and connect each to the same server.
* No audio devices are opened: this keeps the test focused on the
* network/command layer and avoids capture-device contention. */
for (int i = 0; i < numConnections; ++i) {
uint64 scHandlerID;
char* identity = NULL;
char nickname[64];
if ((error = ts3client_spawnNewServerConnectionHandler(localPort, &scHandlerID)) != ERROR_ok) {
printf("Error spawning server connection handler #%d: %u\n", i, error);
continue;
}
g_handlers[g_numHandlers++] = scHandlerID;
if (shareIdentity) {
identity = sharedIdentity;
} else if ((error = ts3client_createIdentity(&identity)) != ERROR_ok) {
printf("Error creating identity #%d: %u\n", i, error);
continue;
}
snprintf(nickname, sizeof(nickname), "multi_%d", i);
if ((error = ts3client_startConnection(scHandlerID, identity, host, port, nickname, NULL, "", serverPassword)) != ERROR_ok) {
printf("Error connecting conn#%d to server: %u\n", i, error);
} else {
printf("[%6.1fs] conn#%d (sch=%llu): startConnection issued as \"%s\"%s\n",
elapsed(), i, (unsigned long long)scHandlerID, nickname,
shareIdentity ? " [shared identity]" : "");
}
if (!shareIdentity && identity)
ts3client_freeMemory(identity);
/* Optionally wait before starting the next connection, so secondary
* sessions are initiated only after the previous one is up. */
if (staggerMs > 0 && i + 1 < numConnections) {
SLEEP(staggerMs);
}
}
if (sharedIdentity) ts3client_freeMemory(sharedIdentity);
printf("\n--- %d connection(s) started. Observing for %d seconds. ---\n", g_numHandlers, runSeconds);
printf("--- Watch for secondary connections dropping (resend/ping timeout). ---\n\n");
fflush(stdout);
/* Idle loop, printing a periodic status snapshot so drops are easy to spot. */
for (int t = 0; t < runSeconds; ++t) {
SLEEP(1000);
if ((t + 1) % 5 == 0) {
printf("[%6.1fs] status snapshot:\n", elapsed());
for (int i = 0; i < g_numHandlers; ++i) {
int status = 0;
ts3client_getConnectionStatus(g_handlers[i], &status);
printf(" conn#%d (sch=%llu): %s\n", i, (unsigned long long)g_handlers[i], statusName(status));
}
fflush(stdout);
}
}
/* Disconnect and clean up all connection handlers. */
printf("\nDisconnecting all connections...\n");
for (int i = 0; i < g_numHandlers; ++i) {
ts3client_stopConnection(g_handlers[i], "leaving");
}
SLEEP(500);
for (int i = 0; i < g_numHandlers; ++i) {
ts3client_destroyServerConnectionHandler(g_handlers[i]);
}
if ((error = ts3client_destroyClientLib()) != ERROR_ok) {
printf("Failed to destroy clientlib: %u\n", error);
return 1;
}
return 0;
}