Marco de socket para Swift usando el Swift Package Manager. Funciona en iOS, MacOS y Linux.
swift-5.1-RELEASE ( se requiere un mínimo para la última versión )swift-5.4-RELEASE Hoolchain ( recomendado )Nota:
Si crea un servidor UDP en iOS, es posible que deba seguir algunos pasos:
Para construir socket desde la línea de comando:
% cd <path-to-clone>
% swift build
Para ejecutar las pruebas unitarias suministradas para el socket desde la línea de comando:
% cd <path-to-clone>
% swift build
% swift test
Para incluir Bluesocket en un paquete Swift Package Manager, agréguelo al atributo dependencies definido en su archivo Package.swift . Puede seleccionar la versión utilizando los parámetros majorVersion y minor . Por ejemplo:
dependencies: [
.Package(url: "https://github.com/Kitura/BlueSocket.git", majorVersion: <majorVersion>, minor: <minor>)
]
Para incluir Bluesocket en un proyecto que usa Carthage, agregue una línea a su Cartfile con la organización GitHub y los nombres y la versión del proyecto. Por ejemplo:
github "Kitura/BlueSocket" ~> <majorVersion>.<minor>
Para incluir bluesocket en un proyecto con cocoapods, solo agregue BlueSocket a su Podfile , por ejemplo:
platform :ios, '10.0'
target 'MyApp' do
use_frameworks!
pod 'BlueSocket'
end
Lo primero que debe hacer es importar el marco de socket. Esto se hace por lo siguiente:
import Socket
Bluesocket admite las siguientes familias, tipos y protocolos:
Socket.ProtocolFamily.inetSocket.ProtocolFamily.inet6Socket.ProtocolFamily.unixSocket.SocketType.streamSocket.SocketType.datagramSocket.SocketProtocol.tcpSocket.SocketProtocol.udpSocket.SocketProtocol.unixBluesocket proporciona cuatro métodos de fábrica diferentes que se utilizan para crear una instancia. Estos son:
create() : esto crea un socket predeterminado totalmente configurado. Se crea un socket predeterminado con family: .inet , type: .stream y proto: .tcp .create(family family: ProtocolFamily, type: SocketType, proto: SocketProtocol) : esta API le permite crear una instancia Socket configurada personalizada para sus necesidades. Puede personalizar la familia del protocolo, el tipo de enchufe y el protocolo de enchufe.create(connectedUsing signature: Signature) : esta API le permitirá crear una instancia Socket e intentar que se conecte a un servidor en función de la información que pasa en Socket.Signature .create(fromNativeHandle nativeHandle: Int32, address: Address?) - Esta API le permite envolver un descriptor de archivos nativo que describe un socket existente en una nueva instancia de Socket . Bluesocket le permite establecer el tamaño del búfer de lectura que usará. Luego, dependiendo de las necesidades de la aplicación, puede cambiarla a un valor más alto o más bajo. El valor predeterminado se establece en Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE que tiene un valor de 4096 . El tamaño mínimo del búfer de lectura es Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE que está configurado en 1024 . A continuación ilustra cómo cambiar el tamaño del búfer de lectura (manejo de excepciones omitido para brevedad):
let mySocket = try Socket.create()
mySocket.readBufferSize = 32768
El ejemplo anterior establece el tamaño de búfer de lectura predeterminado en 32768 . Esta configuración debe realizarse antes de usar la instancia Socket por primera vez.
Para cerrar el zócalo de una instancia abierta, se proporciona la siguiente función:
close() : esta función realizará las tareas necesarias para cerrar limpiamente un enchufe abierto.Para usar Bluesocket para escuchar una conexión en un socket, se proporciona la siguiente API:
listen(on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG, allowPortReuse: Bool = true, node: String? = nil) El primer port de parámetros, es el puerto que se usa para escuchar. El segundo parámetro, maxBacklogSize le permite establecer el tamaño de la cola que contiene las conexiones pendientes. La función determinará la configuración de socket adecuada basada en el port especificado. Para mayor comodidad en MacOS, se puede configurar el Socket.SOCKET_MAX_DARWIN_BACKLOG constante para usar el tamaño máximo permitido de acumulación. El valor predeterminado para todas las plataformas es Socket.SOCKET_DEFAULT_MAX_BACKLOG , actualmente establecido en 50 . Para el uso del servidor, puede ser necesario aumentar este valor. Para permitir la reutilización del puerto de escucha, establezca allowPortReuse a true . Si se establece en false , se producirá un error si intenta escuchar en un puerto que ya está en uso. El comportamiento DEFAULT es allow la reutilización del puerto. El último parámetro, node , se puede usar para escuchar en una dirección específica . El valor aprobado es una cadena opcional que contiene la dirección de red numérica (para IPv4, notación de números y puntos, para IPv6, hexidecimal strtting). El comportamiento DEFAULT es buscar una interfaz apropiada. Si node está formateado incorrectamente, se devolverá un error Socket_err_GetadDrinfo_Failed . Si node está correctamente formateado pero la dirección especificada no está disponible, se devolverá un Socket_err_Bind_Failed .listen(on path: String, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) Esta API solo se puede usar con la familia .unix Protocol. La primera path de parámetros es la ruta que se puede usar para escuchar. El segundo parámetro, maxBacklogSize le permite establecer el tamaño de la cola que contiene las conexiones pendientes. La función determinará la configuración de socket adecuada basada en el port especificado. Para mayor comodidad en MacOS, se puede configurar el Socket.SOCKET_MAX_DARWIN_BACKLOG constante para usar el tamaño máximo permitido de acumulación. El valor predeterminado para todas las plataformas es Socket.SOCKET_DEFAULT_MAX_BACKLOG , actualmente establecido en 50 . Para el uso del servidor, puede ser necesario aumentar este valor. El siguiente ejemplo crea una instancia Socket predeterminada y luego comienza inmediatamente a escuchar en el puerto 1337 . Nota: Manejo de excepciones omitido para brevedad, consulte el ejemplo completo a continuación para obtener un ejemplo de manejo de excepciones.
var socket = try Socket . create ( )
try socket . listen ( on : 1337 )Cuando un enchufe de escucha detecta una solicitud de conexión entrante, el control se devuelve a su programa. Luego puede aceptar la conexión o continuar escuchando o ambos si su aplicación está multiprocesada. Bluesocket admite dos formas distintas de aceptar una conexión entrante. Ellos son:
acceptClientConnection(invokeDelegate: Bool = true) : esta función acepta la conexión y devuelve una nueva instancia Socket basada en el enchufe recién conectado. La instancia que estaba escuchando no afectada. Si invokeDelegate es false y el Socket tiene un delegado SSLService conectado, debe llamar al método invokeDelegateOnAccept utilizando la instancia Socket que devuelve esta función.invokeDelegateOnAccept(for newSocket: Socket) : si la instancia Socket tiene un delegado SSLService , esto invocará la función de aceptación de los delegados para realizar la negociación SSL. Debe llamarse con la instancia Socket devuelta por acceptClientConnection . Esta función lanzará una excepción si se llama con la instancia Socket incorrecta, llamada varias veces, o si la instancia Socket no tiene un delegado SSLService .acceptConnection() : esta función acepta la conexión entrante, reemplazando y cerrando el socket de audición existente. Las propiedades que anteriormente estaban asociadas con el enchufe de escucha se reemplazan por las propiedades que son relevantes para el enchufe recién conectado. Además del método create(connectedUsing:) Factory descrito anteriormente, Bluesocket admite tres funciones de instancia adicionales para conectar una instancia Socket a un servidor. Ellos son:
connect(to host: String, port: Int32, timeout: UInt = 0) : esta API le permite conectarse a un servidor basado en el hostname y port que proporciona. NOTA: Esta función arrojará una exception si el valor del port no está en el rango 1-65535 . Opcionalmente, puede establecer timeout en la cantidad de milisegundos para esperar la conexión. NOTA: Si el socket está en modo de bloqueo, se cambiará al modo sin bloqueo temporalmente si se proporciona un timeout mayor que cero (0). El socket devuelto se volverá a colocar en su configuración original (bloqueo o no bloqueo) . Si el socket está configurado para no bloquear y no se proporciona valor de tiempo de espera , se lanzará una excepción. Alternativamente, puede configurar el socket en no bloquear después de conectarse con éxito.connect(to path: String) : esta API solo se puede usar con la familia .unix del protocolo. Le permite conectarse a un servidor en función de la path que proporciona.connect(using signature: Signature) : esta API le permite especificar la información de conexión proporcionando una instancia Socket.Signature Contiene la información. Consulte Socket.Signature en Socket.swift para obtener más información.Bluesocket admite cuatro formas diferentes de leer datos de un socket. Estos son (en orden de uso recomendado):
read(into data: inout Data) : esta función lee todos los datos disponibles en un socket y los devuelve en el objeto Data que se aprobó.read(into data: NSMutableData) : esta función lee todos los datos disponibles en un socket y los devuelve en el objeto NSMutableData que se aprobó.readString() : esta función lee todos los datos disponibles en un socket y los devuelve como una String . Se devuelve un nil si no hay datos disponibles para leer.read(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int, truncate: Bool = false) : esta función le permite leer datos en un búfer de un tamaño especificado proporcionando un puntero inseguro a ese búfer y un entero que denota el tamaño de ese búfer. Esta API (además de otros tipos de excepciones) lanzará un Socket.SOCKET_ERR_RECV_BUFFER_TOO_SMALL Si el búfer proporcionado es demasiado pequeño, a menos que truncate = true en cuyo caso el socket actuará como si solo se lee los bytes bufSize (los bytes sin retrimidos se devolverán en la próxima llamada). Si truncate = false , deberá volver a llamar con el tamaño del búfer adecuado (ver Error.bufferSizeNeeded .readString() pueden devolver cero (0). Esto puede indicar que la conexión remota estaba cerrada o podría indicar que el enchufe se bloquearía (suponiendo que haya apagado el bloqueo). Para diferenciar entre los dos, se puede verificar la propiedad remoteConnectionClosed . Si true , el socio remoto de Socket ha cerrado la conexión y esta instancia Socket debe estar cerrada.Además de leer un socket, Bluesocket también suministra cuatro métodos para escribir datos en un socket. Estos son (en orden de uso recomendado):
write(from data: Data) : esta función escribe los datos contenidos dentro del objeto Data al socket.write(from data: NSData) : esta función escribe los datos contenidos dentro del objeto NSData al socket.write(from string: String) : esta función escribe los datos contenidos en la String proporcionada al socket.write(from buffer: UnsafeRawPointer, bufSize: Int) : esta función escribe los datos contenidos dentro del búfer del tamaño especificado al proporcionar un puntero inseguro a ese búfer y un entero que denota el tamaño de ese búfer.Bluesocket admite tres formas diferentes de escuchar datagramas entrantes. Estos son (en orden de uso recomendado):
listen(forMessage data: inout Data, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) : esta función escucha un datagrama entrante, lo lee y lo devuelve en el objeto Data aprobado. Devuelve una tupla que contiene el número de bytes leídos y la Address de dónde se originaron los datos.listen(forMessage data: NSMutableData, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) : esta función escucha un datagrama entrante, lo lee y lo devuelve en el objeto NSMutableData PASADO. Devuelve una tupla que contiene el número de bytes leídos y la Address de dónde se originaron los datos.listen(forMessage buffer: UnsafeMutablePointer<CChar>, bufSize: Int, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) : esta función escucha una datagrama entrante, lo lee y lo devuelve en el objeto Data aprobado. Devuelve una tupla que contiene el número de bytes leídos y la Address de dónde se originaron los datos.port especificado. Establecer el valor del port a cero (0) hará que la función determine un puerto libre adecuado.maxBacklogSize le permite establecer el tamaño de la cola que contiene las conexiones pendientes. La función determinará la configuración de socket adecuada basada en el port especificado. Para mayor comodidad en MacOS, el Socket.SOCKET_MAX_DARWIN_BACKLOG constante se puede configurar para usar el tamaño máximo permitido de acumulación. El valor predeterminado para todas las plataformas es Socket.SOCKET_DEFAULT_MAX_BACKLOG , actualmente establecido en 50 . Para el uso del servidor, puede ser necesario aumentar este valor.Bluesocket admite tres formas diferentes de leer datagramas entrantes. Estos son (en orden de uso recomendado):
readDatagram(into data: inout Data) : esta función lee un datagrama entrante y lo devuelve en el objeto Data aprobado. Devuelve una tupla que contiene el número de bytes leídos y la Address de dónde se originaron los datos.readDatagram(into data: NSMutableData) : esta función lee un datagrama entrante y lo devuelve en el objeto NSMutableData pasado. Devuelve una tupla que contiene el número de bytes leídos y la Address de dónde se originaron los datos.readDatagram(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int) : esta función lee un datagrama entrante y lo devuelve en el objeto Data aprobado. Devuelve una tupla que contiene el número de bytes leídos y la Address de dónde se originaron los datos. Si la cantidad de datos se lee es más que se bufSize solo se devolverá bufSize . El resto de los datos leídos se descartará.Bluesocket también suministra cuatro métodos para escribir datagramas a un socket. Estos son (en orden de uso recomendado):
write(from data: Data, to address: Address) : esta función escribe el datagrama contenido dentro del objeto Data al socket.write(from data: NSData, to address: Address) : esta función escribe el datagrama contenido dentro del objeto NSData al socket.write(from string: String, to address: Address) : esta función escribe el datagrama contenido en la String proporcionada al socket.write(from buffer: UnsafeRawPointer, bufSize: Int, to address: Address) : esta función escribe los datos contenidos dentro del búfer del tamaño especificado al proporcionar un puntero inseguro a ese búfer y un entero que denota el tamaño de ese búfer.address representa la dirección para el destino al que está enviando el datagrama. Las API de lectura y escritura anteriores que usan NSData o NSMutableData probablemente estarán en desuso en el futuro no muy lejano.
hostnameAndPort(from address: Address) : esta función de clase proporciona un medio para extraer el nombre de host y el puerto de un Socket.Address dado.Address. Al finalizar con éxito, se devuelve una tupla que contiene el hostname y port .checkStatus(for sockets: [Socket]) : esta función de clase le permite verificar el estado de una matriz de instancias de Socket . Al finalizar, se devuelve una tupla que contiene dos matrices Socket . La primera matriz contiene las instancias Socket que tienen datos disponibles para leer y la segunda matriz contiene instancias Socket que se pueden escribir. Esta API no bloquea. Verificará el estado de cada instancia Socket y luego devolverá los resultados.wait(for sockets: [Socket], timeout: UInt, waitForever: Bool = false) : esta función de clase permite monitorear una matriz de instancias Socket , esperando que ocurra un tiempo de espera o que sean legibles en una de las instancias de Socket monitoreadas. Si se especifica un tiempo de espera de cero (0), esta API verificará cada socket y devolverá de inmediato. De lo contrario, esperará hasta que expire el tiempo de espera o los datos sean legibles de una o más de las instancias Socket monitoreadas. Si se produce un tiempo de espera, esta API devolverá nil . Si los datos están disponibles en una o más de las instancias Socket monitoreadas, esas instancias se devolverán en una matriz. Si el indicador waitForever se establece en True, la función esperará indefinidamente para que los datos estén disponibles independientemente del valor de tiempo de espera especificado .createAddress(host: String, port: Int32) : esta función de clase permite la creación de Address enum dada un host y port . En el éxito, esta función devuelve una Address o nil si el host especificado no existe.isReadableOrWritable(waitForever: Bool = false, timeout: UInt = 0) : esta función de instancia permite determinar si una instancia Socket es legible y/o escritura. Se devuelve una tupla que contiene dos valores Bool . El primero, si es verdadero, indica que la instancia Socket tiene datos para leer, el segundo, si es cierto, indica que la instancia Socket se puede escribir. waitForever Si es verdadero, hace que esta rutina espere hasta que el Socket sea legible o que se escriba o se produce un error. Si False, el parámetro de timeout especifica cuánto tiempo esperar. Si se especifica un valor de cero (0) para el valor de tiempo de espera, esta función verificará el estado actual y volverá inmediatamente . Esta función devuelve una tupla que contiene dos booleanos, la primera readable y la segunda, writable . Están configurados en verdadero si el Socket es legible o de forma legible. Si ninguno de los dos está configurado en verdadero, se ha producido un tiempo de espera. Nota: Si está intentando escribir en un enchufe recién conectado, debe asegurarse de que sea escribida antes de intentar la operación.setBlocking(shouldBlock: Bool) : esta función de instancia le permite controlar si esta instancia Socket debe colocarse o no en modo de bloqueo o no. Nota: Todas las instancias Socket se crean, por defecto , en el modo de bloqueo .setReadTimeout(value: UInt = 0) : esta función de instancia le permite establecer un tiempo de espera para operaciones de lectura. value es un UInt que especifica el tiempo para que la operación de lectura espere antes de regresar. En el caso de un tiempo de espera, la operación de lectura devolverá 0 bytes leídos y errno se establecerá en EAGAIN .setWriteTimeout(value: UInt = 0) : esta función de instancia le permite establecer un tiempo de espera para las operaciones de escritura. value es un UInt que especifica el tiempo para que la operación de escritura espere antes de regresar. En caso de tiempo de espera, la operación de escritura devolverá 0 bytes escritos y errno se establecerá en EAGAIN para enchufes TCP y UNIX , para UDP , la operación de escritura tendrá éxito independientemente del valor de tiempo de espera.udpBroadcast(enable: Bool) : esta función de instancia se utiliza para habilitar el modo de transmisión en un socket UDP. Pase true para habilitar la transmisión, false para deshabilitar. Esta función lanzará una excepción si la instancia Socket no es un socket UDP. El siguiente ejemplo muestra cómo crear un servidor ECHO relativamente simple de subprocesos múltiples utilizando la nueva API de despacho GCD based . Lo que sigue es el código para un servidor de eco simple que una vez en ejecución se puede acceder a través de telnet ::1 1337 .
import Foundation
import Socket
import Dispatch
class EchoServer {
static let quitCommand : String = " QUIT "
static let shutdownCommand : String = " SHUTDOWN "
static let bufferSize = 4096
let port : Int
var listenSocket : Socket ? = nil
var continueRunningValue = true
var connectedSockets = [ Int32 : Socket ] ( )
let socketLockQueue = DispatchQueue ( label : " com.kitura.serverSwift.socketLockQueue " )
var continueRunning : Bool {
set ( newValue ) {
socketLockQueue . sync {
self . continueRunningValue = newValue
}
}
get {
return socketLockQueue . sync {
self . continueRunningValue
}
}
}
init ( port : Int ) {
self . port = port
}
deinit {
// Close all open sockets...
for socket in connectedSockets . values {
socket . close ( )
}
self . listenSocket ? . close ( )
}
func run ( ) {
let queue = DispatchQueue . global ( qos : . userInteractive )
queue . async { [ unowned self ] in
do {
// Create an IPV6 socket...
try self . listenSocket = Socket . create ( family : . inet6 )
guard let socket = self . listenSocket else {
print ( " Unable to unwrap socket... " )
return
}
try socket . listen ( on : self . port )
print ( " Listening on port: ( socket . listeningPort ) " )
repeat {
let newSocket = try socket . acceptClientConnection ( )
print ( " Accepted connection from: ( newSocket . remoteHostname ) on port ( newSocket . remotePort ) " )
print ( " Socket Signature: ( String ( describing : newSocket . signature ? . description ) ) " )
self . addNewConnection ( socket : newSocket )
} while self . continueRunning
}
catch let error {
guard let socketError = error as? Socket . Error else {
print ( " Unexpected error... " )
return
}
if self . continueRunning {
print ( " Error reported: n ( socketError . description ) " )
}
}
}
dispatchMain ( )
}
func addNewConnection ( socket : Socket ) {
// Add the new socket to the list of connected sockets...
socketLockQueue . sync { [ unowned self , socket ] in
self . connectedSockets [ socket . socketfd ] = socket
}
// Get the global concurrent queue...
let queue = DispatchQueue . global ( qos : . default )
// Create the run loop work item and dispatch to the default priority global queue...
queue . async { [ unowned self , socket ] in
var shouldKeepRunning = true
var readData = Data ( capacity : EchoServer . bufferSize )
do {
// Write the welcome string...
try socket . write ( from : " Hello, type 'QUIT' to end session n or 'SHUTDOWN' to stop server. n " )
repeat {
let bytesRead = try socket . read ( into : & readData )
if bytesRead > 0 {
guard let response = String ( data : readData , encoding : . utf8 ) else {
print ( " Error decoding response... " )
readData . count = 0
break
}
if response . hasPrefix ( EchoServer . shutdownCommand ) {
print ( " Shutdown requested by connection at ( socket . remoteHostname ) : ( socket . remotePort ) " )
// Shut things down...
self . shutdownServer ( )
return
}
print ( " Server received from connection at ( socket . remoteHostname ) : ( socket . remotePort ) : ( response ) " )
let reply = " Server response: n ( response ) n "
try socket . write ( from : reply )
if ( response . uppercased ( ) . hasPrefix ( EchoServer . quitCommand ) || response . uppercased ( ) . hasPrefix ( EchoServer . shutdownCommand ) ) &&
( !response . hasPrefix ( EchoServer . quitCommand ) && !response . hasPrefix ( EchoServer . shutdownCommand ) ) {
try socket . write ( from : " If you want to QUIT or SHUTDOWN, please type the name in all caps. ? n " )
}
if response . hasPrefix ( EchoServer . quitCommand ) || response . hasSuffix ( EchoServer . quitCommand ) {
shouldKeepRunning = false
}
}
if bytesRead == 0 {
shouldKeepRunning = false
break
}
readData . count = 0
} while shouldKeepRunning
print ( " Socket: ( socket . remoteHostname ) : ( socket . remotePort ) closed... " )
socket . close ( )
self . socketLockQueue . sync { [ unowned self , socket ] in
self . connectedSockets [ socket . socketfd ] = nil
}
}
catch let error {
guard let socketError = error as? Socket . Error else {
print ( " Unexpected error by connection at ( socket . remoteHostname ) : ( socket . remotePort ) ... " )
return
}
if self . continueRunning {
print ( " Error reported by connection at ( socket . remoteHostname ) : ( socket . remotePort ) : n ( socketError . description ) " )
}
}
}
}
func shutdownServer ( ) {
print ( " n Shutdown in progress... " )
self . continueRunning = false
// Close all open sockets...
for socket in connectedSockets . values {
self . socketLockQueue . sync { [ unowned self , socket ] in
self . connectedSockets [ socket . socketfd ] = nil
socket . close ( )
}
}
DispatchQueue . main . sync {
exit ( 0 )
}
}
}
let port = 1337
let server = EchoServer ( port : port )
print ( " Swift Echo Server Sample " )
print ( " Connect with a command line window by entering 'telnet ::1 ( port ) ' " )
server . run ( ) Este servidor se puede construir especificando el siguiente archivo de Package.swift usando Swift 4.
import PackageDescription
let package = Package (
name : " EchoServer " ,
dependencies : [
. package ( url : " https://github.com/Kitura/BlueSocket.git " , from : " 1.0.8 " ) ,
] ,
targets : [
. target (
name : " EchoServer " ,
dependencies : [
" Socket "
] ) ,
]
) O si todavía está usando Swift 3, especificando el siguiente archivo Package.swift .
import PackageDescription
let package = Package (
name : " EchoServer " ,
dependencies : [
. Package ( url : " https://github.com/Kitura/BlueSocket.git " , majorVersion : 1 , minor : 0 ) ,
] ,
exclude : [ " EchoServer.xcodeproj " ]
) La siguiente secuencia de comando construirá y ejecutará el servidor Echo en Linux. Si se ejecuta en macOS o con cualquier cadena de herramientas más nueva que la cadena de herramientas 8/18, puede omitir el interruptor -Xcc -fblocks ya que ya no es necesario.
$ swift build -Xcc -fblocks
$ .build/debug/EchoServer
Swift Echo Server Sample
Connect with a command line window by entering 'telnet ::1 1337'
Listening on port: 1337
Nos encanta hablar de servidor Swift y Kitura. ¡Únete a nuestra holgura para conocer al equipo!
Esta biblioteca tiene licencia bajo Apache 2.0. El texto completo de la licencia está disponible en licencia.