В связи со своим вынужденным переходом в разработчики Java я получил довольно интересную возможность посмотреть на C и C++ со стороны. В целом, мне нравится то что я вижу глядя на C++ с позиции Java разработчика, но, само собой, нашлось одно «но», которое, если быть честным, мне и раньше не давало покоя: C++ совершенно не подходит для быстрого прототипирования. По большому счету это единственный недостаток который я отмечаю на данный момент.
Если говорить про Java, то с быстрым прототипированием тут все нормально, в то время как скорость разработки приложений приблизительно такая же•• как и в случае с C++, ну может на 10% быстрее. При этом язык крайне ограниченный, практически до убогости, что после 10 лет разработки на C++ вызывает довольно ощутимое отторжение.
Понимая, что так дело не пойдет, я взялся за поиск приличной альтернативы C++. Изначально подумывал про Erlang, ведь в основном я пишу что-то сетевое, но в итоге от этого варианта решил отказаться, потому как за пределами сетей Erlang мягко говоря бесполезен, а в самих сетях, зачастую, ограничен в плане набора доступных библиотек. В результате довольно долгих поисков выбор остановился на JVM, ведь JVM — это действительно кроссплатформенное решение (в отличие от того же CLR) с большой базой библиотек под нормальными лицензиями (MIT, BSD и, в худшем случае, LGPL). Плюс, в JDK, начиная с 7-й версии, появилась поддержка асинхронного ввода/вывода, т.е. возникла вполне реальная возможность писать не тормозящие где попало сетевые приложения.
К основным языкам базирующимся на JVM, само собой за исключением самой Java, относятся Clojure, Groovy и Scala. Clojure — Lisp-подобный язык, так что отмел его сразу. Да, я к Lisp отношусь хорошо, но вот куча народу вокруг — нет, так что никакой возможности реально использовать что-то Lisp-подобное я не вижу. Groovy — хороший язык, но без чего-то действительно цепляющего. А вот Scala… Да, Scala — это язык с довольно крутой кривой вхождения, при этом богатым синтаксисом и отличными возможностями.
У всех разные способы оценить пригодность языка для своих нужд. Кто-то первым делом реализует быструю сортировку, кто-то пишет Hello World. А я, обычно, пишу Echo-сервер с клиентом.В данном посте я начну просто с исходников сервера с клиентом, а позже распишу что, как и почему. Хорошо знающим Scala, конечно, посчитают это скучным, но вот тем кто только думает не выучить ли этот замечательный язык, полагаю, будет интересно. Как мне кажется на данный момент, само собой, почти наверняка это не так, в коде присутствуют основные интересные фичи языка Scala.
Клиент и сервер построены на самой примитивной и не желательной к использованию в продакшн концепции: блокирующий ввод/вывод, одно соединение — один поток.
Echo-сервер.
import java.io.{InputStreamReader, BufferedReader, InputStream, OutputStream, PrintWriter, OutputStreamWriter}
import java.net.{SocketException, ServerSocket, Socket}
import util.logging.ConsoleLogger
case class Client(socket: Socket);
class Worker extends Actor with ConsoleLogger {
implicit def inputStreamWrapper(in: InputStream): BufferedReader =
new BufferedReader(new InputStreamReader(in))
implicit def outputStreamWrapper(out: OutputStream): PrintWriter =
new PrintWriter(new OutputStreamWriter(out))
def act {
loop {
react {
case Client(socket) => {
log("New remote connection from " + socket.getRemoteSocketAddress )
log(Thread.currentThread() + "Curent thread ")
handle(socket.getInputStream, socket.getOutputStream)
socket.close()
log("Remote connection was closed")
exit()
}
}
}
}
private def handle(reader : BufferedReader, writer : PrintWriter) {
var line = reader.readLine()
while(line != null) {
writer.println(line)
writer.flush()
line = reader.readLine()
}
}
}
object EchoServer extends ConsoleLogger {
def main(args: Array[String]) {
var serverPort = 8088
def parseArgs(argsList : List[String]) {
argsList match {
case "--port" :: value ::tail =>
serverPort = value.toInt
parseArgs(tail)
case "--help" :: value :: tail =>
println("usage: EchoServerIO [--port port_number]")
sys.exit()
case option :: tail =>
println("Unknown option")
sys.exit()
case Nil => ()
}
}
parseArgs(args.toList)
try {
val serverSocket = new ServerSocket( serverPort )
log("Waiting for connections on " + serverSocket.getLocalSocketAddress)
while(true) {
val clientConnection = serverSocket.accept()
val worker = new Worker
worker.start()
worker ! Client( clientConnection )
}
} catch {
case sockErr: SocketException =>
log("Network error: " + sockErr)
case generalErr : Exception =>
log("General error: " + generalErr)
}
}
}
Echo-клиент.
import java.io.{OutputStreamWriter, InputStreamReader, BufferedReader, PrintWriter}
import java.net.{Socket}
import scala.util.control.Breaks._
import util.logging.ConsoleLogger
import util.Random
case class DestAddress(addr : String, port : Int, msgCount : Int);
class EchoClientImpl extends Actor with ConsoleLogger {
val outMsg = Random.nextString(100)
private def exchange(msgCount : Int, in : BufferedReader, out : PrintWriter) {
breakable {
for ( i <- 0 until msgCount ) {
out.println(outMsg)
out.flush()
val line = in.readLine()
if (outMsg != line) {
log("Invalid output string.")
break
}
}
}
}
def act() {
loop {
react {
case DestAddress(addr, port, msgCount) => {
log("Establishing new connection to " + addr + ":" + port)
try {
val socket = new Socket(addr, port)
val in = new BufferedReader(new InputStreamReader(socket.getInputStream))
val out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream))
exchange(msgCount, in, out)
socket.close()
} catch {
case err : Exception => println("Error: " + err)
}
log("Done.")
exit()
}
}
}
}
}
object EchoClient extends ConsoleLogger {
def main(args: Array[String]) {
var serverPort = 8088
var serverAddr = "127.0.0.1"
var connectionsCount = 16
var msgCount = 100
def parseArgs(argsList : List[String]) {
argsList match {
case "--addr" :: value ::tail =>
val address = """(.+):(d+)""".r
value match {
case address(ip, port) => {
serverAddr = ip
serverPort = port.toInt
}
}
parseArgs(tail)
case "--connections" :: value ::tail =>
connectionsCount = value.toInt
parseArgs(tail)
case "--help" :: value :: tail =>
println("usage: EchoClientIO [--addr ip_address:port] [--connections connections_count]" +
" [--messages messages_count]")
sys.exit()
case option :: tail =>
println("Unknown option")
sys.exit()
case Nil => ()
}
}
parseArgs(args.toList)
for( i <- 0 until connectionsCount) {
log("Client Nr" + i + " was started.")
val client = new EchoClientImpl
client.start()
client ! new DestAddress(serverAddr, serverPort, msgCount)
}
}
}
Если остановился на JVM, посмотри на Kotlin (если он зарелизился). Лучшие собаководы вроде рекомендовали (при этом критиковали скалу за время компиляции
Кстати да, надо и на него взглянуть. Спасибо что напомнил!
Кстати, еще есть Ceylon от КраснойШапочки.
UPD. Поглядел на оба. Первый (Kotlin) – люди явно не осилили Scala, что не удивительно, он довольно тяжелый для вхождений. Второй (Ceylon) – попытка создать продвинутую версию Java с проблемами с совместимостью с реальной Java. Вобщем, не торт.
А в Scala мне очень нравится то, что это эдакий Erlang на JVM.
Pingback: Слегка разочаровался | System Development