Tải bản đầy đủ (.pdf) (16 trang)

Hoạt động của Radius Server và WebAdmin trên WAN đơn giản - Chương 2 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.29 MB, 16 trang )

10


Chương II
GIỚI THIỆU CÁC KỸ NĂNG BỔ TRỢ
TRONG VIỆC PHÁT TRIỂN MÃ NGUỒN
I-Giao diện Sockets (The Sockets Interface)
Sockets được giới thiệu lần đầu tiên vào năm 1981 như là một phần của hệ thống phân
phối phần mềm Berkeley 4.2 và được dùng như một giao diện cho một ứng dụng nền quan
trọng. Hiện giờ, sockets đã được đặt vào UNIX System phiên bản V như là một phần của sự
hợp nhất BSD/System V.
Socket thực ra là một khối xây dựng cơ bản (basic building block) cho việc giao tiếp
(communication). Socket là một đầu cuối (endpoint) của giao tiếp và có thể được gắn cho một
cái tên (bound). Mỗi socket đang sử dụng có một kiểu (type) và có một hay nhiều process liên
kết. Các sockets tồn tại trong các vùng giao tiếp (communication domains). Vùng giao tiếp là
sự trừu tượng hóa bao hàm cả một cấu trúc đòa chỉ hay họ đòa chỉ (addressing structure –
address family) và một tập các giao thức (protocols) thực hiện các kiểu sockets nằm trong
vùng đó hay còn gọi là họ giao thức (protocol family). Communication domains được đưa ra
nhằm chứa đựng các thuộc tính chung của các quá trình giao tiếp thông qua sockets. Một
thuộc tính như vậy là sơ đồ được dùng cho tên sockets. Chẳng hạn, trong UNIX domain,
sockets được đặt tên là tên đường dẫn trong UNIX. Ví dụ, một socket có thể có tên là
/dev/foo. Các sockets thông thường chỉ trao đổi dữ liệu với các sockets trong cùng một domain
(cũng có thể trao đổi giữa các sockets khác domains, nhưng phải cần có các quá trình dòch.).
Giao diện socket của hệ thống UNIX cung cấp nhiều vùng giao tiếp độc lập: ví dụ như UNIX
domain cho các giao tiếp trên cùng hệ thống (on-system communication); Internet domain
11

được dùng bởi các process giao tiếp sử dụng giao thức giao tiếp chẩn DARPA. Các công cụ
giao tiếp nền tảng được cung cấp bởi các domains đã có sự tác động rất lớn trong việc thực thi
các hệ thống bên trong (internal system) cũng như giao diện socket cung cấp cho user.
1-Kiểu Socket (Socket Types)


Sockets có các kiểu phản ảnh các thuộc tính giao tiếp cho phép đối với user. Các quá
trình được coi là chỉ giao tiếp giữa những sockets cùng kiều, mặc dù không có gì ngăn cản sự
giao tiếp giữa các sockets thuộc các kiểu khác nhau.
Có nhiều kiểu sockets hiện hành:
a-Stream socket:
Stream socket cung cấp cho luồng dữ liệu 2 chiều (bidirectional), đáng tin cậy
(reliable), có thứ tự (sequenced) và không lập lại (unduplicated). Luồng dữ liệu này không có
biên giới giữa các records. Một cặp stream sockets được nối với nhau cung cấp một giao diện
giống hệt giao diện của pipes.
b-Datagram socket:
Datagramsocket cung cấp một luồng dữ liệu 2 chiều nhưng không đảm bảo có thứ tự,
tin cậy và có thể lặp lại. Nghóa là, một process nhận message trên một datagram socket có thể
tìm thấy message đó trên hai lần và có thể có thứ tự khác với thứ tự mà nó đã được gởi đi.
Một đặc trưng quan trọng của datagram socket là biên giới giữa các records trong dữ liệu gởi
được bảo toàn. Datagram socket có mô hình rất gần gũi với các tiện ích trong các mạng
chuyển mạch gói hiện thời (packet switched networks) như Ethernet.
c-Raw socket:
Raw socket cung cấp sự truy xuất vào các giao thức giao tiếp nền có hổ trợ socket.
12

