
Ямар нэгэн толилуур (Mozilla, FireFox, Internet Explorer гэх мэт) ашиглан хандсан хэрэглэгч бүр рүү Welcome, Sir/Madame гэсэн мэдээ илгээх маш хялбархан вэб үйлчлэгчийн жишээ хөтөлбөрийг дор харуулав.
package web.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.Date;
/**
*
* @author Erdenebat Byambaa, USI company
* вэб үйлчлэгчийн маш хялбар жишээ хөтөлбөр
*/
public class SimplestServer {
static final int PORT = 1010;
static final int BUFFER_SIZE = 10000;
static final String NEWLINE = "\r\n";
static final String OK = "HTTP/1.1 200 OK" + NEWLINE;
static String WelcomeMessage = "<html>" +
"<head><title>Welcome</title></head>" +
"<body><h1>" +
"Welcome, Sir/Madame" +
"</h1></body>"+
"</html>";
static long length = WelcomeMessage.length();
public static void main(String[] args) throws IOException {
// Тооцоолууруудын хооронд мэдээлэл солилцохдоо Socket буюу Сүв
// ашигладаг. Сүв нь мэдээлэл хүлээн авах үүрэгтэй ServerSocket
// буюу ҮйлчлэгчСүв, мэдээлэл илгээх үүрэгтэй Socket буюу Сүв
// гэсэн үндсэн хоёр хэлбэртэй. Шинээр Сүв үүсгэхдээ портын дугаар
// буюу боомтын дугаарыг зааж өгнө. Боомтын дугаар нь 0-ээс 65535
// хүртэлхи дурын бүхэл тоо байж болно. Нэг боомт дээр зөвхөн нэг
// сүв үүсгэх боломжтой. 1024 хүртэлхи бүх боомтууд эзэнтэй байдаг.
ServerSocket server = new ServerSocket(PORT);
System.out.println("listening on " + PORT);
// үндсэн эргүүлэг
// аливаа үйлчлэгч хөтөлбөр битүү тойргоор тойрон эргэлдэх
// зарчимтай.
while (true) {
// ҮйлчлэгчСүв нь өөрийн боомт дээр гаднаас мэдээлэл ирж буй эсэхийг
// байнга шалгаж байдаг. Хэрвээ гаднаас мэдээлэл ирэхгүй бол цааш
// хөдлөхгүй. Үүнийг сонсох гэнэ.
// Хэрэв гаднаас мэдээлэл ирвэл ямар нэгэн сул чөлөөтэй
// боомт дээр сүв үүсгэнэ. Сүв гэдгийг өөрөөр нүх гэж ойлгож болно.
// Шинээр үүссэн Сүв нь гаднаас холбогдсон нөгөө тооцоолуурын сүвтэй
// холбогдохоороо нэг төрлийн хоолой үүсгэнэ.
// Ямар нэгэн гэмтэл гарахыг эс тооцвол, харилцагч хоёрын аль нэг нь
// сүвээ хаахад хоолой тасарч тухайн сүв дахин сул, чөлөөтэй болно.
// Тооцоолуур дээр олон хөтөлбөр зэрэгцэн гүйдгийг санах хэрэгтэй.
// Тиймээс сүлжээний хөтөлбөр бичихдээ ямар, ямар хөтөлбөр аль, аль
// боомтыг ашигладаг талаар мэдэж байх хэрэгтэй.
// Эхлээд хөтөлбөрийн явдлаа түр зогсоон сонсоё.
// Шинээр хэрэглэгч холбогдмогц, үүссэн Сүвийг socket нэртэй
// хувьсагч руу хадгалан цааш хөдлөе.
Socket socket = server.accept();
// Хөтөлбөр энд ирнэ гэдэг нь хэрэглэгч холбогджээ гэсэн үг.
// Сүв нь ОрцУрсгуур ба ГарцУрсгуур гэсэн хоёр хэсэгтэй.
// ОрцУрсгуур нь мэдээллийг зөвхөн хүлээн авч унших үүрэгтэй.
// ГарцУрсгуур нь мэдээллийг зөвхөн бичиж илгээх үүрэгтэй.
// Энэ хоёр нийлэхээрээ хоёр чиглэлтэй хоолой болно.
// ГарцУрсгуур руу мэдээлэл бичсэн л бол тухайн мэдээллийг
// буцаан авах боломжгүйгээр цааш урсган авч одно.
// ОрцУрсгуураас мэдээлэл уншсан л бол тухайн мэдээлэл
// дахин сэргэхгүйгээр урсгуураас сорогдон үгүй болно.
// Үүссэн сүвийнхээ ОрцУрсгуурыг in хувьсагч руу хадгалъя.
// Хэрэглэгчийн хүсэлтийг үүгээр дамжуулан уншина.
InputStream in = socket.getInputStream();
// Үүссэн сүвийнхээ ГарцУрсгуурыг out руу хадгалъя.
// Хариугаа үүгээр дамжуулан хэрэглэгч рүү илгээнэ.
OutputStream out = socket.getOutputStream();
// ОрцУрсгуураас унших нэг байт мэдээлэл түр хадгалах үүр.int data = 0;
// Энэ бүл дотор 256 хүртэл байт мэдээлэл хадгалж болно.
// Хэрэглэгчээс ирэх хүсэлт хэдэн ч мөрөөс бүрдэж болно.
// Гэхдээ нэг мөр нь 256 ширхэг байтаас хэтрэхгүй гэж үзэв.
byte[] line = new byte[256]; //
// мөрийн төгсгөлд 0d 0a гэсэн хоёр байт заавал олдох ёстой.
// 0d байтыг CR, харин 0a байтыг NL гэж тэмдэглэсэн байгаа.
// Энэ хоёр байтаар мөр хаана эхэлж, хаана дуусч буйг мэднэ.
boolean CR = false; // return код мөн эсэх
boolean NL = false; // new line код мөн эсэх
// ОрцУрсгуураас мэдээлэл уншихдаа хэрэглэх тоолуур.
int count = 0;
// Энэ бүл дотор 10000 ширхэг байт хадгалж болно.
// Тэгэхлээр нэлээд олон мөр багтаах чадалтай гэсэн үг.
// Одоогоор бид үүнийг ашиглахгүй.
byte[] buffer = new byte[BUFFER_SIZE];
// хэрвээ ОрцУрсгуураас -1 гэсэн утга ирвэл орц хаагджээ,
// цаашдаа мэдээлэл ирэхгүй гэсэн үг.
// Энэ тохиолдолд, давталтаас гарч цааш явна.
// -1 утга ирсний дараа орцоос унших үйлдэл хийж болохгүй.
// Уншина гэдэг нь хүлээнэ гэсэн үг, харин хүлээнэ гэдэг нь
// мэдээлэл иртэл байрнаасаа хөдлөхгүй зогсоно гэсэн үг.
//
// Хэрвээ энэ хөтөлбөрийн кодыг бүхэлд нь гүйлгэн харж
// явдлыг нь шинжвэл дараахь байдалтай:
// эхлэл
// 1. сонсох
// 2. хүсэлт тайлан унших
// 3. хариу илгээх
// 4. сүвээ хаах
// төгсгөл
// Нэг хүсэлт боловсруулахдаа дээрхи дөрвөн шатыг дамжиж байна.
// Сүүлийн шатанд сүвээо хаасныхаа дараа дахиад эхний шатандаа
// эргэн ирж дараагийн хүсэлт ирэхийг хүлээнэ. Үйл явдал ийм
// байдлаар тасралтгүй үргэлжлэн эргэлдэнэ.
// Хүсэлтийг тайлан уншихдаа өөр нэг эргүүлэг үүсгэж байна.
// Явдлыг нь тоймолбол:
// эхлэл
// 1. байт унших
// 2. уншсан байтаа мөр рүү нэмэх
// 3. төлөвөө өөрчлөх
// төгсгөл
// Хүсэлтийг тайлан уншихдаа энэ гурван шатыг дамжиж байж нэг
// байт уншина. Хэрэглэгчээс ирэх нэг хүсэлт хэдэн ч мөрнөөс
// бүрдэж болно. Бас мөр бүхэн ондоо. Тэгэхлээр хэдэн байт
// унших вэ, хэзээ эргүүлгээ зогсоож дараагийн шат руу шилжих
// вэ гэдэг тодорхойгүй.
// Энэ асуудлыг шийдэхийн тулд дотоод төлвийн хувьсагчдыг
// ашигласан байна. Ямар ч байтыг уншихад мөрийн төгсгөл мөн,
// мөрийн төгсгөл биш, хоосон мөр гэсэн гурван төлвийн аль
// нэгэнд заавал оршино.
// Хэрвээ хоосон мөр байвал эргүүлгээс шууд гарна. Манай
// эргүүлгийн нэг онцлог бол төгсгөлдөө хүрээд эхлэлдээ заавал
// буцаж ирдэг, харин дараа нь эргүүлгээс гарах эсэхээ шийднэ.
// Гэтэл эхлэл маань унших үйлдэл хийж байна.
// Хоосон мөр тааралдахад эргүүлгийн төгсгөлөөс шууд таслан
// гарснаар унших үйлдлээс мултарч байна. Хоосон мөр гэдэг нь
// дахиж мэдээлэл ирэхгүй гэсэн үг. Мэдээлэл ирэх явцын дундуур // ямар нэгэн гэмтлийн улмаас хэрэглэгчтэй тогтоосон хоолой // тасарч бас болно. Энэ тохиолдолд хэрэглэгчээс дахин// мэдээлэл ирэхгүй.
// Товчоор хэлэхэд, ОрцУрсгуураас / жишээ нь in.read() / унших
// үйлдэл хийхдээ тун хянуур, болгоомжтой байх ёстой. Учир нь
// энэ үйлдэл нэг байт мэдээлэл ирэн иртэл хүлээгээд зогсдог.
// Хэрэв нөгөө тал мэдээлэл дамжуулахаа зогсоочихсон байхад нь
// унших гэж оролдвол тэр чигээрээ үүрд хүлээнэ гэсэн үг.
// Ийм үед хөтөлбөрийг хүчээр унтраахгүй л бол цаашаа явахгүй.
// Уул нь нэг хэрэглэгчийн хүсэлтийг хүлээн авч хариуг нь
// явуулаад дараагийн хүсэлтээ хүлээн авах ёстой. Гэтэл нэг
// хэрэглэгч дээр очоод зогсчих аваас дараагийн хэрэглэгчтэй
// харьцаж чадахаа болино.
// Энэ мэт логикийг доороос харж болно.
while ((data = in.read()) > -1) {
line[count] = (byte)data; // уншсан байтаа мөр рүү хадгалъя
count++;
String hex = Integer.toHexString(data);
hex = (hex.length()==1? "0"+hex: hex);
// ямар байт ирснийг арванзургаатын кодоор нь харья
System.out.print(hex + " ");
switch (data) {
case 13: CR = true; break;
case 10: NL = (CR ? true: false); break;
default: CR = false; NL = false; break;
}
// шинэ мөр гэдэг нь дараах хоёр байт: 13 10;
if ( CR && NL ) {
// хоосон мөр гэдэг нь толгойн хэсэг дууслаа
// гэсэн үг.
// хоосон мөр хоёрхон байтаас бүрдэнэ: 13 10
if (count == 2) {
break;
} else {
// мөр бүтээе
byte[] temp = new byte[count];
System.arraycopy(line,0,temp,0,count);
// мөрөө бичвэр болгон харъя
System.out.println();
System.out.print(new String(temp));
System.out.println();
// шинэ мөр
count = 0;
CR = false;
NL = false;
}
}
}
// хэрэглэгчээс ирсэн хүсэлтийг тайлж уншиж дууслаа
// хариугаа илгээе
System.out.println();
System.out.println("sending response ...");
// эхлээд OK мэдээ илгээнэ
out.write(OK.getBytes());
// http header хэсгээ илгээе
out.write("Content-Type: text/html".getBytes());
out.write(NEWLINE.getBytes());
out.write("Content-Length: ".getBytes());
out.write(Long.toString(length).getBytes());
out.write(NEWLINE.getBytes());
out.write("Date: ".getBytes());
out.write(new Date().toString().getBytes());
out.write(NEWLINE.getBytes());
out.write("Server: Simplest Web Server".getBytes());
out.write(NEWLINE.getBytes());
// http header хэсгээ илгээж дууслаа
// тиймээс, хоосон мөр илгээе
out.write(NEWLINE.getBytes());
// http body биеэ илгээе
out.write(WelcomeMessage.getBytes());
in.close();
out.close();
socket.close();
} // main loop
} // main method
} // SimplestServer
энэ хөтөлбөрийн гарц:
listening on 1010
47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a
GET / HTTP/1.1
48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 31 30 31 30 0d 0a
Host: localhost:1010
55 73 65 72 2d 41 67 65 6e 74 3a 20 4d 6f 7a 69 6c 6c 61 2f 35 2e 30 20 28 57 69 6e 64 6f 77 73 3b 20 55 3b 20 57 69 6e 64 6f 77 73 20 4e 54 20 35 2e 31 3b 20 65 6e 2d 55 53 3b 20 72 76 3a 31 2e 38 2e 30 2e 38 29 20 47 65 63 6b 6f 2f 32 30 30 36 31 30 32 35 20 46 69 72 65 66 6f 78 2f 31 2e 35 2e 30 2e 38 0d 0a
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.8) Gecko/20061025 Firefox/1.5.0.8
41 63 63 65 70 74 3a 20 74 65 78 74 2f 78 6d 6c 2c 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 6d 6c 2c 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 68 74 6d 6c 2b 78 6d 6c 2c 74 65 78 74 2f 68 74 6d 6c 3b 71 3d 30 2e 39 2c 74 65 78 74 2f 70 6c 61 69 6e 3b 71 3d 30 2e 38 2c 69 6d 61 67 65 2f 70 6e 67 2c 2a 2f 2a 3b 71 3d 30 2e 35 0d 0a
Accept: text/xml,application/xml,application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8,image/png,*/*;q=0.5
41 63 63 65 70 74 2d 4c 61 6e 67 75 61 67 65 3a 20 65 6e 2d 75 73 2c 65 6e 3b 71 3d 30 2e 35 0d 0a
Accept-Language: en-us,en;q=0.5
41 63 63 65 70 74 2d 45 6e 63 6f 64 69 6e 67 3a 20 67 7a 69 70 2c 64 65 66 6c 61 74 65 0d 0a
Accept-Encoding: gzip,deflate
41 63 63 65 70 74 2d 43 68 61 72 73 65 74 3a 20 49 53 4f 2d 38 38 35 39 2d 31 2c 75 74 66 2d 38 3b 71 3d 30 2e 37 2c 2a 3b 71 3d 30 2e 37 0d 0a
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
4b 65 65 70 2d 41 6c 69 76 65 3a 20 33 30 30 0d 0a
Keep-Alive: 300
43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 6b 65 65 70 2d 61 6c 69 76 65 0d 0a
Connection: keep-alive
43 61 63 68 65 2d 43 6f 6e 74 72 6f 6c 3a 20 6d 61 78 2d 61 67 65 3d 30 0d 0a
Cache-Control: max-age=0
0d 0a
sending response ...
Энд, үйлчлэгч, үйлчлүүлэгч хоёрын хооронд явагдаж буй харилцан яриаг байт болон бичвэр гэсэн хоёр хэлбэрээр зэрэг харуулсан байна. Гэхдээ зөвхөн header буюу толгойн хэсэг.
Хөтөлбөр маань сүвээр (сокет) дамжин хэрэглэгчээс (толилуур) ирж буй өгөгдлийг эхлээд байт мөр (line, дараа нь temp), дараа нь тэмдгийн хэлхээс / new String(temp) / болгон хувиргаж байна. Мөрийн төгсгөлийг 0d, 0a (ердийн 13, 10) гэсэн хоёр байтаар дүрсэлдгийг сануулъя.
Хэрвээ тухайн мөр зөвхөн 0d, 0a гэсэн хоёрхон байтаас бүрдэж байвал уг мөр нь хоосон мөр гэсэн үг. Энэ нь толгойн хэсэг дууслаа гэсэн үг.
Одоохондоо хариу мэдээ маань WelcomeMessage хувьсагч доторхи тогтмол бичвэр байна. Үүнийг хэрхэн файлаас уншдаг болгон өөрчлөх вэ?
1 comment:
Shal手ねg湯m。Concepts -iig ni hyalbar oilguulah bolomjtoi yumiig.
Post a Comment