Created
September 2, 2018 12:28
-
-
Save kizernis/4b85d352ea5166ee15c3c36e6ec6c294 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#if defined(__unix__) || defined(__unix) | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <malloc.h> | |
#include <netdb.h> | |
#include <sys/socket.h> | |
#include <netinet/tcp.h> | |
#include <arpa/inet.h> | |
#include <sys/time.h> | |
#include <signal.h> | |
#include <errno.h> | |
typedef int SOCKET; | |
#define INVALID_SOCKET -1 | |
#define SOCKET_ERROR -1 | |
#define SD_RECEIVE SHUT_RD | |
#define closesocket(s) close(s) | |
typedef int BOOL; | |
#define TRUE 1 | |
#define FALSE 0 | |
#else | |
#include <winsock2.h> | |
#pragma comment(lib, "ws2_32.lib") | |
#endif | |
long unsigned int time_stamp() | |
{ | |
#if defined(__unix__) || defined(__unix) | |
static struct timeval tv; | |
gettimeofday(&tv, NULL); | |
return tv.tv_sec * 1000 + tv.tv_usec / 1000; | |
#else | |
return GetTickCount(); | |
#endif | |
} | |
void number_format(long unsigned int n, char * buf) | |
{ | |
int n1 = 0, scale = 1; | |
while (n >= 1000) | |
{ n1 = n1 + scale * (n % 1000); n /= 1000; scale *= 1000; } | |
buf += sprintf(buf, "%lu", n); | |
while (scale != 1) | |
{ scale /= 1000; n = n1 / scale; n1 = n1 % scale; buf += sprintf(buf, ",%03lu", n); } | |
} | |
int g_exit_code = 0; | |
void print_error_message(const char * prefix, BOOL is_dns_error) | |
{ | |
char * buf; | |
int e; | |
#if !defined(__unix__) && !defined(__unix) | |
int len; | |
#endif | |
if (prefix) | |
{ fputs(prefix, stderr); fputs(" ", stderr); } | |
#if defined(__unix__) || defined(__unix) | |
if (is_dns_error) | |
{ e = h_errno; buf = (char *)hstrerror(e); } | |
else | |
{ | |
e = errno; if (e == EINPROGRESS) e = ETIMEDOUT; // connect() returns EINPROGRESS instead of ETIMEDOUT if timeout value is set by setsockopt() | |
buf = malloc(1024); strerror_r(e, buf, 1024); | |
} | |
fprintf(stderr, "failed (%d: %s).", e, buf); | |
if (!is_dns_error) free(buf); | |
#else | |
e = WSAGetLastError(); if (e == WSAECONNABORTED) e = WSAECONNRESET; // ? | |
len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, | |
NULL, e, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPTSTR)&buf, 256, NULL); | |
if (buf[len - 1] == ' ') | |
{ if (buf[len - 2] == '.') buf[len - 2] = '\0'; else buf[len - 1] = '\0'; } | |
else if (buf[len - 1] == '.') | |
buf[len - 1] = '\0'; | |
fprintf(stderr, "failed (%d: %s).", e, buf); | |
LocalFree(buf); | |
#endif | |
g_exit_code = 1; | |
} | |
int main(int argc, char ** argv) | |
{ | |
unsigned int upload_size = 1024 * 1024, download_limit = 0; | |
char *host_name = "cachefly.cachefly.net", *host_file_path = "1mb.test"; | |
BOOL is_url_set = FALSE; | |
SOCKET sock = INVALID_SOCKET; | |
struct sockaddr sa; | |
struct sockaddr_in * p_sa_in; | |
struct hostent * host; | |
unsigned char buf[1024]; | |
unsigned char * p_buf = buf; | |
unsigned int buf_len, full_request_len; | |
char msg[256], fmt1[32], fmt2[32]; | |
int msg_len1, msg_len2; | |
int transferred; | |
unsigned int transferred_total, kB_per_sec; | |
BOOL is_first_packet, is_header_sent; | |
long unsigned int t0, t1, t2, td, td_total; | |
#if defined(__unix__) || defined(__unix) | |
struct timeval timeout; | |
#else | |
DWORD timeout; | |
WSADATA wsa; | |
#endif | |
if (argc > 1) | |
{ | |
const char *err_duplicate_key = "Error: duplicate key\n\n", *err_number_missing = "Error: NUMBER missing\n\n", | |
*err_unknown_key = "Error: unknown key '%s'\n\n"; | |
const char *usage = | |
"Usage: speedt [KEY]... [URL] [KEY]...\n" | |
"Test (roughly) internet upload and download speeds.\n\n" | |
"Keys:\n" | |
" -u NUMBER, --upload-size=NUMBER Upload NUMBER bytes (default 1M)\n" | |
" -d NUMBER, --download-limit=NUMBER Limit downloading to NUMBER bytes\n" | |
" -h, --help Show this help message and exit\n" | |
" NUMBERs may have M or K postfixes for megabytes and kilobytes (e. g. 5M, 300K)\n\n" | |
"URL must point to a webserver which can take the POST data of the specified upload size\n" | |
" (default http://cachefly.cachefly.net/1mb.test)\n\n" | |
"Examples:\n" | |
" speedt -u 7.5m http://speed.kubtel.ru/speedtest/random2000x2000.jpg\n" | |
" speedt -u 61.1m http://ftp.dlink.ru/pub/vpn/robot.mpg\n" | |
" speedt -u 20m -d 20m http://ipv4.download.thinkbroadband.com/1GB.zip\n"; | |
int i; char * a; | |
BOOL is_upload_size_set = FALSE, is_download_limit_set = FALSE; // is_url_set = FALSE | |
char *upload_size_string, *download_limit_string; | |
BOOL is_pending_upload_value = FALSE, is_pending_download_value = FALSE; | |
for (i = 1; i < argc; i++) | |
{ | |
a = argv[i]; | |
if (a[0] == '-') | |
{ | |
if (is_pending_upload_value || is_pending_download_value) | |
{ fputs(err_number_missing, stderr); fputs(usage, stderr); return 1; } | |
if (a[1] == '-') | |
{ | |
if (strncmp(&a[2], "upload-size=", 12) == 0) | |
{ | |
if (is_upload_size_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_upload_size_set = TRUE; | |
upload_size_string = &a[14]; | |
} | |
else if (strncmp(&a[2], "download-limit=", 15) == 0) | |
{ | |
if (is_download_limit_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_download_limit_set = TRUE; | |
download_limit_string = &a[17]; | |
} | |
else if (strcmp(&a[2], "help") == 0) | |
{ fputs(usage, stdout); return 0; } | |
else | |
{ char * p = strchr(&a[2], '='); if (p) *p = '\0'; fprintf(stderr, err_unknown_key, &a[2]); fputs(usage, stderr); return 1; } | |
} | |
else if (a[2] != '\0') | |
{ char * p = strchr(&a[1], '='); if (p) *p = '\0'; fprintf(stderr, "Error: long key '%s' without '--' prefix\n\n", &a[1]); fputs(usage, stderr); return 1; } | |
else | |
{ | |
switch (a[1]) | |
{ | |
case 'u': | |
if (is_upload_size_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_upload_size_set = TRUE; | |
is_pending_upload_value = TRUE; | |
break; | |
case 'd': | |
if (is_download_limit_set) { fputs(err_duplicate_key, stderr); fputs(usage, stderr); return 1; } else is_download_limit_set = TRUE; | |
is_pending_download_value = TRUE; | |
break; | |
case 'h': | |
fputs(usage, stdout); return 0; | |
default: | |
fprintf(stderr, err_unknown_key, &a[1]); fputs(usage, stderr); return 1; | |
} | |
} | |
} | |
else | |
{ | |
if (is_pending_upload_value) | |
{ is_pending_upload_value = FALSE; upload_size_string = a; } | |
else if (is_pending_download_value) | |
{ is_pending_download_value = FALSE; download_limit_string = a; } | |
else if (is_url_set) | |
{ fprintf(stderr, "Error: unexpected argument: '%s'\n\n", a); fputs(usage, stderr); return 1; } | |
else | |
{ | |
BOOL url_has_prefix = (strncmp(a, "http://", 7) == 0); | |
if (!url_has_prefix && strstr(a, "://")) // TODO: check if "://" is in URI reference | |
{ fputs("Error: only HTTP protocol is supported\n", stderr); return 1; } | |
host_name = url_has_prefix ? &a[7] : a; host_file_path = strchr(host_name, '/'); | |
if (host_file_path == host_name) | |
{ fprintf(stderr, "Error: incorrect URL '%s'\n", a); return 1; } | |
if (host_file_path) { *host_file_path = '\0'; host_file_path++; } else host_file_path = ""; | |
is_url_set = TRUE; | |
} | |
} | |
} | |
if (is_pending_upload_value || is_pending_download_value) | |
{ fputs(err_number_missing, stderr); fputs(usage, stderr); return 1; } | |
for (i = 1; i <= 2; i++) | |
{ | |
char * s; unsigned int * p_n; | |
unsigned int len; | |
double multiplier; | |
double number; char * end_ptr; | |
if (i == 1) | |
{ if (!is_upload_size_set) continue; s = upload_size_string; p_n = &upload_size; } | |
else | |
{ if (!is_download_limit_set) break; s = download_limit_string; p_n = &download_limit; } // GCC shows a warning here | |
len = strlen(s); multiplier = 1; | |
if (s[len - 1] == 'm' || s[len - 1] == 'M') | |
{ s[len - 1] = '\0'; len--; multiplier = 1024 * 1024; } | |
else if (s[len - 1] == 'k' || s[len - 1] == 'K') | |
{ s[len - 1] = '\0'; len--; multiplier = 1024; } | |
while (len && (s[len - 1] == ' ' || s[len - 1] == '\t')) | |
{ s[len - 1] = '\0'; len--; } | |
number = strtod(s, &end_ptr); | |
if (*end_ptr != '\0' || number < 0 || number * multiplier > 1024 * 1024 * 1024) | |
{ fputs("Error: NUMBER must be a numeric value from 0 to 1,073,741,824\n\n", stderr); fputs(usage, stderr); return 1; } | |
*p_n = (unsigned int)(number * multiplier); | |
} | |
} | |
#if defined(__unix__) || defined(__unix) | |
timeout.tv_sec = 5; timeout.tv_usec = 0; | |
signal(SIGPIPE, SIG_IGN); | |
#else | |
timeout = 5000; | |
WSAStartup(0x0202, &wsa); | |
#endif | |
memset(&sa, 0, sizeof(struct sockaddr)); | |
p_sa_in = (struct sockaddr_in *)&sa; | |
p_sa_in->sin_family = AF_INET; p_sa_in->sin_port = htons(80); | |
p_sa_in->sin_addr.s_addr = inet_addr(host_name); | |
setvbuf(stdout, NULL, _IONBF, BUFSIZ); | |
if (!is_url_set) | |
printf("URL: http://%s/%s\n", host_name, host_file_path); | |
if (p_sa_in->sin_addr.s_addr == INADDR_NONE) | |
{ | |
fputs("Domain name resolving... ", stdout); | |
t1 = time_stamp(); | |
if (!(host = gethostbyname(host_name))) // TODO: check host->h_addrtype | |
{ print_error_message(NULL, TRUE); goto exit; } | |
memcpy(&(p_sa_in->sin_addr.s_addr), host->h_addr, host->h_length); // host_name = host->h_name | |
t2 = time_stamp(); number_format(t2 - t1, fmt1); | |
printf("%s ms\n", fmt1); | |
} | |
if (INVALID_SOCKET == (sock = socket(AF_INET, SOCK_STREAM, 0))) | |
{ print_error_message("Socket creating", FALSE); goto exit; } | |
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (unsigned char *)&timeout, sizeof(timeout)); | |
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (unsigned char *)&timeout, sizeof(timeout)); | |
printf("Connecting to %s... ", inet_ntoa(p_sa_in->sin_addr)); | |
t1 = time_stamp(); | |
if (SOCKET_ERROR == connect(sock, &sa, sizeof(struct sockaddr))) | |
{ print_error_message(NULL, FALSE); goto exit; } | |
t2 = time_stamp(); number_format(t2 - t1, fmt1); | |
printf("%s ms\n", fmt1); | |
buf_len = sprintf((char *)buf, "POST /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: speedt\r\nContent-Length: %u\r\n\r\n", host_file_path, host_name, upload_size); | |
full_request_len = buf_len + upload_size; | |
transferred_total = 0; | |
is_header_sent = FALSE; | |
t0 = t1 = t2; msg_len1 = 0; is_first_packet = TRUE; | |
for (;;) | |
{ | |
if (SOCKET_ERROR == (transferred = send(sock, p_buf, buf_len, 0))) | |
{ if (transferred_total) fputs("\n", stdout); print_error_message("Uploading", FALSE); goto exit; } | |
transferred_total += transferred; | |
t2 = time_stamp(); td = t2 - t1; | |
if (td >= 200 || is_first_packet || transferred_total == full_request_len) | |
{ | |
if (is_first_packet) is_first_packet = FALSE; | |
td_total = t2 - t0; | |
kB_per_sec = transferred_total / (td_total ? td_total : 1) * 1000 / 1024; | |
number_format(transferred_total, fmt1); number_format(kB_per_sec, fmt2); | |
msg_len2 = sprintf(msg, "Uploading... %s bytes in %.3f sec (%s kB/s or %.2f Mb/s)", fmt1, (float)td_total / 1000, fmt2, (float)kB_per_sec / 125); | |
printf("%-*s\r", msg_len1, msg); msg_len1 = msg_len2; | |
t1 = t2; | |
} | |
if ((unsigned int)transferred < buf_len) | |
{ buf_len -= transferred; if (!is_header_sent) p_buf = buf + transferred; } | |
else | |
{ | |
if (!is_header_sent) | |
{ is_header_sent = TRUE; if (p_buf != buf) p_buf = buf; memset(buf, '\0', 1024); buf_len = (upload_size > 1024 ? 1024 : upload_size); } | |
else if (transferred_total < full_request_len) | |
buf_len = (transferred_total + 1024 > full_request_len ? full_request_len - transferred_total : 1024); | |
else | |
break; | |
} | |
} | |
fputs("\n", stdout); | |
transferred_total = 0; | |
t0 = t1; msg_len1 = 0; is_first_packet = TRUE; | |
for (;;) | |
{ | |
buf_len = (download_limit && sizeof(buf) > download_limit - transferred_total ? download_limit - transferred_total : sizeof(buf)); | |
if (SOCKET_ERROR == (transferred = recv(sock, buf, buf_len, 0))) | |
{ if (transferred_total) fputs("\n", stdout); print_error_message("Downloading", FALSE); goto exit; } | |
if (buf_len != sizeof(buf)) | |
shutdown(sock, SD_RECEIVE); | |
if (transferred) | |
transferred_total += transferred; | |
t2 = time_stamp(); td = t2 - t1; | |
if (td >= 200 || is_first_packet || !transferred || buf_len != sizeof(buf)) | |
{ | |
if (is_first_packet) is_first_packet = FALSE; | |
td_total = t2 - t0; | |
kB_per_sec = transferred_total / (td_total ? td_total : 1) * 1000 / 1024; | |
number_format(transferred_total, fmt1); number_format(kB_per_sec, fmt2); | |
msg_len2 = sprintf(msg, "Downloading... %s bytes in %.3f sec (%s kB/s or %.2f Mb/s)", fmt1, (float)td_total / 1000, fmt2, (float)kB_per_sec / 125); | |
printf("%-*s\r", msg_len1, msg); msg_len1 = msg_len2; | |
if (!transferred || buf_len != sizeof(buf)) | |
break; | |
t1 = t2; | |
} | |
} | |
exit: | |
fputs("\n", stdout); | |
if (sock != INVALID_SOCKET) | |
closesocket(sock); | |
#if !defined(__unix__) && !defined(__unix) | |
WSACleanup(); | |
#endif | |
return g_exit_code; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment