CHƯƠNG 1 PHÂN TÍCH BÀI TỐN 1.1.TấN ĐỀ TÀI
Tỡm hiểu xõy dựng ứng dụng thư điện tử
1.2.DỀ CƯƠNG CHI TIẾT 1.2.1.Khảo sỏt 1.2.1.Khảo sỏt
Trong thời gian thực tập tốt nghiệp tụi đó khảo sỏt, tỡm hiểu hệ thống thư tớn điện tử. Quỏ trỡnh xõy dựng một ứng dụng thư điện tử (Email) rất đa dạng nhưng chủ yếu tập trung vào hai phần:
ư Xõy dựng mail server : là chương trỡnh hoạt động phớa mỏy chủ nhận, lưu trữ mail, phõn phối, gởi mail đến cỏc trỡnh chủ khỏc. Cỏc chương trỡnh như Mail Deamon, SendMail, Mail Exchange… là những mail server.
ư Xõy dựng mail client : là chương trỡnh hoạt động phớa mỏy khỏch thực hiện chức năng cho phộp người dựng nhập vào nội dung mail, gởi mail đến mỏy chủ mail server xỏc định. Nhận mail từ mỏy chủ về và hiển thị cho người dựng xem nội dung mail. Vớ dụ như Outlook Express của Windows hay Web mail trờn Internet là những trỡnh đúng vai trũ mail client.
1.2.2.Yờu cầu của bài toỏn
Yờu cầu chớnh của bài toỏn
ư Phần mail client: thực hiện được cơ bản nhất những chức năng của một mail client như việc gởi, nhận, hiển thị nội dung mail thụng qua trỡnh duyệt Web với giao thức HTTP của Internet.
ư Phần mail server : thực hiện được chức năng tiếp nhận mail do trỡnh khỏch (mail client) gửi lờn (SMTP Server), lưu trữ mail trong thư mục nhất định cho phộp người dựng sử dụng giao thức POP3 đọc mail (POP3 Server), chuyển mail đến mỏy chủ khỏc (Forward Server) hoặc phõn giải địa chỉ mail gửi thẳng đến đớch (Relay Server).
1.2.3.Dữ liệu vào, dữ liệu ra và cỏc chức năng xử lý của hệ thống
* Dữ liệu vào :
ư Phần mail client :
+ Thụng tin đăng ký của người dựng ư Phần mail server :
+ Mail do trỡnh khỏch gửi lờn
+ Thụng tin về vị trớ thư mục lưu trữ mail trờn server * Dữ liệu ra :
ư Phần mail client :
+ Thụng tin về tài khoản thư điện tử của người dựng. + Thư mục tương ứng với tờn tài khoản, thụng tin về user name và password để dựng cho việc POP3 Server chứng thực quyền truy cập của người dựng khi cần đọc mail
ư Phần mail server :
+ Lưu trữ mail do trỡnh khỏch gửi đến
+ Chuyển tiếp những mail khụng thuộc domain mail do mail server quản lý.
1.2.4. Chức năng của hệ thống thụng tin quản lý
* Quản lý toàn bộ thụng tin liờn quan đến user như: họ, tờn, ngày thỏng năm sinh, nghề nghiệp, giới tớnh, quốc gia, thành phố…
* Quản lý sổ địa chỉ
1.3. Lí DO CHỌN ĐỀ TÀI
Ngày nay đối với mỗi chỳng ta thư điện tử khụng cú gỡ xa lạ tuy nhiờn đú là đứng về phương diện người dựng. Xuất phỏt từ mong muốn tỡm hiểu một cỏch tường tận hơn hệ thống thư điện tử nhỡn từ khớa cạnh nhà thiết kế nờn tụi đó quyết định chọn đề tài xõy dựng ứng dụng thư điện tử theo mụ hỡnh Client Server.
CHƯƠNG 2 : THIẾT KẾ VÀ CÀI ĐẶT ỨNG DỤNG 2.1.PHÂN TÍCH VÀ THẾT KẾ CƠ SỞ DỮ LIỆU 2.1.PHÂN TÍCH VÀ THẾT KẾ CƠ SỞ DỮ LIỆU
2.1.1.Phõn tớch
Cơ sở dữ liệu được thiết kế đơn giản và dựng vào mục đớch quản lý danh sỏch thành viờn đăng ký sử dụng dịch vụ thư điện tử của vietmail.
Với mục đớch đú CSDL chỉ bao gồm hai thực thể chớnh là được thể hiện trong bảng sau :
1 members
(thành viờn)
userid (mó thành viờn ), user_name (tờn đăng nhập),
password (mật khẩu), question (cõu hỏi), answer (cõu trả lời), ho (họ), ten (tờn), ngay (ngày sinh), thang (thỏng sinh),
nam (năm sinh), gioi_tinh (giới tớnh)
nuoc (quốc gia), thanh_pho (thành phố), thanh_pho_khac (thành phố khụng thuộc Việt Nam), job (nghề nghiệp),
thong_tin_khac (thụng tin phụ khỏc), date(ngày đăng ký)
2 addressbook
(Sổ địa chhỉ)
addressid (mó sổ địa chỉ), userid (mó thành viờn), quickname (tờn gợi nhớ), ho (họ), ten (tờn), email (địa chỉ
email), phone (số điện thoại), diachi (địa chỉ)
CÁC LƯỢC ĐỒ QUAN HỆ
Từ những thực thể và thực thể trung gian trờn, bằng cỏc nguyờn tắc biến đổi, ta xõy dựng thành cỏc lược đồ quan hệ như sau:
Member (userid, user_name, password, question, answer, ho, ten, ngay, thang,
nam, gioi_tinh, nuoc, thanh_pho, thanh_pho_khac, job, thong_tin_khac, date)
Lược đồ quan hệ dữ liệu
Phõn tớch chức năng
Sơ đồ biểu diễn chức năng của hệ thống
2.1.2. Giải thớch cỏc chức năng của hệ thống
ư chức năng : Đăng ký thành viờn
Hệ thống quản lý thành viờn của vietmail
Đăng ký thành viờn Quản lý sổ địa chỉ
member userid user_name password question answer ho ten ngay thang nam gioi_tinh nuoc thanh_pho thanh_pho_khac job thong_tin_khac date addressbook addressid userid quickname ho ten email phone diachi
ý nghĩa : thu thập cỏc thụng tin của thành viờn nhằm mục đớch quản lý cũng như thiết lập cỏc thụng số phục vụ cho việc gởi và nhận thư điện tử của thành viờn
ư chức năng : Quản lý sổ địa chỉ
ý nghĩa : tạo một danh sỏch boa gồm cỏc địa chỉ do thành viờn tự tạo rong quỏ trỡnh sử dụng hệ thống.
2.1.3.biểu đồ luồng dữ liệu( DFD – Data flow Diagram)
.Biểu đồ luồng dữ liờu mức khung cảnh (BFD)
2.1.4. THIẾT KẾ HỆ THỐNG
2.1.4.1. Cỏc bảng dữ liệu chớnh
Ký hiệu:
PK: Khoỏ chớnh (Primary Key) FK: Khoỏ ngoại (Foreign Key)
Bảng dữ liệu: Member ( Thành viờn)
Yờu cầu Thành viờn Đăng ký Thụng bỏo Hệ thống vietmail
Tờn trường Kiểu DL Độ lớn Ràng buộc Khoỏ Ghi chỳ
userid int 4 Not Null PK
user_name varchar 20 Not Null Tờn đăng nhập
password char 10 Mật khẩu
question text 16
answer text 16
ho text 16 Not Null
ten text 16 Not Null
ngay smallint 2 Not Null
thang smallint 2 Not Null
nam int 4 Not Null
gioitinh char 5 Not Null
nuoc text Not Null
thanh_pho_k hac text 16 job int 4 thong_tin_k hac text 16
date datetime 8 Not Null
Bảngdữ liệu : Addresbook (sổ địa chỉ)
Tờn trường Kiểu DL Độ lớn Ràng buộc Khoỏ Ghi chỳ
addressid int 7 Not Null PK
userid int 8 Not Null Fk
quickname text 8
ho text 8
email text Not Null
phone text
diachi text
2.2. CÀI ĐẶT MAILSERVER
2.2.1.Phương ỏn tổ chức lưu trữ mail trờn Server
Để lưu trữ mail gửi đến trờn server, mỗi trỡnh mailserver sẽ cú một phương ỏn riờng để lưu trữ. Chẳng hạn cú thể lưu trữ thụng điệp mail như là cỏc record trong bảng dữ liệu của database hoặc lưu trong cựng một file text phõn cỏch mỗi thụng điệp bằng một dấu hiệu đặc trưng nào đú. Tuy nhiờn việc lưu thụng điệp mail dưới dạng cỏc file trong từng thư mục tương ứng của cỏc User tỏ ra đơn giản hơn và cũng khụng kộm phần hiệu quả. Vỡ vậy trong đồ ỏn này tụi quyết định chọn phương ỏn lưu thụng điệp mail dưới dạng tập tin trờn đĩa cục bộ. Trước khi cài đặt cỏc đơn thể Mail server ta cần tổ chức thư mục để SMTP server lưu trữ mail như sau:
Mỗi mailserver cú thể cú nhiều tờn domain cho địa chỉ mail, cỏc tờn domain này được tổ chức trong 1 thư mục, ở đõy ta chọn vietmail.com là tờn domain mail. Lỳc
này một địa chỉ e – mail hợp lệ gửi đến SMTP server của ta phải cú dạng như sau
username@vietmail.com. Hệ thống mail server cũn cho phộp mở rộng thờm vào cỏc
domain mail khỏc. Danh sỏch cỏc tờn domain main nằm trong file domain.txt.
Trong thư mục email\data\vietmail.com chứa danh sỏch cỏc thư mục con đại diện cho từng tài khoản (như thanhboeing, xuanthu…). Thụng tin đăng nhập của tài khoản được đặt trong file User.txt.
2.2.2.Cỏc đơn thể của mailserver
Trỡnh MailServer cú cỏc đơn thể được cài đặt bằng ngụn ngữ java và bao gồm cỏc phần chớnh sau:
ư Đơn thể xử lý tập lệnh SMTP ư Đơn thể xử lý tập lệnh POP3
2.2.2.1. Xõy dựng SMTP Server
Nhiệm vụchớnh của SMTP Server là sử lý tập lệnh SMTP, trả lại mó lỗi do trỡnh khỏch gởi lờn khụng hợp lệ. Tiếp nhận dữ liệu và lưu vào thư mục nhất định để trỡnh chủ POP3 Server cú thể truy xuất sau này. SMTP server sẽ mở socket lắng nghe trờn cổng 25 (cổng mặc định của SMTP). Ta cũng cú thể thay đổi số hiệu cổng trong file cấu hỡnh email.properties. khi nhận được kết nối từ trỡnh khỏch, SMTP server sẽ mở một tuyến (thread) là lớp SMTPConection chịu trỏch nhiệm phõn tớch cỏc lệnh SMTP và nhận mail do trỡnh khỏch gởi lờn. Lớp SMTP được cài đặt như sau:
SMTPServer.java
import java.io.*; import java.net.*;
import java.util.*;
public class SMTPServer extends Thread {
protected ServerSocket listenSocket = null; protected Vector connections;
public Boolean stopRequested;
public SMTPServer() throws Exception { int port = 0;
String portString = null;
// Lấy số hiệu cổng S MTP từ file cấu hỡnh try {
portString = Server.properties.getProperty("smtp.port"); port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
throw new Exception("Invalid 'smtp.port' ư " + portString); }
// Mở socket lỏng nghe kết nối từ trỡnh khỏch listenSocket = new ServerSocket(port); // Mảng lưu cỏc kết nối từ trỡnh khỏch connections = new Vector(10, 10);
}
public void removeConnection(SMTPConnection connection) { connections.removeElement(connection);
}
public void run() {
// Lặp vụ tận chờ nhận kết nối cho đến khi cú tớn hiệu dừng stopRequested = new Boolean(false);
while (true) { synchronized (stopRequested) { if (stopRequested.booleanValue()) break; } // chấp nhận kết nối try { Socket s = listenSocket.accept(); System.out.println("Accept"); //Phõn tớch cỏc lệnh SMTP của trỡnh khỏch – thực hiện việc tiếp nhận mail
SMTPConnection connection = new SMTPConnection(s, this); connections.addElement(connection);
connection.start(); } catch (IOException e) { e.printStackTrace(); break; } } // Đúng kết nối try { listenSocket.close(); } catch (IOException e) { e.printStackTrace(); } } }
Lớp SMTPConnection đúng vai trũ chớnh trong SMTPServer. SMTPConnection phõn tớch cỏc lệnh SMTP, nhận mail và lưu và thư mục tương ứng với địa chỉ mail của User. Chương trỡnh mailserver cũng cho phộp khả năng relayvà chuyển tiếp mail. Cỏc mail cú địa chỉ khụng thuộc domain do mailserver quản lý sẽ được lưu và thư mục queue (hay hàng đợi). Chương trỡnh SMTPRelayServer và SMTPRForrwardServer sẽ xử lý cỏc mail này. Lớp SMTPConnection được cài đặt như sau:
SMTPConnection.java
import java.io.*; import java.net.*; import java.util.*; /**
* SMTPConnection : Xử lý cỏc lệnh SMTP, lưu mail vào thư mục thớch hợp */
public class SMTPConnection extends Thread { protected SMTPServer server;
protected Socket socket; protected Vector recipients; public BufferedReader in; public PrintStream out; public String returnPath; protected Vector users;
protected boolean stopRequested;
public SMTPConnection(Socket socket, SMTPServer server) { this.socket = socket;
this.server = server; }
/**
* Đúng kết nối – hoàn tất quỏ trỡnh nhận mail từ trỡnh khỏch * */
public void close() { try {
socket.close(); } catch (Exception e) {
System.err.println("Exception trying to close SMTPConnection socket!"); e.printStackTrace(System.err); } } /** * Xử lý lệnh SMTP DATA */
public void processDATA() throws IOException { String line;
StringBuffer data = new StringBuffer(); line = in.readLine();
if (line.startsWith(".."))
line = line.substring(1);
// Đặt chuỗi nhận được vào StringBuffer System.out.println(line);
data.append(line + "\n"); line = in.readLine(); }
// Lưu thụng điệp
String messageId = Server.storage.saveMessage(users, data); // Thụng bỏo cho trỡnh khỏch thụng điệp đó được lưu
out.println("250 Message '" + messageId + "' accepted for delivery"); }
/**
* Xử lý lệnh SMTP EHELO – Lệnh này chỉ dành cho tập lệnh của SMTP mở rộng – nú tương ứng với lệnh bắt tay HELO
**/
public void processEHLOCommand(StringTokenizer arguments) { processHELOCommand(arguments);
} /**
* Xử lý lệnh SMTP HELO **/
public void processHELOCommand(StringTokenizer arguments) { if (!arguments.hasMoreTokens()) {
out.println("501 HELO requires domain address"); return;
}
out.println("250 " + Server.getAddress() + " Hello"); }
/**
* Xử lý lệnh SMTP MAIL **/
public boolean processMAIL() throws IOException { String line = "";
while (true) {
line = in.readLine(); System.out.println(line);
if (line.length() < 4) {
out.println("500 Command Unknown '" + line + "'"); continue;
}
StringTokenizer tokenizer = new StringTokenizer(line); String command = tokenizer.nextToken();
if (command.equalsIgnoreCase("HELO")) { processHELOCommand(tokenizer); continue; } if (command.equalsIgnoreCase("EHLO")) { processEHLOCommand(tokenizer); continue; } if (command.equalsIgnoreCase("VRFY")) { processVRFYCommand(tokenizer); continue; } if (command.equalsIgnoreCase("QUIT")) { processQUITCommand(tokenizer); return false; }
if(command.equalsIgnoreCase("RCPT")||command.equalsIgnoreCase("DATA" )) {
out.println("503 Bad sequence of commands ư specify MAIL first"); continue; } if (command.equalsIgnoreCase("MAIL")) { if (processMAILCommand(tokenizer)) { out.println("250 OK"); return true; } }
out.println("500 Command Unknown '" + line + "'"); }
}
public boolean processMAILCommand(StringTokenizer arguments) { if (!arguments.hasMoreTokens()) {
out.println("503 Syntax: MAIL FROM:<user>"); return false;
returnPath = arguments.nextToken();
System.out.println(returnPath+" "+returnPath.length()); if (returnPath.length() < 5) {
out.println("503 Syntax: MAIL FROM:<user>"); return false;
}
if (!returnPath.substring(0, 5).equalsIgnoreCase("FROM:")) { out.println("503 Syntax: MAIL FROM:<user>"); return false;
}
return true; }
protected void processQUITCommand(StringTokenizer arguments) { out.println("221 " + Server.getAddress() + " closing connection"); stopRequested = true;
}
public boolean processRCPT() throws IOException { users = new Vector(2, 2);
String line = ""; while (true) {
line = in.readLine(); System.out.println(line);
if (line.length() < 4) {
out.println("500 Command Unknown '" + line + "'"); continue;
}
StringTokenizer tokenizer = new StringTokenizer(line); String command = tokenizer.nextToken();
if (command.equalsIgnoreCase("EHLO")) { processEHLOCommand(tokenizer); continue; } if (command.equalsIgnoreCase("VRFY")) { processVRFYCommand(tokenizer); continue; } if (command.equalsIgnoreCase("RSET")) { out.println("250 Reset state");
return false; } if (command.equalsIgnoreCase("QUIT")) { processQUITCommand(tokenizer); return false; } if (command.equalsIgnoreCase("MAIL")) {
out.println("503 Sender already specified"); continue;
}
if (command.equalsIgnoreCase("DATA")) { if (users.size() == 0) {
out.println("503 Bad sequence of commands ư specify RCPT first"); continue;
}
out.println("354 Enter mail, ending with '.' on a line by itself"); return true;
}
if (command.equalsIgnoreCase("RCPT")) { processRCPTCommand(tokenizer);
continue; }
out.println("500 Command Unknown '" + line + "'"); }
}
public void processRCPTCommand(StringTokenizer arguments) { if (!arguments.hasMoreTokens()) {
out.println("501 Syntax: RCPT TO:<address>"); return;
}
String arg = arguments.nextToken();
if (!arg.substring(0, 3).equalsIgnoreCase("TO:")) {
out.println("501 Syntax: RCPT TO:<address>"); return;
}
// Địa chỉ nằm sau "TO:" System.out.println(arg);
String address; try{
address=arguments.nextToken(); //cú khoảng trắng } catch(Exception e){
address = arg.substring(3);//khụng cú khoảng trắng }
System.out.println("Receipt address :"+address);
// Thụng thường địa chỉ mail được gởi theo dạng <user@domainname>
if (address.substring(0, 1).equals("<") && address.substring(address.length() ư 1, address.length()).equals(">"))
address = address.substring(1, address.length() ư 1); // Lấy về thụng tin của user từ địa chỉ mail
User user = Server.storage.getUser(address); // Bỏo lỗi cho trỡnh khỏch nếu user khụng tồn tại
if (user == null) {
out.println("550 User " + address + " is not known"); return;
}
users.addElement(user); // Trả về mó lỗi thành cụng
out.println("250 Recipient " + address + " ok"); }
public void processVRFYCommand(StringTokenizer arguments) { out.println("252 VRFY command not implemented");
} /**
* Phương thức run() lặp liờn tục để ư lý lệnh cho đến khi hoàn tất */
public void run() { try {
in=new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintStream(socket.getOutputStream());
} catch (IOException e) { e.printStackTrace(System.err); close(); return; } stopRequested = false;
out.println("220mail.goemaat.comJAVASMTPServer (com.goemaat.email.SMTP) ready"); while (!stopRequested) { try { if (processMAIL()) { if (processRCPT()) processDATA(); } } catch (IOException e) { e.printStackTrace(System.err); stopRequested = true; } } // Đúng socket đó mở trước đú close();
// Loại bỏ kết nối trong danh sỏch SMTPServer server.removeConnection(this);
} }
Chương trỡnh Server.java dưới đõy được dựng để đọc file cấu hỡnh email.properties, khởi tạo SMTPServer. Server.java cũng làm nhiệm vụ khởi tạo trỡnh chủ POPServer. Trỡnh Server.java được cài đặt như sau:
Server.java
import java.io.*; import java.util.*; public class Server {
public static Properties properties; public static SMTPServer smtp; public static EmailStorage storage; public static POPServer pop;
public static String getAddress() {
String address = properties.getProperty("server.address"); if (address == null)
address = "UNKNOWN[server.address]"; return address;
}
protected static boolean getProperties() { try {
String fileName = "email.properties";
// Kiểm tra sự tồn tại của file cấu hỡnh email.properties File file = new File(fileName);
if (!file.exists()) {
fileName = file.getAbsolutePath();
System.out.println("Specified properties file '" + file.getAbsolutePath() + "' does not exist!");
return false; }
// Đọc cỏc thụng tin cấu hỡnh properties = new Properties();
FileInputStream in = new FileInputStream(file); properties.load(in); in.close(); } catch (Exception e) { System.out.println(e); return false; } System.getProperties().put("line.separator", "\r\n");
return true; }
/**
* Chương trỡnh chớnh */
public static void main(String[] args) {
if (!getProperties()) {
System.err.println("Could not get properties!"); return;
}
System.out.println(Server.properties.getProperty("smtp.port")); // Thiết lập nơi lưu trữ mail
if (!setupStorage()) {
System.out.println("Could not setup storage!"); return;
}
// Khởi tạo SMTP server if (!startSMTPServer()) {
return; }
// Khởi tạo POP server if (!startPOPServer()) {
System.err.println("Could not start POP server!"); return;
} }
/**
*Phương thức khởi tạo nơi lưu trữ mail */
public static boolean setupStorage() {
String className = properties.getProperty("storage.class"); if (className == null || className.length() == 0) {
System.err.println("No storage class specified in property 'storage.class'!");
return false; }
try {
c = Class.forName(className); } catch (ClassNotFoundException e) {
System.err.println("Class '" + className + "' not found! (check CLASSPATH)");
e.printStackTrace(System.err); return false;
}
// Tạo thể hiện của lớp try { storage = (EmailStorage)c.newInstance(); } catch (InstantiationException e) { e.printStackTrace(System.err); return false; } catch (IllegalAccessException e) { e.printStackTrace(System.err); return false; } return storage.init(); }
/**
* Khởi tạo POP server. */
protected static boolean startPOPServer() { try {
pop = new POPServer(); pop.setDaemon(false); pop.start(); } catch (Exception e) { e.printStackTrace(System.err); return false; } return true; } /**
* Khởi tạo SMTP server. */
protected static boolean startSMTPServer() { try {
smtp = new SMTPServer(); smtp.setDaemon(false); smtp.start(); } catch (Exception e) { e.printStackTrace(System.err); return false; } return true; } }
2.2.2.2. Xõy dựng POP3 Server
Trỡnh POP3 Server gồm hai phần, phần POPServer chịu trỏch nhiệm mở socket và lắng nghe kết nối từ trỡnh khỏch gởi lờn theo cổng 110 (cổng mặc định của giao thức POP3). Khi nhận được kết nối, POPServer yờu cầu lớp PễPCnnection phõn tớch cỏc lệnh của giao thức POP3 và gởi mail về cho trỡnh khỏch. Lớp POP3 được cài đặt như sau:
POPServer.java
import java.io.*; import java.net.*;
import java.util.*;
public class POPServer extends Thread { public Vector connections;
public Boolean stopRequested; protected ServerSocket listenSocket; public POPServer() throws Exception {
int port = 0;
String portString = null;
// Lấy sú hiệu cổng từ file cấu hỡnh try {
portString = Server.properties.getProperty("pop.port"); port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
throw new Exception("Invalid 'pop.port' ư " + portString); }
// Lắng nghe trờn socket sự kết nối từ trỡnh khỏch listenSocket = new ServerSocket(port); // Tạo mảng chứa danh sỏch cỏc kết nối connections = new Vector(10, 10);
}
public void removeConnection(POPConnection connection) { }
/**
* Xử lý kết nối