Skip to content

Instantly share code, notes, and snippets.

@bplaat
Created November 10, 2023 16:04
Show Gist options
  • Save bplaat/91840b97b740e6b2f92dfb79564799a4 to your computer and use it in GitHub Desktop.
Save bplaat/91840b97b740e6b2f92dfb79564799a4 to your computer and use it in GitHub Desktop.
A bare bones websocket client written in C
#include <stdio.h>
#include <stdint.h>
#include <time.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#endif
#define MAX_BUFFER_SIZE 4096
int create_socket()
{
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
fprintf(stderr, "Failed to initialize winsock\n");
return -1;
}
#endif
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
perror("Error creating socket");
return -1;
}
return sockfd;
}
int connect_to_server(int sockfd, const char *hostname, int port)
{
struct hostent *server = gethostbyname(hostname);
if (server == NULL)
{
fprintf(stderr, "Error, no such host\n");
return -1;
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr = *((struct in_addr *)server->h_addr);
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("Error connecting to server");
return -1;
}
return 0;
}
#define KEY_LENGTH 16
char *generate_websocket_key()
{
char *key = (char *)malloc(KEY_LENGTH);
srand((uint32_t)time(NULL));
for (int i = 0; i < KEY_LENGTH; ++i)
{
key[i] = rand() % 256;
}
return key;
}
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char* base64_encode(const char* asciiString, size_t asciiLen) {
size_t base64Len = 4 * ((asciiLen + 2) / 3) + 1;
char* base64String = (char*)malloc(base64Len);
size_t i = 0, j = 0;
while (i < asciiLen) {
unsigned char octet1 = (unsigned char)asciiString[i++];
unsigned char sextet1 = octet1 >> 2;
base64String[j++] = base64_chars[sextet1];
if (i < asciiLen) {
unsigned char octet2 = (unsigned char)asciiString[i++];
unsigned char sextet2 = ((octet1 & 0x03) << 4) | (octet2 >> 4);
base64String[j++] = base64_chars[sextet2];
if (i < asciiLen) {
unsigned char octet3 = (unsigned char)asciiString[i++];
unsigned char sextet3 = ((octet2 & 0x0F) << 2) | (octet3 >> 6);
base64String[j++] = base64_chars[sextet3];
base64String[j++] = base64_chars[octet3 & 0x3F];
} else {
base64String[j++] = base64_chars[(octet2 & 0x0F) << 2];
base64String[j++] = '=';
}
} else {
base64String[j++] = base64_chars[(octet1 & 0x03) << 4];
base64String[j++] = '=';
base64String[j++] = '=';
}
}
base64String[j] = '\0';
return base64String;
}
void send_http_request(int sockfd)
{
char *websocket_key = generate_websocket_key();
char *base64_key = base64_encode((const uint8_t *)websocket_key, KEY_LENGTH);
char request[512];
sprintf(request, "GET / HTTP/1.1\r\n"
"Host: localhost:8080\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Sec-WebSocket-Key: %s\r\n\r\n", base64_key);
if (send(sockfd, request, strlen(request), 0) < 0)
{
perror("Error sending request");
}
}
int main()
{
int sockfd = create_socket();
if (sockfd < 0)
{
return 1;
}
if (connect_to_server(sockfd, "localhost", 8080) < 0)
{
#ifdef _WIN32
closesocket(sockfd);
#else
close(sockfd);
#endif
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}
send_http_request(sockfd);
char buffer[MAX_BUFFER_SIZE];
recv(sockfd, buffer, sizeof(buffer), 0);
for (;;)
{
recv(sockfd, buffer, sizeof(buffer), 0);
uint8_t frame_type = buffer[0] & 15;
// When binary frame
if (frame_type == 2)
{
uint8_t *message;
if ((buffer[1] & 127) == 126) {
message = &buffer[2 + 2];
} else if ((buffer[1] & 127) == 127) {
message = &buffer[2 + 4];
} else {
message = &buffer[2];
}
printf("type = 0x%02x\n", message[0]);
}
}
#ifdef _WIN32
closesocket(sockfd);
WSACleanup();
#else
close(sockfd);
#endif
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment