I am new in Swift.
I am studying to write a pure swift app to be socket server.
code is as below
server = CFSocketCreate(kCFAllocatorDefault, AF_INET,
SOCK_STREAM, IPPROTO_TCP, callbackOpts.rawValue, {
(socket, type, address, data, info) in
print("type ->\(type)")
}, nil)
let size = MemoryLayout<sockaddr_in>.size
addr.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
addr.sin_family = sa_family_t(AF_INET); /* Address family */
addr.sin_port = in_port_t(10001); /* Or a specific port */
addr.sin_addr.s_addr = in_addr_t(INADDR_ANY);
let addrInPtr = withUnsafeMutablePointer(to: &addr) { ptr in
return ptr.withMemoryRebound(to: UInt8.self, capacity: size){
return $0
}
}
let sincfd = CFDataCreate(
kCFAllocatorDefault,
addrInPtr,
size)
let result = CFSocketSetAddress(server, sincfd)
switch result {
case .success:
print("xx sucess")
case .timeout:
print("xx timeout")
case .error:
print("xx error")
}
let runLoopSourceRef = CFSocketCreateRunLoopSource(
kCFAllocatorDefault, server, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(),
runLoopSourceRef, CFRunLoopMode.defaultMode)
I got "xx success" when binder.
I can not connect this server from client.
Does someone give me a hint what problem is and how to do a server?
I know there are some 3rd party library can do that,
but I am studying Swift and try to understand how to use CFNetwork.
I just found my problem.
It was caused by the byte order of port number.
Code should be
addr.sin_port = in_port_t(CFSwapInt16HostToBig(10001))
Related
I send messages to applications running on remote computers.
I send messages this ways.
c#:
void web_data_work()
{
try
{
TcpClient client = new TcpClient(address, port);
Byte[] data = Encoding.UTF8.GetBytes(string.Format("{0}", message));
NetworkStream stream = client.GetStream();
try
{
if (message != "")
{
stream.Write(data, 0, data.Length);
Byte[] readingData = new Byte[256];
String responseData = String.Empty;
StringBuilder completeMessage = new StringBuilder();
int numberOfBytesRead = 0;
do
{
numberOfBytesRead = stream.Read(readingData, 0, readingData.Length);
completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(readingData, 0, numberOfBytesRead));
}
while (stream.DataAvailable);
responseData = completeMessage.ToString();
this.Invoke((MethodInvoker)delegate ()
{
output_list.Items.Add(string.Format("Sended – {0}", responseData));
message = "";
});
}
}
finally
{
stream.Close();
client.Close();
}
}
catch
{
this.Invoke((MethodInvoker)delegate ()
{
output_list.Items.Add("Not sended");
message = "";
});
}
}
swift:
func web_data_work()
{
let address = address_box.stringValue
let port = port_box.intValue
let task = URLSession.shared.streamTask(withHostName: address, port: Int(port))
let data = message.data(using: .utf8)!
task.write(data as Data, timeout: 0)
{
error in
//om = "Not sended"
//self.output_message()
}
task.resume()
}
In c# i can read messages using TcpListener, how do i do it in swift?
Only two parameters are used:
"address" – ip address of the computer to which messages are sent and which is listened to by the TcpListener of that computer.
"port" – port of the sending and receiving between computers.
P.S. Preferably without additional libraries.
You can achieve this with just two classes: SocketPort & FileHandle.
Before you copy & paste the example below, please, read my comments at the end.
import Cocoa
class TcpEchoClient {
var readToEndOfFileCompletionHandler: (() -> Void)?
let fileHandle: FileHandle
var observer: NSObjectProtocol?
init(fileHandle: FileHandle) {
self.fileHandle = fileHandle
// Register observer for the data & EOF
self.observer = NotificationCenter.default.addObserver(forName: .NSFileHandleReadToEndOfFileCompletion,
object: fileHandle,
queue: OperationQueue.main) { [weak self] note in
self?.handleReadToEndOfFileCompletion(notification: note)
}
// Instruct the handle to read till the EOF & notify
fileHandle.readToEndOfFileInBackgroundAndNotify()
}
func handleReadToEndOfFileCompletion(notification note: Notification) {
defer {
// No matter what happens, call the completion handle by the end
readToEndOfFileCompletionHandler?()
}
// Is there an error?
if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
print("Client \(fileHandle.fileDescriptor) error: File handle error \(errorCode.intValue)")
return
}
// No error, we should have data available
guard let data = note.userInfo?[NSFileHandleNotificationDataItem] as? Data else {
print("Client \(fileHandle.fileDescriptor) error: Unable to get data")
return
}
// Convert them to UTF-8 string
guard let text = String(data: data, encoding: .utf8) else {
print("Client \(fileHandle.fileDescriptor) error: Unable to convert data to UTF-8 string")
return
}
// Print the text
print("Client \(fileHandle.fileDescriptor) received: \(text)")
}
deinit {
// Remove observer for the data & EOF
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
// Close the handle
try? fileHandle.close()
}
}
class TcpServer {
let port: SocketPort
let fileHandle: FileHandle
var clients: Dictionary<Int32, TcpEchoClient>
var observer: NSObjectProtocol?
init?(tcpPort: UInt16) {
guard let socketPort = SocketPort(tcpPort: tcpPort) else {
return nil
}
// Keep the socket port around otherwise you'll get error 38
port = socketPort
// No clients for now
clients = [:]
// Create handle from the socket
fileHandle = FileHandle(fileDescriptor: port.socket)
// Register observer for the connection accepted
observer = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
object: fileHandle,
queue: OperationQueue.main) { [weak self] note in
if let handle = note.object as? FileHandle {
// Ask immediately for another accepted connection notification
handle.acceptConnectionInBackgroundAndNotify()
}
self?.handleConnectionAccepted(notification: note)
}
// Instruct the handle to accept connection & notify
fileHandle.acceptConnectionInBackgroundAndNotify()
}
func handleConnectionAccepted(notification note: Notification) {
// Is there an error?
if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
print("Server error: File handle error \(errorCode.intValue)")
return
}
// No, we should have received the client file handle
guard let clientFileHandle = note.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else {
print("Server error: Unable to get accepted connection file handle")
return
}
let fileDescriptor = clientFileHandle.fileDescriptor
// Create new client from the file handle
let client = TcpEchoClient(fileHandle: clientFileHandle)
// Once it finishes, remove it from the clients dictionary
client.readToEndOfFileCompletionHandler = { [weak self] in
guard let self = self else { return }
self.clients.removeValue(forKey: fileDescriptor)
print("Server: Client removed \(fileDescriptor) (total \(self.clients.count))")
}
// Store the client in the clients dictionary
clients[fileDescriptor] = client
print("Server: New client \(fileDescriptor) added (total \(self.clients.count))")
}
deinit {
// Remove all clients
clients.removeAll()
// Close the file handle
try? fileHandle.close()
// Invalidate the socket port
port.invalidate()
// Remove connection accepted observer
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Then create a property (var server: TcpServer?) and initialize it (server = TcpServer(tcpPort: 8080)).
Test:
parallel -j 1 echo -n Foo '|' nc 127.0.0.1 8080 ::: {1..4}
Console output:
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Comments:
This example is only a basic skeleton you can start with.
Don't be misleaded with the parallel test command.
I'm using it if I want to run multiple commands at once (same commands).
Everything in this example operates on the main queue.
Read documentation of all these classes, methods and notifications I do use. There're gotchas, you have to be familiar with run loops, etc.
I need to send and then receive some data(string) from/to UDP client.
I can send data to client with code below but how can I implement read data as well? I have read thousands pages and examples but I just can not figure it out. Can someone please modify my code so it will be capable sent and than received data from UDP device?
My code is from here:
How to implement UDP client and send data in Swift on iPhone?
Details:
I am sending string to ESP32 device with specific IP and port.
then this device will do some calculations and return some data(string) back. So I will need receive that data after I sent data string to it.
I am able to sent string with provided code below but I do not know how to modify for receiving data from my ESP32 device as well via UDP.
import Foundation
func udpSend(textToSend: String, address: in_addr, port: CUnsignedShort) {
func htons(value: CUnsignedShort) -> CUnsignedShort {
return (value << 8) + (value >> 8);
}
let fd = socket(AF_INET, SOCK_DGRAM, 0) // DGRAM makes it UDP
let addr = sockaddr_in(
sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
sin_family: sa_family_t(AF_INET),
sin_port: htons(value: port),
sin_addr: address,
sin_zero: ( 0, 0, 0, 0, 0, 0, 0, 0 )
)
let sent = textToSend.withCString { cstr -> Int in
var localCopy = addr
let sent = withUnsafePointer(to: &localCopy) { pointer -> Int in
let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1)
let sent = sendto(fd, cstr, strlen(cstr), 0, memory, socklen_t(addr.sin_len))
return sent
}
return sent
}
close(fd)
}
Currently I am working on a program in which i am connecting two IOS devices using NSNetService class. I am able to connect both the devices but i don't know how to send data using getInoutStream function. Can any buddy help me.
Why not use multipeer? It's a much simpler solution to what you're trying to do. Also NSNetService has a few known bugs. However, you send data between the two by setting up a Bonjour connection on NSNetService. The getInputStream method requires arguments of the type UnsafeMutablePointer:
public func getInputStream(inputStream: UnsafeMutablePointer<NSInputStream?>, outputStream: UnsafeMutablePointer<NSOutputStream?>) -> Bool
var inputStream : NSInputStream?
var outputStream : NSOutputStream?
let success = service.getInputStream(&inputStream, outputStream: &outputStream)
Then just write data using a memory stream and it'll get passed to the listener port.
This is the finished code:
This function initiates the connection on 127.0.0.1
func initNetworkCommunication(){
var host : CFString = "127.0.0.1"
var port : UInt32 = 7001
var readstream : Unmanaged<CFReadStream>?
var writestream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, &readstream, &writestream)
inputstream = readstream!.takeRetainedValue()
outputstream = writestream!.takeRetainedValue()
inputstream.delegate = self
outputstream.delegate = self
inputstream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputstream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
inputstream.open()
And this is for the stream IO:
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { //This is the stream IO function. It allows RW of the stream
switch (eventCode){
case NSStreamEvent.ErrorOccurred:
NSLog("ErrorOccurred")
break
case NSStreamEvent.EndEncountered:
NSLog("EndEncountered")
break
case NSStreamEvent.None:
NSLog("None")
break
case NSStreamEvent.HasBytesAvailable:
NSLog("HasBytesAvaible")
var buffer = [UInt8](count: 4096, repeatedValue: 0)
if ( aStream == inputstream){
while (inputstream.hasBytesAvailable){
var len = inputstream.read(&buffer, maxLength: buffer.count)
if(len > 0){
var output = NSString(bytes: &buffer, length: buffer.count, encoding: NSUTF8StringEncoding)
if (output != ""){
NSLog("server said: %#", output!)
}
}
}
}
break
case NSStreamEvent.allZeros:
NSLog("allZeros")
break
case NSStreamEvent.OpenCompleted:
NSLog("OpenCompleted")
break
case NSStreamEvent.HasSpaceAvailable:
NSLog("HasSpaceAvailable")
break
default:
// default code here
break
}
I try to send and receive data with NSOutputStream and NSInputStream in Swift. The sending of data is working well, but i have some questions about the receiving.
I found a solution with handling the NSStreamEvent, which i have tried.
First of all my function for initializing the connection:
func initNetworkCommunication(){
var host : CFString = "127.0.0.1"
var port : UInt32 = 7001
var readstream : Unmanaged<CFReadStream>?
var writestream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, &readstream, &writestream)
inputstream = readstream!.takeRetainedValue()
outputstream = writestream!.takeRetainedValue()
inputstream.delegate = self
outputstream.delegate = self
inputstream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputstream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
inputstream.open()
outputstream.open()
}
This part is working. I have set the delegates to self, so i should be able to handle the NSStreamEvents in this class.
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode){
case NSStreamEvent.OpenCompleted:
NSLog("Stream opened")
break
case NSStreamEvent.HasBytesAvailable:
NSLog("HasBytesAvailable")
break
case NSStreamEvent.ErrorOccurred:
NSLog("ErrorOccurred")
break
case NSStreamEvent.EndEncountered:
NSLog("EndEncountered")
break
default:
NSLog("unknown.")
}
}
In my understanding everytime when some data arrives, it should print "HasBytesAvaible", but it printed "unknown." everytime. So i played around a bit and it worked with this:
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
var buffer = [UInt8](count: 4096, repeatedValue: 0)
while (inputstream.hasBytesAvailable){
let result: Int = inputstream.read(&buffer, maxLength: buffer.count)
}
switch (eventCode){
case NSStreamEvent.OpenCompleted:
NSLog("Stream opened")
break
case NSStreamEvent.HasBytesAvailable:
NSLog("HasBytesAvailable")
var output = NSString(bytes: &buffer, length: buffer.count, encoding: NSUTF8StringEncoding)
NSLog("output: %#", output)
receiveMessage(output) //only adds the message to an array
break
case NSStreamEvent.ErrorOccurred:
NSLog("ErrorOccurred")
break
case NSStreamEvent.EndEncountered:
NSLog("EndEncountered")
break
default:
NSLog("unknown.")
}
}
It's working this way, but i wanted to ask you whether this is the right way ? What is the best practice at this point ?
UPDATE
I worked on it again a few weeks ago and figured out my mistakes. So here is the working code.
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode){
case NSStreamEvent.ErrorOccurred:
NSLog("ErrorOccurred")
break
case NSStreamEvent.EndEncountered:
NSLog("EndEncountered")
break
case NSStreamEvent.None:
NSLog("None")
break
case NSStreamEvent.HasBytesAvailable:
NSLog("HasBytesAvaible")
var buffer = [UInt8](count: 4096, repeatedValue: 0)
if ( aStream == inputstream){
while (inputstream.hasBytesAvailable){
var len = inputstream.read(&buffer, maxLength: buffer.count)
if(len > 0){
var output = NSString(bytes: &buffer, length: buffer.count, encoding: NSUTF8StringEncoding)
if (output != ""){
NSLog("server said: %#", output!)
}
}
}
}
break
case NSStreamEvent.allZeros:
NSLog("allZeros")
break
case NSStreamEvent.OpenCompleted:
NSLog("OpenCompleted")
break
case NSStreamEvent.HasSpaceAvailable:
NSLog("HasSpaceAvailable")
break
}
You're missing the event hasSpaceAvailable, which I expect is occurring when it says "unknown". It's telling you that it is ready to receive more data.
Generally, I avoid using default in switch statements for enums, since the compiler will tell you when you've missed something.
I am using the code written by hoedding, and noticed a mistake. The line:
var output = NSString(bytes: &buffer, length: buffer.count, encoding: NSUTF8StringEncoding)
should be replaced by:
var output = NSString(bytes: &buffer, length: len, encoding: NSUTF8StringEncoding)
You have to get in the output var only the number of characters returned by the server, and not the full length of the buffer, or you will get garbage from previous requests.
I am able to get the current IP address of my device/machine that I am using - by using this question's answer.
I have gone through this question. Java allows to get the IP address from a domain name. Is it possible in Objective C? How?
The second question is How to get the name of device/machine by using its IP address. Say for example I have an IP address 192.168.0.74 = What is the device name? in Objective C?
I'm not sure if this is the best way to do this, but it works for me, mostly. I put in StackOverflow's IP addresses (69.59.196.211) and it gave me back stackoverflow.com, but I put in one of Google's IP addresses (210.55.180.158) and it gave me back cache.googlevideo.com (for all results, not just the first one).
int error;
struct addrinfo *results = NULL;
error = getaddrinfo("69.59.196.211", NULL, NULL, &results);
if (error != 0)
{
NSLog (#"Could not get any info for the address");
return; // or exit(1);
}
for (struct addrinfo *r = results; r; r = r->ai_next)
{
char hostname[NI_MAXHOST] = {0};
error = getnameinfo(r->ai_addr, r->ai_addrlen, hostname, sizeof hostname, NULL, 0 , 0);
if (error != 0)
{
continue; // try next one
}
else
{
NSLog (#"Found hostname: %s", hostname);
break;
}
}
freeaddrinfo(results);
There can be multiple names for the address, so you might not want to stop at the first one you find.
I wrote a Swift version of the accepted answer, though I'm not 100% sure of its correctness.
func reverseDNS(ip: String) -> String {
var results: UnsafeMutablePointer<addrinfo>? = nil
defer {
if let results = results {
freeaddrinfo(results)
}
}
let error = getaddrinfo(ip, nil, nil, &results)
if (error != 0) {
print("Unable to reverse ip: \(ip)")
return ip
}
for addrinfo in sequence(first: results, next: { $0?.pointee.ai_next }) {
guard let pointee = addrinfo?.pointee else {
print("Unable to reverse ip: \(ip)")
return ip
}
let hname = UnsafeMutablePointer<Int8>.allocate(capacity: Int(NI_MAXHOST))
defer {
hname.deallocate()
}
let error = getnameinfo(pointee.ai_addr, pointee.ai_addrlen, hname, socklen_t(NI_MAXHOST), nil, 0, 0)
if (error != 0) {
continue
}
return String(cString: hname)
}
return ip
}
You need to read the routing table - basically the same way as "netstat -r" command does. The netstat implementation is opensource - it's in the package
network_cmds-396.6
at
http://www.opensource.apple.com/release/mac-os-x-1082/
so you can check what it does. The default gateway contains the "G" flag but shouldn't connect the "I" flag (when both wifi and cell are active, wifi is used for internet connection - the cell gateway is not used and is given the "I" flag).
Hope it helps.