I’m working on a learning project where I’m trying to create a websocket server on macOS using objective-C and a client using swift. I’m able to send message from client to server and it’s getting through fine. After receiving a message on server, I want to send a message back to client. Message is UTF8 encoded. But I’m receiving some receiving garbled text on client(raw data not matching). Now 2 questions –
1)I can’t pin point as to where I’m making the mistake.
2)If there is a better way to build something like this?
Server side code
#import <Foundation/Foundation.h>
#import <Network/Network.h>
@interface WebSocketServer : NSObject
@property (nonatomic, retain) nw_listener_t listener;
@property (nonatomic, strong) NSNetService *netService;
- (void)startServerOnPort:(uint16_t)port;
@end
@implementation WebSocketServer
- (void)startServerOnPort:(uint16_t)port {
//configure connection parameters such as tcp, udp and whether you want tls or not
nw_parameters_configure_protocol_block_t configure_tls = NW_PARAMETERS_DISABLE_PROTOCOL;
nw_parameters_t parameters = nw_parameters_create_secure_tcp(
configure_tls,
NW_PARAMETERS_DEFAULT_CONFIGURATION
);
//create a listener on a specific port
self.listener = nw_listener_create_with_port([NSString stringWithFormat:@"%d", port].UTF8String, parameters);
//set a queue
nw_listener_set_queue(self.listener, dispatch_get_main_queue());
//set state change handler
nw_listener_set_state_changed_handler(self.listener, ^(nw_listener_state_t state, nw_error_t error) {
// … your state changed handler …
switch (state) {
/*! @const nw_listener_state_invalid The state of the listener is not valid. This state will never be delivered in the listener's state update handler, and can be treated as an unexpected value. */
case nw_listener_state_invalid:
NSLog(@"nw_listener_set_state_changed_handler:nw_listener_state_invalid");
break;
/*! @const nw_listener_state_waiting The listener is waiting for a usable network before being able to receive connections */
case nw_listener_state_waiting:
NSLog(@"nw_listener_set_state_changed_handler:nw_listener_state_waiting");
break;
/*! @const nw_listener_state_ready The listener is ready and able to accept incoming connections */
case nw_listener_state_ready:
NSLog(@"nw_listener_set_state_changed_handler:nw_listener_state_ready");
break;
/*! @const nw_listener_state_failed The listener has irrecoverably closed or failed */
case nw_listener_state_failed:
NSLog(@"nw_listener_set_state_changed_handler:nw_listener_state_failed");
break;
/*! @const nw_listener_state_cancelled The listener has been cancelled by the caller */
case nw_listener_state_cancelled:
NSLog(@"nw_listener_set_state_changed_handler:nw_listener_state_cancelled");
break;
default:
break;
}
});
//set new connection handler
nw_listener_set_new_connection_handler(self.listener, ^(nw_connection_t connection) {
NSLog(@"New connection received");
nw_connection_set_queue(connection, dispatch_get_main_queue());
nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t _Nullable error) {
switch (state) {
/*! @const nw_connection_state_invalid The state of the connection is not valid. This state will never be delivered in the connection's state update handler, and can be treated as an unexpected value. */
case nw_connection_state_invalid:
NSLog(@"nw_listener_set_new_connection_handler:nw_connection_state_invalid");
break;
/*! @const nw_connection_state_waiting The connection is waiting for a usable network before re-attempting */
case nw_connection_state_waiting:
NSLog(@"nw_listener_set_new_connection_handler:nw_connection_state_waiting");
break;
/*! @const nw_connection_state_preparing The connection is in the process of establishing */
case nw_connection_state_preparing:
NSLog(@"nw_listener_set_new_connection_handler:nw_connection_state_preparing");
break;
/*! @const nw_connection_state_ready The connection is established and ready to send and receive data upon */
case nw_connection_state_ready:
NSLog(@"nw_listener_set_new_connection_handler:nw_connection_state_ready");
[self logClientDetails:connection];
[self receiveMessageOnConnection:connection];
break;
/*! @const nw_connection_state_failed The connection has irrecoverably closed or failed */
case nw_connection_state_failed:
NSLog(@"nw_listener_set_new_connection_handler:nw_connection_state_failed");
break;
/*! @const nw_connection_state_cancelled The connection has been cancelled by the caller */
case nw_connection_state_cancelled:
NSLog(@"nw_listener_set_new_connection_handler:nw_connection_state_cancelled");
break;
default:
NSLog(@"nw_listener_set_new_connection_handler:default");
break;
}
});
NSLog(@"Starting connection");
nw_connection_start(connection);
});
//start listening
nw_listener_start(self.listener);
}
- (void)logClientDetails:(nw_connection_t)connection {
nw_endpoint_t endpoint = nw_connection_copy_endpoint(connection);
if (nw_endpoint_get_type(endpoint) == nw_endpoint_type_host) {
const char *hostname = nw_endpoint_get_hostname(endpoint);
const char *port = nw_endpoint_copy_port_string(endpoint);
NSLog(@"Client connected from %s:%s", hostname, port);
free((void *)hostname); // If needed to free, but usually ARC should handle
free((void *)port); // If needed to free, but usually ARC should handle
}
}
- (void)receiveMessageOnConnection:(nw_connection_t)connection {
nw_connection_receive(connection, 1, UINT32_MAX, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t receive_error) {
if (content) {
const void *buffer;
size_t buffer_size;
dispatch_data_t data = dispatch_data_create_map(content, &buffer, &buffer_size);
NSString *message = [[NSString alloc] initWithBytes:buffer length:buffer_size encoding:NSUTF8StringEncoding];
NSLog(@"Received message: %@", message);
[self sendMessage:@"Message received" onConnection:connection];
// Respond to message if needed
[self receiveMessageOnConnection:connection]; // Continue receiving more messages
} else if (receive_error) {
NSLog(@"Failed to receive message with error: %@", receive_error);
}
});
}
- (void)sendMessage:(NSString *)message onConnection:(nw_connection_t)connection {
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
dispatch_data_t content = dispatch_data_create(data.bytes, data.length, dispatch_get_main_queue(), ^{
// No-op
});
// Log the raw data in hexadecimal format
NSMutableString *hexString = [NSMutableString stringWithCapacity:data.length * 2];
const unsigned char *dataBytes = (const unsigned char *)data.bytes;
for (NSInteger i = 0; i < data.length; i++) {
[hexString appendFormat:@"%02x", dataBytes[i]];
}
NSLog(@"Raw data to be sent: %@", hexString);
nw_connection_send(connection, content, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t _Nullable send_error) {
if (send_error) {
NSLog(@"Failed to send message with error: %@", send_error);
} else {
NSLog(@"Message sent: %@", message);
}
});
}
@end
WebSocketServer *server = [[WebSocketServer alloc] init];
uint16_t port = 12345;
[server startServerOnPort:port];
[[NSRunLoop currentRunLoop] run]; // Keep the application running
Server side console logs
2024-05-25 23:45:28.693748+0530 WebSocketServerAppObjectiveC[89294:2585208] nw_listener_set_state_changed_handler:nw_listener_state_ready
2024-05-25 23:45:30.297234+0530 WebSocketServerAppObjectiveC[89294:2585208] New connection received
2024-05-25 23:45:30.297275+0530 WebSocketServerAppObjectiveC[89294:2585208] Starting connection
2024-05-25 23:45:30.297506+0530 WebSocketServerAppObjectiveC[89294:2585208] nw_listener_set_new_connection_handler:nw_connection_state_preparing
2024-05-25 23:45:30.297699+0530 WebSocketServerAppObjectiveC[89294:2585208] nw_listener_set_new_connection_handler:nw_connection_state_ready
2024-05-25 23:45:30.297896+0530 WebSocketServerAppObjectiveC[89294:2585208] Received message: Hello, server!
2024-05-25 23:45:30.297934+0530 WebSocketServerAppObjectiveC[89294:2585208] Raw data to be sent: 4d657373616765207265636569766564
2024-05-25 23:45:30.298040+0530 WebSocketServerAppObjectiveC[89294:2585208] Message sent: Message received
Client side code
import Foundation
import Network
class WebSocketClient {
var connection: NWConnection?
func startConnection(to host: String, port: UInt16) {
let parameters = NWParameters.tcp
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: NWEndpoint.Port(rawValue: port)!)
connection = NWConnection(to: endpoint, using: parameters)
connection?.stateUpdateHandler = { state in
switch state {
case .ready:
print("Client connection ready")
self.sendMessage("Hello, server!")
self.receiveMessage()
case .failed(let error):
print("Client connection failed: (error)")
case .waiting(let error):
print("Client connection waiting: (error)")
case .cancelled:
print("Client connection cancelled")
default:
break
}
}
connection?.start(queue: .main)
}
func sendMessage(_ message: String) {
guard let connection = connection else { return }
let data = message.data(using: .utf8)!
connection.send(content: data, completion: .contentProcessed { error in
if let error = error {
print("Failed to send message: (error)")
} else {
print("Message sent")
}
})
}
func receiveMessage() {
guard let connection = connection else { return }
connection.receive(minimumIncompleteLength: 1, maximumLength: Int(UInt32.max)) { [weak self]data, context, isComplete, error in
guard let weakSelf = self else { return }
if let data = data, !data.isEmpty {
let rawData = data.map { String(format: "%02x", $0) }.joined()
print("Received raw data: (rawData)")
if let message = String(data: data, encoding: .utf8) {
print("Received message: (message)")
// Continue to receive more messages
weakSelf.receiveMessage()
} else {
print("Failed to decode received message as UTF-8")
}
}
if let error = error {
print("Failed to receive message: (error)")
}
}
}
func stopConnection() {
connection?.cancel()
}
}
let client = WebSocketClient()
client.startConnection(to: "localhost", port: 12345)
RunLoop.main.run()
Client side console logs
Client connection ready
Message sent
Received raw data: d8fda4a2018017445cd572f201000000
Failed to decode received message as UTF-8