Những socket này thường là datagram oriented mặc dù tính chất chính xác của chúng phụ
thuộc vào giao diện được cung cấp bởi protocol. Raw socket không có ý đònh trang bò chung
cho các user mà chỉ dành cho các user muốn phát triển các giao thức giao tiếp mới hoặc muốn
truy xuất sâu thêm vào các tiện ích bí mật (esoteric facilities) của giao thức đã có.
2-Tạo socket:
Sử dụng lời gọi hệ thống socket() để tạo một socket.
S= socket( domain, type, protocol);
Lời gọi trên sẽ yêu cầu hệ thống tạo ra một socket trong một domain chỉ đònh và có kiểu chỉ
đònh. Nếu protocol vẫn không được xác đònh (giá trò 0) thì hệ thống sẽ chọn một protocol thích
hợp mà có thể chứa đựng domain đã cho và hổ trợ cho kiểu socket được yêu cầu. Một

descriptor (một số nguyên nhỏ) được dùng sau lời gọi hệ thống như là một số hiệu để thực
hiện các thao tác trên socket đã tạo ra.
Các domains được đònh nghóa như những hằng số trong file <sys/socket.h>. Ví dụ, UNIX
domain là AF_UNIX, Internet domain là AF_INET.
Các kiểu của socket cũng được đònh nghóa trong file <sys/socket.h>. Ví dụ, stream socket là
SOCK_STREAM, datagram socket là SOCK_DGRAM, raw socket là SOCK_RAW.
Để tạo một socket stream trong Internet domain ta có thể gọi:
S = socket (AF_INET, SOCK_STREAM, 0);
Socket call có thể thất bại do nhiều nguyên nhân. Chẳng hạn, sự cố thiếu bộ nhớ
(ENOBUFS), sự cố không hiểu protocol (EPROTONOSUPPORT), hoặc sự cố không có kiểu
socket yêu cầu (EPROTOTYPE).
3- Đặt tên cho socket
13

Một socket khi tạo ra không có tên. Các process không thể truy cập socket, và do đó
không có message nào được nhận trên nó, mỗi khi nó chưa được đặt tên. Các quá trình giao
tiếp được ràng buộc bởi một liên kết. Trong Internet domain, liên kết này là local, foreign
addresses và local, foreign ports. Trong UNIX domain liên kết này là local, foreign
pathnames.
Để đặt tên cho socket, ta dùng lời gọi hệ thống:
bind (s, name, namelen);
Ví dụ, Muốn đặt tên cho socket trong Internet address, ta có thể gọi:
#include <sys/types.h>
#include <netinet/in.h>

struct sockaddr_in sin;

bin (s, (struct sockaddr_in *) &sin, sizeof sin );

4-Thiết lập cầu nối (Connection Establishment)

Sự thiết lập cầu nối thường là không đối xứng, với một process là client và process kia
là server. Server, khi muốn cung cấp các dòch vụ nào đó, phải đặt tên cho socket với một đòa
chỉ đã biết tương ứng với dòch vụ và sau đó lắng nghe nột cách thụ động trên socket này. Còn
client yêu cầu các dòch vụ mà server cung cấp bằng cách thiết lập một cầu nối tới socket của
server bởi lời gọi hệ thống connect() . Ví dụ, trong Internet domain, lời gọi hệ thống có thể là:
14

Struct sockaddr_in server;

connect (s, (struct sockaddr *) &server, sizeof server);
Có thể cầu nối không thể được thiết lập do nhiều nguyên nhân. Chẳng hạn:
-ETIMEDOUT: Hết thời gian qui đònh cho việc cố gắng thiết lập cầu nối tới server (timeout)
khi host mà server chạy trên đó bò down hoặc sự truyền tải trên mạng bò trục trặc do các
nguyên nhân khác về phần cứng (card mạng bò hỏng…).
-ECONNREFUSE: Host từ chối dòch vụ được yêu cầu khi server process chưa chạy hoặc tên
dòch vụ được yêu cầu không đúng.
Để nhận được yêu cầu kết nối từ client, server phải thực hai bước sau khi đã đặt tên
cho sockket:
-Lắng nghe các yêu cầu kếu nối từ client gởi tới (listen ()).
-Khi có một yêu cầu kết nối gởi tới, có thể chấp nhận yêu cầu này (accept()).
Ví dụ, trong Internet domain, hai bước trên có thể là:
Struct sockaddr_in from;

listen(s, 5);
fromlen = sizeof from;
newsock = accept (s, (struct sockaddr *) &from, fromlen);
15

5-Truyền dữ liệu:
Với một cầu nối đã được thiết lập, việc truyền dữ liệu có thể bắt đầu. Có nhiều cách

gọi để thực hiện việc gởi (sending) và nhận (receiving) dữ liệu.
Thực hiện việc truyền dữ liệu bằng lời gọi read () và write ().
Read (s, buf, sizeof buf);
Write (s, buf, sizeof buf);
Thực hiện việc truyền dữ liệu bằng lời gọi send () và recv()
Send (s, buf, sizeof buf, flags);
Recv (s, buf, sizeof buf, flags);
Giá trò của flags là rất quan trọng (flags được khai báo như những hằng số trong file
<sys/socket.h>). Nó cho phép ta trực tiếp biến đổi nội dung dữ liệu trên socket sau lời gọi
send () hoặc recv ().
Ví dụ, Flags = MSG_PEEK cho phép ta chỉ dò trên socket xem đã có dữ liệu hay chưa,
nội dung dữ liệu đó là gì, mà không thay đổi nội dung dữ liệu sau lời gọi. Do đó, lời gọi recv()
hay read () có thể nhận lại dữ liệu trên socket sau khi dò. Điều này trang bò khả năng kiểm tra
dữ liệu trước khi nhận.
6-Đóng socket:
Sau khi thực hiện xong việc truyền dữ liệu và không cần dùng đến socket nữa, ta nên
đóng socket lại bằng lời gọi hệ thống:
Close (s);
16

Sau khi lời gọi được thực hiện, dữ liệu còn chưa xử lý trên socket sẽ chỉ được xử lý sau
một thời gian nhất đònh, sau thời gian này nếu dữ liệu vẫn chưa xử lý xong vì một lý do nào
đó thì nó sẽ bò mất. Nếu muốn tất cả dữ liệu trên socket sẽ được xử lý trước khi socket bò
đóng, ta nên thực hiện lời gọi shutdown().
Shutdown (s, how);
Giá trò how sẽ quyết đònh việc xử lý dữ liệu trước khi đóng socket s.
7-Socket kết khơng kết nối (connectionless sockets)
Các bước trên được giới thiệu cho các sockets hướng kết nối (connection-oriented
sockets). Còn đối với các datagram sockets dùng trong kỹ thuật truyền dữ liệu không kết nối
đặc trưng cho các mạng chuyển mạch gói, giao diện được cung cấp là đối xứng. Nghóa là các

processes có thể đóng hai vai trò client và server mà không cần thiết lập một cầu nối giữa
chúng. Thực ra, đòa chỉ nguồn và đích đã được chứa trong message truyền đi trên socket.
Các bước tạo và đặt tên cho socket là cần thiết cho việc truyền dữ liệu theo kiều
datagram. Thậm chí không cần đặt tên cho socket cũng được, lúc đó hệ thống tự động lấy tên
của host cục bộ cho socket. Các bước thiết lập cầu nối ở client và lắng nghe trên socket ở
server là không cần thiết nữa (Trừ khi mong muốn các gói dữ liệu truyền đi theo những đòa
chỉ nhất đònh và không thay đổi trong một khoảng thời gian dài).
Việc gởi và nhận các gói (packets) dữ liệu có thể được thực hiện bởi các lời gọi sau:
Sendto (s, buf, buflen, flags, (struct sockaddr *) &to, tolen);
Recvfrom (s, buf, buflen, flags, (struct sockaddr *)&from, &fromlen);
8-Input/Output Multiplexing
Ta có thể multiplex các yêu cầu I/O đối với các sockets (hoặc files) bởi lời gọi hệ
17

thống select().
Select() sẽ trả về 3 con trỏ tới 3 tập các socket có thể dọc, ghi dữ liệu và các
exceptional conditions chờ xử lý.
Ví dụ về cách sử dụng lời gọi select
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>

fd_set readmask, writemask, exceptmask;
struct timeval timeout;

select (nfds, & readmask, & writemask, & exceptmask, &ttimeou);
Ta có thể thêm vào hoặc gở bỏ một socket vào 3 tập trên bởi các macro sau:
FD_SET(fd, &mask), FD_CLR(fd, &mask), hoặc xóa rỗng một tập bởi FD_ZERO(&mask).
Tham số timeout sẽ xác đònh xem chế độ của sự chọn lựa: 0 – kiểu poll, #0 – Trả về
sau khoảng thời gian timeout, NULL – block.

Tham số nfd = MAX(descriptors) + 1, chính là khoảng giá trò cho các sockets.
Để kiểm tra xem một socket có thuộc tập các socket được chọn lựa không, ta có thể
dùng macro FD_ISSET(fd, &mask).
18

Có rất nhiều lời gọi cho các thao tác được cung cấp bởi giao diện giao tiếp qua socket
cần thiết trong quá trình phát triển các ứng dụng truyền dữ liệu giữa các processes trên mạng
có thể tham khảo chi tiết trong bộ sách AT&T UNIX System V Release 4, Programmers’s
Guide: Networking Interfaces của nhà xuất bản Pretice Hall.
II- Semaphores
Semaphore là một kỹ thuật đảm bảo cho tài nguyên dùng chung (shared resource) an
toàn về mặt dữ liệu đối với sự truy cập đồng thời của các process. Lấy một ví dụ, trên hệ
thống đường ray, một semaphore đảm bảo cho các đoàn tàu đang vận hành không đụng nhau
ở các vùng giao nhau. Trong máy tính cũng tương tự như vậy. Nếu một process không kiểm
tra semaphore trước khi truy cập tài nguyên dùng chung thì sự hỗn loạn (chaos) về dữ liệu sẽ
xảy ra.
Ta có thể lấy một ví dụ về sự nguy hiểm khi xảy ra sự hỗn loạn về dữ liệu khi truy cập
tài nguyên dùng chung mà không dùng kỹ thuật semaphore. Giả sử ta có một đoạn chương
trình rút tiền khỏi nhà băng theo tài khoản khai báo, với biến dùng chung cho các process là
deposit là số tiền ký quỹ hiện hành của tài khoản khai báo.
int deposit;
void withdraw(int account, double money)
{
if (checkInAccount(account))
{
deposit = getLastDeposit(account);
if (deposit >= money)
19

deposit -=money;

depositMoney(deposit);
}
}
Giả sử có 2 người cùng rút tiền tại 2 ATM khác nhau cùng một lúc, nghóa là
withdraw() được gọi cùng 1 lúc cho hai quá trình P1, P2.
P1: còn 100, rút 50 , withdraw (1111, 50), còn 50
P2: còn 300, rút 70, withdraw (2222, 70), còn 230
Giả sử P1 gọi trước 1 chút, các bước kiểm tra điều hợp lệ và các bước xảy ra như sau:
P1: …deposit = getLastDeposit (1111) = 100
100 > 50
Trao quyền cho P2
…deposit = getLastDeposit (2222) = 300
300 > 70
Trao quyền cho P1
deposit = 300 – 50 = 150
depositMoney(150)
Kết thúc P1 và Trao Quyền cho P2
20

deposit = 150 – 70 = 80
depositMoney(80)
Kết thúc P2
Như vậy, sau cùng giá trò còn lại của tài khoản 1111 lại là 150, và tài khoản 2222 là80.
Chỉ là tưởng tượng nhưng thật là nguy kiểm.

Mọi việc sẽ được giải quyết nếu như ta được tìm cách ngăn cản chỉ có một process
được phép truy cập tài nguyên chung mỗi lúc mà thôi. Semaphore sẽ giải quyết việc này như
sau:
sem = createSemaphore(); /* Tạo semaphore*/


enterCriticalSection(sem); /* Vào vùng tranh chấp , không cho phép các process khác
truy cập vào*/
deposit = getLastDeposit(account);
if (deposit >= money)
deposit -=money;
depositMoney(deposit);
leaveCriticalSection(sem); /* Ra khỏi vùng chanh chấp, cho phép các process truy cập vào*/
releaseSemaphore(sem); /* Giải phóng semaphore */
21

Khoảng thời gian chuyển tiếp giữa các lệnh vó mô điều có thể là điểm mà các process
có thể trao quyền cho nhau, và do đó nguy cơ dẫn đến sự hỗn loạn về dữ liệu của tài nguyên
dùng chung tồn tại trong các khoảng thời gian nhỏ này. Như vậy , để xây dựng semaphore, ta
không thể dùng các lệnh vó mô được mà phải dùng các lời gọi trang bò bởi kernel, ở đó chúng
được bảo vệ khỏi sự tác động của các process. System V có trang bò các lời goiï hệ thống thao
tác trên semaphore như sau.
-Tạo semaphore
int semget (key, nsems, flags) /* return semaphore-set-ID or –1 on error */
key_t key; /* get semaphore-set-ID*/
int nsems; /* semaphore-set key */
int flags; /* option flags */

-Thao tác trên semaphore
int semop (sid, ops, nops) /* return semaphore value prior to last operation or –1 on error*/
int sid; /* semaphore-set-ID*/
struct sembuf (*ops)[]; /* pointer to array of operations*/
int nops; /* number of operations */
-Hủy semaphore
int semctl (sid, snum, command, arg) /* return value depending on command or –1 on error*/
int sid;

