iOS simple TCP connection example
@Mohamad Chami's example was really great, tried to write a Swift version of it
GitHubLink
class SocketDataManager: NSObject, StreamDelegate {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
var inputStream: InputStream?
var outputStream: OutputStream?
var messages = [AnyHashable]()
weak var uiPresenter :PresenterProtocol!
init(with presenter:PresenterProtocol){
self.uiPresenter = presenter
}
func connectWith(socket: DataSocket) {
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (socket.ipAddress! as CFString), UInt32(socket.port), &readStream, &writeStream)
messages = [AnyHashable]()
open()
}
func disconnect(){
close()
}
func open() {
print("Opening streams.")
outputStream = writeStream?.takeRetainedValue()
inputStream = readStream?.takeRetainedValue()
outputStream?.delegate = self
inputStream?.delegate = self
outputStream?.schedule(in: RunLoop.current, forMode: .defaultRunLoopMode)
inputStream?.schedule(in: RunLoop.current, forMode: .defaultRunLoopMode)
outputStream?.open()
inputStream?.open()
}
func close() {
print("Closing streams.")
uiPresenter?.resetUIWithConnection(status: false)
inputStream?.close()
outputStream?.close()
inputStream?.remove(from: RunLoop.current, forMode: .defaultRunLoopMode)
outputStream?.remove(from: RunLoop.current, forMode: .defaultRunLoopMode)
inputStream?.delegate = nil
outputStream?.delegate = nil
inputStream = nil
outputStream = nil
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
print("stream event \(eventCode)")
switch eventCode {
case .openCompleted:
uiPresenter?.resetUIWithConnection(status: true)
print("Stream opened")
case .hasBytesAvailable:
if aStream == inputStream {
var dataBuffer = Array<UInt8>(repeating: 0, count: 1024)
var len: Int
while (inputStream?.hasBytesAvailable)! {
len = (inputStream?.read(&dataBuffer, maxLength: 1024))!
if len > 0 {
let output = String(bytes: dataBuffer, encoding: .ascii)
if nil != output {
print("server said: \(output ?? "")")
messageReceived(message: output!)
}
}
}
}
case .hasSpaceAvailable:
print("Stream has space available now")
case .errorOccurred:
print("\(aStream.streamError?.localizedDescription ?? "")")
case .endEncountered:
aStream.close()
aStream.remove(from: RunLoop.current, forMode: .defaultRunLoopMode)
print("close stream")
uiPresenter?.resetUIWithConnection(status: false)
default:
print("Unknown event")
}
}
func messageReceived(message: String){
uiPresenter?.update(message: "server said: \(message)")
print(message)
}
func send(message: String){
let response = "msg:\(message)"
let buff = [UInt8](message.utf8)
if let _ = response.data(using: .ascii) {
outputStream?.write(buff, maxLength: buff.count)
}
}
}
class ViewController: UIViewController {
var socketConnector:SocketDataManager!
@IBOutlet weak var ipAddressField: UITextField!
@IBOutlet weak var portField: UITextField!
@IBOutlet weak var messageField: UITextField!
@IBOutlet weak var messageHistoryView: UITextView!
@IBOutlet weak var connectBtn: UIButton!
@IBOutlet weak var sendBtn: UIButton!
@IBOutlet weak var statusView: UIView!
@IBOutlet weak var statusLabl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
socketConnector = SocketDataManager(with: self)
resetUIWithConnection(status: false)
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func connect(){
//http://localhost:50694/
guard let ipAddr = ipAddressField.text, let portVal = portField.text else {
return
}
let soc = DataSocket(ip: ipAddr, port: portVal)
socketConnector.connectWith(socket: soc)
}
@IBAction func send(){
guard let msg = messageField.text else {
return
}
send(message: msg)
messageField.text = ""
}
func send(message: String){
socketConnector.send(message: message)
update(message: "me:\(message)")
}
}
extension ViewController: PresenterProtocol{
func resetUIWithConnection(status: Bool){
ipAddressField.isEnabled = !status
portField.isEnabled = !status
messageField.isEnabled = status
connectBtn.isEnabled = !status
sendBtn.isEnabled = status
if (status){
updateStatusViewWith(status: "Connected")
}else{
updateStatusViewWith(status: "Disconnected")
}
}
func updateStatusViewWith(status: String){
statusLabl.text = status
}
func update(message: String){
if let text = messageHistoryView.text{
let newText = """
\(text)
\(message)
"""
messageHistoryView.text = newText
}else{
let newText = """
\(message)
"""
messageHistoryView.text = newText
}
let myRange=NSMakeRange(messageHistoryView.text.count-1, 0);
messageHistoryView.scrollRangeToVisible(myRange)
}
}
struct DataSocket {
let ipAddress: String!
let port: Int!
init(ip: String, port: String){
self.ipAddress = ip
self.port = Int(port)
}
}
https://github.com/swiftsocket/SwiftSocket Swift Socket library provide you a simple interface for socket based connection. Refer to this link and below sample.
let client = TCPClient(address: "www.apple.com", port: 80)
switch client.connect(timeout: 1) {
case .success:
switch client.send(string: "GET / HTTP/1.0\n\n" ) {
case .success:
guard let data = client.read(1024*10) else { return }
if let response = String(bytes: data, encoding: .utf8) {
print(response)
}
case .failure(let error):
print(error)
}
case .failure(let error):
print(error)
}
Heading
Those who are unable to receive data at the server side:
It is perhaps due to data encoding mechanism. @Mohamad Chami's answer works fine after changing the data encoding mechanism at the sendMessage
method as follows:
In his example, NSString
is converted to NSData
by:
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
Change the NSASCIIStringEncoding
to NSUTF8StringEncoding
. It is because at server's side (Oracle database server in my case), the data is received in modified UTF-8 encoding.
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