NWConnection timeout
You can set connection timed out by NWProtocolTCP.Options
lazy var tcpOptions: NWProtocolTCP.Options = {
let options = NWProtocolTCP.Options()
options.connectionTimeout = 5 // connection timed out
return options
}()
lazy var parames: NWParameters = {
let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
isOption.version = .v4
}
parames.preferNoProxies = true
return parames
}()
lazy var connection: NWConnection = {
let connection = NWConnection(host: "x.x.x.x", port: xxxx, using: self.parames)
return connection
}()
When trigger the timed out event, the .waiting state will call back.
2021-08-30 23:05:17.xxxxxx+xxxx Demo[xxxx:xxxxxx] [connection] nw_socket_handle_socket_event [C1:1] Socket SO_ERROR [60: Operation timed out]
The connection is waiting for a network path change with: POSIXErrorCode: Operation timed out
reference from Developer Fourms
Detecting a client disconnecting with UDP using Network.framework
A UDP server gets no information via the network about whether a UDP client has disconnected or gone away, unless perhaps the client explicitly sends a some sort of additional message (via UDP, TCP, or other side channel) regarding its disconnect status. So there's nothing to change the NWConnection state (except perhaps some sort of problem with the server itself).
Perhaps the server can assume a disconnect after some agreed upon or negotiated timeout time has passed without some sort of activity. Or number of packets, bytes of data. Etc. And close the connection itself.
Swift UDP Connection Issue
In your function receive
you are using the NWConnection.receiveMessage
if you check the documentation here:
https://developer.apple.com/documentation/network/nwconnection/3020638-receivemessage
You'll see that it schedules a single receive completion handler. That means that you'll have to do something to trigger it again. What I normally do is have a function like:
private func setupReceive() {
connection.receive(minimumIncompleteLength: 1, maximumLength: MTU) { (data, _, isComplete, error) in
if let data = data, !data.isEmpty {
let message = String(data: data, encoding: .utf8)
print("connection \(self.id) did receive, data: \(data as NSData) string: \(message ?? "-")")
self.send(data: data)
}
if isComplete {
self.connectionDidEnd()
} else if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive() // HERE I SET THE RECEIVE AGAIN
}
}
}
That way, after processing the read, you end up setting up another single receive completion handler.
If you want to see a full example, you can check my article on using Network.framework
here:
https://rderik.com/blog/building-a-server-client-aplication-using-apple-s-network-framework/
The example uses TCP instead of UDP, but it should give a general idea.
iOS simple TCP connection example
SocketConnectionVC.h
#import <UIKit/UIKit.h>
@interface SocketConnectionVC : UIViewController<NSStreamDelegate>
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSInputStream *inputStream;
NSOutputStream *outputStream;
NSMutableArray *messages;
}
@property (weak, nonatomic) IBOutlet UITextField *ipAddressText;
@property (weak, nonatomic) IBOutlet UITextField *portText;
@property (weak, nonatomic) IBOutlet UITextField *dataToSendText;
@property (weak, nonatomic) IBOutlet UITextView *dataRecievedTextView;
@property (weak, nonatomic) IBOutlet UILabel *connectedLabel;
@end
SocketConnectionVC.m
#import "SocketConnectionVC.h"
@interface SocketConnectionVC ()
@end
@implementation SocketConnectionVC
- (void)viewDidLoad {
[super viewDidLoad];
_connectedLabel.text = @"Disconnected";
}
- (IBAction) sendMessage {
NSString *response = [NSString stringWithFormat:@"msg:%@", _dataToSendText.text];
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
}
- (void) messageReceived:(NSString *)message {
[messages addObject:message];
_dataRecievedTextView.text = message;
NSLog(@"%@", message);
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
NSLog(@"stream event %lu", streamEvent);
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(@"Stream opened");
_connectedLabel.text = @"Connected";
break;
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream)
{
uint8_t buffer[1024];
NSInteger len;
while ([inputStream hasBytesAvailable])
{
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output)
{
NSLog(@"server said: %@", output);
[self messageReceived:output];
}
}
}
}
break;
case NSStreamEventHasSpaceAvailable:
NSLog(@"Stream has space available now");
break;
case NSStreamEventErrorOccurred:
NSLog(@"%@",[theStream streamError].localizedDescription);
break;
case NSStreamEventEndEncountered:
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
_connectedLabel.text = @"Disconnected";
NSLog(@"close stream");
break;
default:
NSLog(@"Unknown event");
}
}
- (IBAction)connectToServer:(id)sender {
NSLog(@"Setting up connection to %@ : %i", _ipAddressText.text, [_portText.text intValue]);
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef) _ipAddressText.text, [_portText.text intValue], &readStream, &writeStream);
messages = [[NSMutableArray alloc] init];
[self open];
}
- (IBAction)disconnect:(id)sender {
[self close];
}
- (void)open {
NSLog(@"Opening streams.");
outputStream = (__bridge NSOutputStream *)writeStream;
inputStream = (__bridge NSInputStream *)readStream;
[outputStream setDelegate:self];
[inputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
[inputStream open];
_connectedLabel.text = @"Connected";
}
- (void)close {
NSLog(@"Closing streams.");
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream setDelegate:nil];
[outputStream setDelegate:nil];
inputStream = nil;
outputStream = nil;
_connectedLabel.text = @"Disconnected";
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
snapshot for ui of this SocketConnectionVC
and Follow these steps
1- input the ip on ipAdress textfield
2- input the port on port textfield
3- press connect button
4- (make sure the ip address and port is correct and the open of stream is fine. you can show the status of stream on console of Xcode)
5- input data to send to server
6- press send button
7- you can show the received message from server on the text view above connect button
TCP Socket no connection timeout
Pulling the network cable will not break a TCP connection(1) though it will disrupt communications. You can plug the cable back in and once IP connectivity is established, all back-data will move. This is what makes TCP reliable, even on cellular networks.
When TCP sends data, it expects an ACK in reply. If none comes within some amount of time, it re-transmits the data and waits again. The time it waits between transmissions generally increases exponentially.
After some number of retransmissions or some amount of total time with no ACK, TCP will consider the connection "broken". How many times or how long depends on your OS and its configuration but it typically times-out on the order of many minutes.
From Linux's tcp.7 man page:
tcp_retries2 (integer; default: 15; since Linux 2.2)
The maximum number of times a TCP packet is retransmitted in
established state before giving up. The default value is 15, which
corresponds to a duration of approximately between 13 to 30 minutes,
depending on the retransmission timeout. The RFC 1122 specified
minimum limit of 100 seconds is typically deemed too short.
This is likely the value you'll want to adjust to change how long it takes to detect if your connection has vanished.
(1) There are exceptions to this. The operating system, upon noticing a cable being removed, could notify upper layers that all connections should be considered "broken".
Related Topics
Sharing File Data Between Applications in Swift/Ios
Cannot Use Tabview on Swiftui, Watchos
Play Segment of Avaudiopcmbuffer
Coerced to Any' But Property Is of Type Uicolor
Moving Keyboard When Editing Multiple Textfields with Constraints Swift
How Have Multiple Init() with Swift
Stack Overflow When Defining Subscript on Ckrecord in Swift
Differencebetween Date.Addingtimeinterval(_:) and Date.Advanced(By:)
Swift. Is the (Absolutely) Sole Specific Advantage of Unowned Over Weak, Performance
Disable Bounce Scrolling for Wkwebview in MACos
Split String by Components and Keep Components in Place
How to Use Tabs to Evenly Space Out Description Strings in Swift
Swift: How to Fix Infinite Loop When Adding a Value to a Firebase Variable
Shows the Alert When Uitextfield's Are Full or Empty Swift
How to Animate Opacity Using Swift
How to Draw Dashed Line in Arkit (Scenekit) Like in the Measure App