22

int snum; /* semaphore number */
int cmd; /* command */
char *arg; /* argument */

Các hàm yêu cầu ở trên có thể được xây dựng như sau:
int createSemaphore()
{
int key =0;
/* Tạo semaphore nhò phân, do đó tham số thứ 2 là 1*/
return semget((key_t) key, 1, 0666 | IPC_CREAT );
}

void enterCriticalSection(int sem)
{
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = -1;
sb.sem_flg = 0;
23

semop(sem, &sb, 1);
}

void leaveCriticalSection(int sem)
{
struct sembuf sb;
sb.sem_num = 0;
sb.sem_op = +1;

sb.sem_flg = 0;
semop(sem, &sb, 1);
}

releaseSemaphore(int sem)
{
semctl ( sem, 0, IPC_RMID , 0 );
}

III-Shared Memory
24

Làm sao để một biến có thể dùng chung cho mọi process. Nghóa là mỗi sự tác động lên
giá trò của biến đó bởi một process đều thể hiện ở mọi process khác mà có tham khảo đến
biến này. Điều này không thể thực hiện được với việc khai báo biến thông thường cho dù có
khai báo toàn cục. Bởi vì mỗi process có vùng nhớ riêng của nó, các process không thể tham
khảo vùng nhớ của nhau được. Do đó mặc dù biến được khai báo toàn cục nhưng mỗi khi sinh
ra process con, các tính chất của biến này được sao chép vào vùng nhớ dành riêng cho process
con đó, mọi sự thay đổi giá trò trên biến này ở process cha và ở process con từ lúc đó không
còn tác động lên nhau nữa (Trừ trường hợp thao tác với đòa chỉ I/O, nhưng lúc đó hậu quả là
do bản chất của hệ thống).
Như vậy, để có được một vùng nhớ chứa biến khai báo sao cho mọi process có thể truy cập
tới, hệ thống phải cho phép cấp phát một vùng nhớ nằm ngoài tất cả các vùng nhớ của các
process (tất nhiên phải nằm trong vùng nhớ của process đầu tiên khi hệ thống khởi động.), và
mỗi lúc một process nào đó muốn truy cập vào vùng nhớ này chỉ việc ánh xạ nó vào không
gian đòa chỉ của process. đây, ta giới hạn với các process trên cùng một host.
Muốn sử dụng dữ liệu của vùng nhớ chung chính xác, ta phải dùng kỹ thuật semaphore.
Việc ứng dụng shared memory và semaphore vào truyền dữ liệu giữa các process trên cùng
một host sẽ tiết kiệm một số thời gian đáng kể so với việc dùng socket hoặc message. Các lời
gọi gởi và nhận dữ liệu sẽ được xây dựng lại dựa trên việc đọc ghi trên một vùng nhớ chỉ ra

trong shared memory và kỹ thuật semaphore đảm bảo sao cho receiver không đọc dữ liệu từ
shared memory quá sớm hoặc sender không ghi dữ liệu vào shared memory cho tới khi nào
receiver đã đọc hoàn tất.
System V có cung cấp một số lời gọi thao tác với shared memory như sau:
-Xin cấp phát vùng nhớ dùng chung kích thước nbytes bytes.
25

int segid = shmget (key_t key, int nbytes, int flags);
-ánh xạ shared memory vào không gian đòa chỉ mà process quản lý để truy cập.
char *shmat (int segid, char *addr, int flags);
-Bỏ ánh xạ
int shmdt (char *addr);
-Giải phóng shared memory
int shmctl (int segid, int cmd, struct shmid_ds *sbuf);
với cmd = IPC_RMID
Ta có thể gây ra các lỗi khó tìm khi dùng kèm shared memory với các lệnh cấp phát
vùng nhớ động như malloc(), new()… Shared memory được cấp phát với kích thước yêu cầu
xác đònh ngay ban đầu, cho nên rất hợp với dạng stack, array. Các lỗi còn có thể xảy ra khi
trong các cấu trúc ta lại dùng khai báo con trỏ. Thay vì dùng kiểu con trỏ ta nên dùng kiểu
tónh. Ví dụ, thay vì dùng char * string; ta nên dùng char srting[256];
Các gợi ý làm sao để sử dụng shared memory cho hiệu quả có thể tham khảo trong
cuốn Advanced Unix Programming của Marc J. Rochkind do Prentice-Hall xuất bản năm
1984.

×