(in objective-c, not swift), I have tried prepending the websocket options with nw_protocol_stack_prepend_application_protocol to my existing tls/tcp parameters but it is failing to upgrade the connection status and throwing an error. I cannot find any good examples or documentation on the Apple developer forums for this. Why do none of the functions have examples?
How do I upgrade nw_connection to a websocket connection?
it is failing to upgrade the connection status and throwing an error.
It’s hard to offer insight into that without knowing what the error is. Regardless, here’s a simple WebSocket client in C:
@import Foundation;
@import Network;
int main(int argc, char **argv) {
#pragma unused(argc)
#pragma unused(argv)
NSLog(@"will start");
nw_endpoint_t endpoint = nw_endpoint_create_url("ws://127.0.0.1:12345/test");
nw_parameters_t params = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);
nw_protocol_stack_t stack = nw_parameters_copy_default_protocol_stack(params);
nw_protocol_options_t ws = nw_ws_create_options(nw_ws_version_13);
nw_protocol_stack_prepend_application_protocol(stack, ws);
nw_connection_t connection = nw_connection_create(endpoint, params);
nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) {
NSLog(@"state did change, new: %d, error: %@", (int) state, error);
});
nw_connection_set_queue(connection, dispatch_get_main_queue());
nw_connection_start(connection);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 1000 * NSEC_PER_MSEC), 1000 * NSEC_PER_MSEC, 500 * NSEC_PER_MSEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"will send");
NSString * message = [NSString stringWithFormat:@"Hello Cruel World %@", [NSDate date]];
const char * messageC = message.UTF8String;
dispatch_data_t content = dispatch_data_create(messageC, strlen(messageC), dispatch_get_main_queue(), DISPATCH_DATA_DESTRUCTOR_DEFAULT);
nw_protocol_metadata_t metadata = nw_ws_create_metadata(nw_ws_opcode_text);
nw_content_context_t context = nw_content_context_create("send");
nw_content_context_set_metadata_for_protocol(context, metadata);
nw_connection_send(connection, content, context, true, ^(nw_error_t error) {
NSLog(@"did send, error: %@", error);
});
});
dispatch_activate(timer);
dispatch_main();
return EXIT_SUCCESS;
}
Annoyingly, my go-to WebSocket test site, websocket.org
, is no longer available so I ended up creating a simple server to match. That code is pasted in below.
If anyone has an alternative WebSocket test site, please post the details here. My code here only tests the Mac-to-Mac case, and I’ve been bitten by stuff like that before (for example, back in the day, certain Secure Transport features would work Mac-to-Mac but fail with third-party TLS stacks; or was it the other way around? :-).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
@import Foundation;
@import Network;
static void startReceive(nw_connection_t connection) {
nw_connection_receive(connection, 1, 65536, ^(dispatch_data_t content, nw_content_context_t context, bool isComplete, nw_error_t error) {
#pragma unused(context)
#pragma unused(isComplete)
if (error != NULL) {
NSLog(@"connection receive did fail, error: %@", error);
return;
}
if (content != NULL) {
size_t size = dispatch_data_get_size(content);
NSLog(@"connection did receive content, size: %zu", size);
}
startReceive(connection);
});
}
int main(int argc, char **argv) {
#pragma unused(argc)
#pragma unused(argv)
NSLog(@"will start");
NSMutableArray * connections = [[NSMutableArray alloc] init];
nw_parameters_t params = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);
nw_protocol_stack_t stack = nw_parameters_copy_default_protocol_stack(params);
nw_protocol_options_t ws = nw_ws_create_options(nw_ws_version_13);
nw_protocol_stack_prepend_application_protocol(stack, ws);
nw_endpoint_t localEndpoint = nw_endpoint_create_host("::", "12345");
nw_parameters_set_local_endpoint(params, localEndpoint);
nw_listener_t listener = nw_listener_create(params);
nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t error) {
NSLog(@"listener state did change, new: %d, error: %@", (int) state, error);
});
nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection) {
NSLog(@"listener did accept connection");
[connections addObject:connection];
nw_connection_set_state_changed_handler(connection, ^(nw_connection_state_t state, nw_error_t error) {
NSLog(@"connection state did change, new: %d, error: %@", (int) state, error);
});
nw_connection_set_queue(connection, dispatch_get_main_queue());
nw_connection_start(connection);
startReceive(connection);
});
nw_listener_set_queue(listener, dispatch_get_main_queue());
nw_listener_start(listener);
dispatch_main();
}
I’m working with WebSockets today and I wanted a Swift version of the above. I ended up porting it line-for-line, so I thought I’d share it here for the benefit of others (and, most importantly, Future Quinn™ :-).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
import Foundation
import Network
func main() {
print("will start")
let endpoint = NWEndpoint.url(URL(string: "ws://127.0.0.1:12345/test")!)
let params = NWParameters.tcp
let stack = params.defaultProtocolStack
let ws = NWProtocolWebSocket.Options(.version13)
stack.applicationProtocols.insert(ws, at: 0)
let connection = NWConnection(to: endpoint, using: params)
connection.stateUpdateHandler = { newState in
print("state did change, new: \(newState)")
}
connection.start(queue: .main)
let timer = DispatchSource.makeTimerSource(queue: .main)
timer.schedule(deadline: .now() + 1.0, repeating: 1.0)
timer.setEventHandler {
print("will send")
let message = Data("Hello Cruel World \(Date())".utf8)
let metadata = NWProtocolWebSocket.Metadata(opcode: .text)
let context = NWConnection.ContentContext(identifier: "send", metadata: [metadata])
connection.send(content: message, contentContext: context, isComplete: true, completion: NWConnection.SendCompletion.contentProcessed({ error in
print("did send, error: \(error.flatMap { "\($0)" } ?? "-")")
}))
}
timer.activate()
withExtendedLifetime(connection) {
dispatchMain()
}
}
main()
exit(EXIT_SUCCESS)
import Foundation
import Network
private func startReceive(connection: NWConnection) {
connection.receiveMessage { content, _, _, error in
if let error = error {
print("connection receive did fail, error: \(error)")
return
}
if let content = content {
print("connection did receive content, count: \(content.count)")
}
startReceive(connection: connection)
}
}
func main() throws {
print("will start")
var connections: [NWConnection] = []
let params = NWParameters.tcp
let stack = params.defaultProtocolStack
let ws = NWProtocolWebSocket.Options(.version13)
stack.applicationProtocols.insert(ws, at: 0)
let listener = try NWListener(using: params, on: 12345)
listener.stateUpdateHandler = { newState in
print("listener state did change, new: \(newState)")
}
listener.newConnectionHandler = { connection in
print("listener did accept connection")
connections.append(connection)
connection.stateUpdateHandler = { newState in
print("connection state did change, new: \(newState)")
}
connection.start(queue: .main)
startReceive(connection: connection)
}
listener.start(queue: .main)
withExtendedLifetime(listener) {
dispatchMain()
}
}
try! main()
it corresponds to POSIX error
ECONNREFUSED
That suggests a low-level connectivity problem. Notably, I’m not seeing that here in my office. Here’s what I did:
-
Using Xcode 13.2.1 on macOS 12.1, I created a new command-line tool project with the C client code from my first post (I assume you’re using C rather than Swift because you commented on my first post, not my second).
-
I changed
nw_endpoint_create_url("ws://127.0.0.1:12345/test")
tonw_endpoint_create_url("wss://echo.websocket.events/.ws")
. -
I changed
NW_PARAMETERS_DISABLE_PROTOCOL
toNW_PARAMETERS_DEFAULT_CONFIGURATION
, to enable TLS (my code used anws
URL, whereas your example is clearly intended to usewss
and hence you need TLS). -
I ran it. It printed:
2022-01-14 09:39:58.488498+0000 xxot[839:6135282] will start 2022-01-14 09:39:58.513357+0000 xxot[839:6136041] state did change, new: 2, error: (null) 2022-01-14 09:39:58.999719+0000 xxot[839:6136043] state did change, new: 3, error: (null) 2022-01-14 09:39:59.512637+0000 xxot[839:6136041] will send
As you can see, the connection went through just fine (state 3 is nw_connection_state_ready
).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"