Estrutura de soquete para SWIFT usando o Swift Package Manager. Trabalha em iOS, macOS e Linux.
swift-5.1-RELEASE Chain de ferramentas ( mínimo necessário para a versão mais recente )swift-5.4-RELEASE Chain de ferramentas ( recomendado )Observação:
Se criar um servidor UDP no iOS, pode ser necessário seguir algumas etapas:
Para construir o soquete a partir da linha de comando:
% cd <path-to-clone>
% swift build
Para executar os testes de unidade fornecidos para soquete da linha de comando:
% cd <path-to-clone>
% swift build
% swift test
Para incluir o bluesocket em um pacote Swift Package Manager, adicione -o ao atributo dependencies definido no seu arquivo Package.swift . Você pode selecionar a versão usando os parâmetros majorVersion e minor . Por exemplo:
dependencies: [
.Package(url: "https://github.com/Kitura/BlueSocket.git", majorVersion: <majorVersion>, minor: <minor>)
]
Para incluir o bluesocket em um projeto usando o Cartago, adicione uma linha ao seu Cartfile com a organização do Github e os nomes e a versão do projeto. Por exemplo:
github "Kitura/BlueSocket" ~> <majorVersion>.<minor>
Para incluir o bluesocket em um projeto usando Cocoapods, basta adicionar BlueSocket ao seu Podfile , por exemplo:
platform :ios, '10.0'
target 'MyApp' do
use_frameworks!
pod 'BlueSocket'
end
A primeira coisa que você precisa fazer é importar a estrutura do soquete. Isso é feito pelo seguinte:
import Socket
O Bluesocket suporta as seguintes famílias, tipos e protocolos:
Socket.ProtocolFamily.inetSocket.ProtocolFamily.inet6Socket.ProtocolFamily.unixSocket.SocketType.streamSocket.SocketType.datagramSocket.SocketProtocol.tcpSocket.SocketProtocol.udpSocket.SocketProtocol.unixO Bluesocket fornece quatro métodos de fábrica diferentes que são usados para criar uma instância. Estes são:
create() - Isso cria um soquete padrão totalmente configurado. Um soquete padrão é criado com family: .inet , type: .stream e proto: .tcp .create(family family: ProtocolFamily, type: SocketType, proto: SocketProtocol) - Esta API permite criar uma instância Socket configurada personalizada para suas necessidades. Você pode personalizar a família do protocolo, o tipo de soquete e o protocolo de soquete.create(connectedUsing signature: Signature) - Esta API permitirá que você crie uma instância Socket e tente se conectar a um servidor com base nas informações que você passa no Socket.Signature .create(fromNativeHandle nativeHandle: Int32, address: Address?) - Esta API permite envolver um descritor de arquivo nativo descrevendo um soquete existente em uma nova instância do Socket . O Bluesocket permite definir o tamanho do buffer de leitura que ele usará. Então, dependendo das necessidades do aplicativo, você pode alterá -lo para um valor mais alto ou mais baixo. O padrão está definido como Socket.SOCKET_DEFAULT_READ_BUFFER_SIZE , que possui um valor de 4096 . O tamanho mínimo do buffer de leitura é Socket.SOCKET_MINIMUM_READ_BUFFER_SIZE , que está definido como 1024 . Abaixo ilustra como alterar o tamanho do buffer de leitura (manuseio de exceção omitido por brevidade):
let mySocket = try Socket.create()
mySocket.readBufferSize = 32768
O exemplo acima define o tamanho padrão do buffer de leitura para 32768 . Essa configuração deve ser feita antes de usar a instância Socket pela primeira vez.
Para fechar o soquete de uma instância aberta, é fornecida a seguinte função:
close() - Esta função executará as tarefas necessárias para fechar de maneira limpa um soquete aberto.Para usar o Bluesocket para ouvir uma conexão em um soquete, a seguinte API é fornecida:
listen(on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG, allowPortReuse: Bool = true, node: String? = nil) A primeira port de parâmetro é a porta a ser usada para ouvir. O segundo parâmetro, maxBacklogSize permite definir o tamanho da fila segurando conexões pendentes. A função determinará a configuração de soquete apropriada com base na port especificada. Por conveniência no macOS, o Socket.SOCKET_MAX_DARWIN_BACKLOG constante.socket_max_darwin_backlog pode ser definido para usar o tamanho máximo permitido do backlog. O valor padrão para todas as plataformas é Socket.SOCKET_DEFAULT_MAX_BACKLOG , atualmente definido como 50 . Para uso do servidor, pode ser necessário aumentar esse valor. Para permitir a reutilização da porta de escuta, defina allowPortReuse como true . Se definido como false , ocorrerá um erro se você tentar ouvir em uma porta já em uso. O comportamento DEFAULT é allow a reutilização da porta. O último parâmetro, node , pode ser usado para ouvir em um endereço específico . O valor aprovado é uma string opcional que contém o endereço de rede numérico (para IPv4, notação de números e pontos, para IPv6, helicóptero hexidecimal). O comportamento DEFAULT é procurar uma interface apropriada. Se node for incorretamente formatado, um erro soket_err_getaddrinfo_failed será retornado. Se node for formatado corretamente, mas o endereço especificado não estiver disponível, um soquete_err_bind_failed será retornado.listen(on path: String, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) Esta API pode ser usada apenas com a família .unix protocolo. O primeiro path do parâmetro é o caminho a ser usado para ouvir. O segundo parâmetro, maxBacklogSize permite definir o tamanho da fila segurando conexões pendentes. A função determinará a configuração de soquete apropriada com base na port especificada. Por conveniência no macOS, o Socket.SOCKET_MAX_DARWIN_BACKLOG constante.socket_max_darwin_backlog pode ser definido para usar o tamanho máximo permitido do backlog. O valor padrão para todas as plataformas é Socket.SOCKET_DEFAULT_MAX_BACKLOG , atualmente definido como 50 . Para uso do servidor, pode ser necessário aumentar esse valor. O exemplo a seguir cria uma instância Socket padrão e imediatamente começa a ouvir na porta 1337 . NOTA: Manuseio de exceção omitido por brevidade, consulte o exemplo completo abaixo para obter um exemplo de manipulação de exceções.
var socket = try Socket . create ( )
try socket . listen ( on : 1337 )Quando um soquete de escuta detecta uma solicitação de conexão recebida, o controle é retornado ao seu programa. Você pode aceitar a conexão ou continuar ouvindo ou se o seu aplicativo for multi-thread. O Bluesocket suporta duas maneiras distintas de aceitar uma conexão recebida. Eles são:
acceptClientConnection(invokeDelegate: Bool = true) - Esta função aceita a conexão e retorna uma nova instância Socket com base no soquete recém -conectado. A instância que estava ouvindo não afetada. Se invokeDelegate for false e o Socket tiver um delegado SSLService , você deverá chamar o método invokeDelegateOnAccept usando a instância Socket que é retornada por esta função.invokeDelegateOnAccept(for newSocket: Socket) - Se a instância Socket tiver um delegado SSLService , isso invocará os delegados aceitarem a função para executar a negociação do SSL. Deve ser chamado com a instância Socket retornada pela acceptClientConnection . Essa função lançará uma exceção se chamado com a instância Socket errada, chamada várias vezes, ou se a instância Socket não tiver um delegado SSLService .acceptConnection() - Esta função aceita a conexão recebida, substituindo e fechando o soquete de escuta existente. As propriedades anteriormente associadas ao soquete de escuta são substituídas pelas propriedades relevantes para o soquete recém -conectado. Além do create(connectedUsing:) Factory descrito acima, o Bluesocket suporta três funções de instância adicionais para conectar uma instância Socket a um servidor. Eles são:
connect(to host: String, port: Int32, timeout: UInt = 0) - Esta API permite que você se conecte a um servidor com base no hostname e port que você fornece. NOTA: Uma exception será lançada por esta função se o valor da port não estiver no intervalo 1-65535 . Opcionalmente, você pode definir timeout para o número de milissegundos para aguardar a conexão. Nota: Se o soquete estiver no modo de bloqueio, ele será alterado para o modo não bloqueador temporariamente se um timeout maior que zero (0) for fornecido. O soquete retornado será colocado de volta à sua configuração original (bloqueando ou não bloqueando) . Se o soquete estiver definido como não bloqueio e nenhum valor de tempo limite será fornecido , uma exceção será lançada. Como alternativa, você pode definir o soquete para não bloquear após a conexão com sucesso.connect(to path: String) - Esta API só pode ser usada com a família .unix Protocol. Ele permite que você se conecte a um servidor com base no path que você fornece.connect(using signature: Signature) - Esta API permite especificar as informações de conexão fornecendo uma instância Socket.Signature contendo as informações. Consulte o Socket.Signature in Socket.Swift para obter mais informações.O Bluesocket suporta quatro maneiras diferentes de ler dados de um soquete. Estes são (na ordem de uso recomendada):
read(into data: inout Data) - Esta função lê todos os dados disponíveis em um soquete e o retorna no objeto Data que foi aprovado.read(into data: NSMutableData) - Esta função lê todos os dados disponíveis em um soquete e o retorna no objeto NSMutableData que foi aprovado.readString() - Esta função lê todos os dados disponíveis em um soquete e o retorna como uma String . Um nil é retornado se não houver dados disponíveis para leitura.read(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int, truncate: Bool = false) - Esta função permite ler dados em um buffer de um tamanho especificado, fornecendo um ponteiro inseguro para esse buffer e um número inteiro, denota o tamanho desse buffer. Esta API (além de outros tipos de exceções) lançará um Socket.SOCKET_ERR_RECV_BUFFER_TOO_SMALL Se o buffer fornecido for muito pequeno, a menos que truncate = true nesse caso, o soquete agirá como se apenas bytes bufSize foram lidos (os bytes não foram retornados na próxima chamada). Se truncate = false , você precisará ligar novamente com o tamanho adequado do buffer (consulte Error.bufferSizeNeeded no Socket.Swift para obter mais informações).readString() podem retornar zero (0). Isso pode indicar que a conexão remota foi fechada ou pode indicar que o soquete bloquearia (assumindo que você desativou o bloqueio). Para diferenciar entre os dois, a propriedade remoteConnectionClosed pode ser verificada. Se true , o parceiro remoto do soquete fechou a conexão e esta instância Socket deve ser fechada.Além de ler a partir de um soquete, o Bluesocket também fornece quatro métodos para escrever dados para um soquete. Estes são (na ordem de uso recomendada):
write(from data: Data) - Esta função grava os dados contidos no objeto Data no soquete.write(from data: NSData) - Esta função grava os dados contidos no objeto NSData no soquete.write(from string: String) - Esta função grava os dados contidos na String fornecida no soquete.write(from buffer: UnsafeRawPointer, bufSize: Int) - Esta função grava os dados contidos no buffer do tamanho especificado, fornecendo um ponteiro inseguro para esse buffer e um número inteiro que denota o tamanho desse buffer.O Bluesocket suporta três maneiras diferentes de ouvir para datagramas de entrada. Estes são (na ordem de uso recomendada):
listen(forMessage data: inout Data, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) - Esta função ouve um datagrama de entrada, o lê e o retorna no objeto Data aprovado. Ele retorna uma tupla que contém o número de bytes lida e o Address de onde os dados se originaram.listen(forMessage data: NSMutableData, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) - Esta função ouve um datagrama de entrada, o lê e o retorna no objeto NSMutableData aprovado. Ele retorna uma tupla que contém o número de bytes lida e o Address de onde os dados se originaram.listen(forMessage buffer: UnsafeMutablePointer<CChar>, bufSize: Int, on port: Int, maxBacklogSize: Int = Socket.SOCKET_DEFAULT_MAX_BACKLOG) - Esta função ouve um datagrama de entrada, lê e o retorna no objeto Data aprovado. Ele retorna uma tupla que contém o número de bytes lida e o Address de onde os dados se originaram.port especificada. Definir o valor da port como zero (0) fará com que a função determine uma porta livre adequada.maxBacklogSize permite definir o tamanho da fila de retenção de conexões pendentes. A função determinará a configuração de soquete apropriada com base na port especificada. Por conveniência no macOS, o Socket.SOCKET_MAX_DARWIN_BACKLOG constante.socket_max_darwin_backlog pode ser definido para usar o tamanho máximo permitido do backlog. O valor padrão para todas as plataformas é Socket.SOCKET_DEFAULT_MAX_BACKLOG , atualmente definido como 50 . Para uso do servidor, pode ser necessário aumentar esse valor.O Bluesocket suporta três maneiras diferentes de ler datagramas de entrada. Estes são (na ordem de uso recomendada):
readDatagram(into data: inout Data) - Esta função lê um datagrama de entrada e o retorna no objeto Data aprovado. Ele retorna uma tupla que contém o número de bytes lida e o Address de onde os dados se originaram.readDatagram(into data: NSMutableData) - Esta função lê um datagrama de entrada e o retorna no objeto NSMutableData aprovado. Ele retorna uma tupla que contém o número de bytes lida e o Address de onde os dados se originaram.readDatagram(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int) - Esta função lê um datagrama de entrada e o retorna no objeto Data passados. Ele retorna uma tupla que contém o número de bytes lida e o Address de onde os dados se originaram. Se a quantidade de dados lida for mais do que bufSize somente bufSize será retornado. O restante da leitura dos dados será descartado.O Bluesocket também fornece quatro métodos para escrever datagramas em um soquete. Estes são (na ordem de uso recomendada):
write(from data: Data, to address: Address) - Esta função grava o datagrama contido no objeto Data no soquete.write(from data: NSData, to address: Address) - Esta função grava o datagrama contido no objeto NSData no soquete.write(from string: String, to address: Address) - Esta função grava o datagrama contido na String fornecida ao soquete.write(from buffer: UnsafeRawPointer, bufSize: Int, to address: Address) - Esta função grava os dados contidos no buffer do tamanho especificado, fornecendo um ponteiro inseguro a esse buffer e um número inteiro que denota o tamanho desse buffer.address representa o endereço para o destino para o qual você está enviando o datagrama. As APIs de leitura e gravação acima que usam NSData ou NSMutableData provavelmente serão precedentes no futuro não tão distante.
hostnameAndPort(from address: Address) - Esta função de classe fornece um meio de extrair o nome do host e a porta de um determinado Socket.Address . Na conclusão bem -sucedida, uma tupla que contém o hostname e port é retornada.checkStatus(for sockets: [Socket]) - Esta função de classe permite verificar o status de uma matriz de instâncias Socket . Após a conclusão, uma tupla contendo duas matrizes Socket é devolvida. A primeira matriz contém as instâncias Socket que possuem dados disponíveis para serem lidos e a segunda matriz contém instâncias Socket que podem ser gravadas. Esta API não bloqueia. Ele verificará o status de cada instância Socket e retornará os resultados.wait(for sockets: [Socket], timeout: UInt, waitForever: Bool = false) - Esta função de classe permite monitorar uma matriz de instâncias Socket , aguardando o tempo limite ou os dados serem legíveis em uma das instâncias Socket monitoradas. Se um tempo limite de zero (0) for especificado, esta API verificará cada soquete e retornará imediatamente. Caso contrário, ele esperará até que o tempo limite expire ou os dados sejam legíveis a partir de uma ou mais instâncias de Socket monitoradas. Se ocorrer um tempo limite, esta API retornará nil . Se os dados estiverem disponíveis em uma ou mais instâncias Socket monitoradas, essas instâncias serão devolvidas em uma matriz. Se o sinalizador waitForever estiver definido como TRUE, a função aguardará indefinidamente os dados disponíveis , independentemente do valor do tempo limite especificado .createAddress(host: String, port: Int32) - Esta função de classe permite a criação do Address enum, dado um host e port . No sucesso, essa função retorna um Address ou nil se o host especificado não existir.isReadableOrWritable(waitForever: Bool = false, timeout: UInt = 0) - Esta função de instância permite determinar se uma instância Socket é legível e/ou gravável. Uma tupla é retornada contendo dois valores Bool . O primeiro, se verdadeiro, indica que a instância Socket tem dados para ler, o segundo, se verdadeiro, indica que a instância Socket pode ser gravada. waitForever , se verdadeiro, faz com que essa rotina aguarde até que o Socket seja legível ou gravável ou ocorra um erro. Se falso, o parâmetro timeout especifica quanto tempo espera. Se um valor de zero (0) for especificado para o valor do tempo limite, essa função verificará o status atual e retornará imediatamente . Esta função retorna uma tupla contendo dois booleanos, a primeira readable e a segunda, writable . Eles estão definidos como TRUE se o Socket for legível ou gravável de forma repete. Se nenhum deles estiver definido como TRUE, ocorreu um tempo limite. Nota: Se você estiver tentando escrever em um soquete recém -conectado, verifique se ele é gravável antes de tentar a operação.setBlocking(shouldBlock: Bool) - Esta função de instância permite controlar se essa instância Socket deve ou não ser colocada no modo de bloqueio ou não. Nota: Todas as instâncias Socket são, por padrão , criadas no modo de bloqueio .setReadTimeout(value: UInt = 0) - Esta função de instância permite definir um tempo limite para operações de leitura. value é um UInt especifica o tempo para a operação de leitura esperar antes de retornar. No caso de um tempo limite, a operação de leitura retornará 0 bytes Read e errno será definido como EAGAIN .setWriteTimeout(value: UInt = 0) - Esta função de instância permite definir um tempo limite para operações de gravação. value é um UInt especifica o tempo para a operação de gravação esperar antes de retornar. No caso de um tempo limite, a operação de gravação retornará 0 bytes escritos e errno será definido como EAGAIN para soquetes TCP e UNIX , para UDP , a operação de gravação terá sucesso independentemente do valor do tempo limite.udpBroadcast(enable: Bool) - Esta função de instância é usada para ativar o modo de transmissão em um soquete UDP. Passe true a ativar a transmissão, false para desativar. Esta função lançará uma exceção se a instância Socket não for um soquete UDP. O exemplo a seguir mostra como criar um servidor de eco multithread relativamente simples usando a nova API de despacho GCD based . O que se segue é o código para um servidor de eco simples que, uma vez em execução, pode ser acessado via 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 pode ser criado especificando o seguinte arquivo Package.swift usando o 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 "
] ) ,
]
) Ou se você ainda estiver usando o Swift 3, especificando o seguinte arquivo 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 " ]
) A seguinte sequência de comando criará e executará o servidor Echo no Linux. Se estiver executando no macOS ou com qualquer cadeia de ferramentas mais recente que a cadeia de ferramentas 8/18, você poderá omitir o comutador -Xcc -fblocks conforme não é mais necessário.
$ 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
Adoramos conversar sobre Swift e Kitura do lado do servidor. Junte -se à nossa folga para conhecer a equipe!
Esta biblioteca está licenciada no Apache 2.0. O texto completo da licença está disponível na licença.