CHƯƠNG IV XÂY DỰNG ỨNG DỤNG TRUYỀN TỆP

Một phần của tài liệu KIẾN THỨC MẠNG CƠ BẢN (Trang 56 - 78)

I.

GIAO DIỆN LẬP TRÌNH

I.1.

Giao diện lập trình

Bộ giao thức TCP/IP đã định nghĩa đầy đủ các giao thức truyền thông để các máy tính trên mạng có thể giao tiếp với nhau, che đi cấu trúc vật lý của mạng. Tuy nhiên, để xây dựng các ứng dụng thì sử dụng TCP/IP là không đủ, bởi vì người lập trình sẽ phải tự mình xây dựng những thủ tục giải quyết từng sự kiện, một giao diện (Application Program Interface) lập trình là cần thiết để cung cấp cho người lập trình một cách tiếp cận đơn giản, dễ hiểu hơn.

UNIX cung cấp cho người lập trình hai giao diện chính cho lập trình C:

 Sockets lần đầu tiên xuất hiện trong bản BSD UNIX năm 1982 và nhanh chóng phát triển rộng rãi trong một khoảng thời gian ngắn. Được phát triển thêm vào năm 1990 để hỗ trợ giao thức ISO. Socket được gắn với khả năng truyền thông dễ dàng giữa các tiến trình cả trên mạng và trên một máy tính. Mục tiêu của nó là che dấu càng nhiều càng tốt những gì xảy ra ở tầng truyền thông phía dưới.

Tiến trình ứng dụng

Thư viện

Giao diện với nhân hệ điều hành

Giao diện với phần cứng

 TLI (Transport Layer Interface) Phát triển đầu tiên cho UNIX System V 3.0 có những hàm và thủ tục tương đương với Socket nhưng mô hình phức tạp hơn để đạt được tính mềm dẻo hơn.

I.2.

Network I/O và file I/O

Hệ điều hành UNIX, có 6 lời gọi hệ thống vào ra file đó là:

open: Mở file.

creat: Tạo file.

close: Đóng file.

read: Đọc từ file.

write: Ghi file.

lseek: Chuyển con trỏ file.

Và UNIX coi mỗi thiết bị đều như một file, có thể được truy cập như một file. Tuy nhiên việc vào ra trên mạng với giao diện lập trình ứng dụng phức tạp hơn vì:

 Mô hình Client-Server là không đối xứng, ta phải xác định chương trình nào đóng vai trò Client, chương trình nào đóng vai trò Server.

 Một đối thoại trên mạng có thể là hướng kết nối hoặc không kết nối.

 Một tiến trình con có thể sử dụng file descriptor của chương trình cha truyền cho để làm việc với file mà không cần biết đến tên file thực sự. Các ứng dụng trên mạng cần biết được đầu kia của kết nối để chắc chắn rằng nó được phép đòi hỏi dịch vụ.

 Trong phần nói về giao thức TCP/IP chúng ta đã biết để thiết lập kết nối cần nhiều tham số hơn đối với file I/O, đó là giao thức (protocol), địa chỉ IP đích (Destination IP Address), địa chỉ IP nguồn (Source IP Address), số

Application Socket Library User Kernel Stream Head SHOCKMOD Transport Provider TPI

 Giao diện mạng phải hỗ trợ nhiều giao thức truyền thông ví dụ các chuẩn của tầng cao với tầng thấp hơn.

I.3.

Làm việc với Socket

Như đã giới thiệu ở trên, Socket được gắn với khả năng truyền thông dễ dàng giữa các tiến trình cả trên mạng và trên một máy tính. Và nó là che dấu càng nhiều càng tốt những gì xảy ra ở tầng truyền thông phía dưới. Chúng ta sẽ lựa chọn Socket để phát triển ứng dụng của mình trên mạng dựa trên mô hình Client-Server. (adsbygoogle = window.adsbygoogle || []).push({});

I.3.1.

Socket

Nói chung cơ chế socket tương tự với cơ chế truy nhập tệp trong UNIX. Khi cần thiết tiến trình có thể tạo ra một socket, hệ thống trả lại kết quả là một socket descriptor. Sự khác biệt giữa file descriptor và socket descriptor ở chỗ file descriptor được gắn liền với một tệp hay thiết bị cố định còn socket descriptor không bị gắn với một địa chỉ nào. Chương trình có thể cung cấp địa

chỉ tại mỗi lần truy nhập tới socket hay cũng có thể gắn socket descriptor với một địa chỉ nhất định. Do nó tương thích với cơ chế truy nhập tệp, nên vẫn có thể dùng các lệnh thông dụng như read, write đối với socket.

Một Socket được dùng như một endpoint. Mỗi Socket gắn với một nhóm

 SHOCK_STREAM sử dụng cho dịch vụ hướng kết nối .

 SHOCK_DGRAM sử dụng cho dịch vụ không kết nối.

 SHOCK_RAW cung cấp truy cập trực tiếp vào giao thức để xây dựng công cụ quản trị ở mức user.

 SHOCK_SEQPACKET Giống như SHOCK_STREAM nhưng cung cấp biên giới các mesage.

 SHOCK_RDM Cung cấp dịch vụ không kết nối đơn giản. I.3.2.

Địa chỉ Socket

Có hai loại địa chỉ được gán với một socket: Địa chỉ local và địa chỉ TCP/IP. Địa chỉ này được khai báo là những bản ghi và được dùng làm tham số cho những lệnh hệ thống và thủ tục truy nhập socket. Địa chỉ local là địa chỉ để xác định socket trong nội bộ máy.

Cấu trúc địa chỉ cục bộ được mô tả như sau

Address family Address bytes 0-1 Address bytes 2-5

Address bytes 6-9 Address bytes 10-13 struct sock_addr

{

u_short sa_family; /* address family */

char sa_data[14]; /* up to 14 bytes address */ };

Protocol family Description

PF_UNSPEC Unspecified family

PF_UNIX UNIX domain

PF_INET Internet protocols

PF_IMPLINK Arpanet IMP address

PF_PUP Old Xerox protocols

PF_CHAOS M.I.T. CHAOS protocols

PF_NS Xerox Network system protocols

PF_NBS NBS protocols

PF_ECMA European Computer Manual

PF_DATAKIT DATAKIT protocols

PF_CCITT CCITT protocols (adsbygoogle = window.adsbygoogle || []).push({});

PF_SNA IBM System Network Architeture

PF_DECnet DECnet protocols

PF_DLI DEC direct data link interface

PF_LAT LAN terminal interface

PF_HYLINK NSC Hyperchannel

PF_APPLETALK Apple Talk protocols

PF_NIT Network interface tap

PF_802 IEEE 802.2

PF_OSI OSI protocols

PF_X25 CCITT X.25

PF_OSINET OSI protocols

PF_GOSIP U.S.government OSI protocols

Protocol Families

Địa chỉ TCP/IP được khai báo với tên sockaddr_in. Nó chứa số hiệu port và địa chỉ IP 32-bit.

Address family Protocol port IP Address

Unused (0) Unused (0) struct in_addr /* 32-bit IP Addresss */ {

u_long s_addr; /* network byte order */ };

struct sockaddr_in {

short sin_family; /* = AF_INET */

u_short sin_port; /* 16-bit port number */ struct in_addr sin_addr; /* 32-bit IP Addresss */

char sa_zero[14]; /*unused*/

};

I.3.3.

Một số lời gọi tạo lập socket

Lời gọi socket():

Muốn làm việc với socket thì việc đầu tiên là phải tạo socket đó. Để làm việc đó cần gọi lệnh socket() với các tham số cần thiết. Hàm trả lại một socket descriptor nếu thành công, -1 nếu có lỗi.

int socket (int family, int type, int protcol)

 Tham số family xác định họ giao thức được sử dụng với socket như đã nói đến ở trện.

AF_UNIX Giao thức nội bộ UNIX

AF_INER Giao thức Internet(TCP/IP) (adsbygoogle = window.adsbygoogle || []).push({});

AF_NS Giao thức Xeror NS

AF_IMPLINK Giao thức lớp IMP

 Tham số type xác định kiểu của liên kết. Những kiểu có thể sử dụng là dạng stream có độ tin cậy cao (SOCK_STREAM), dạng datagram (SOCK_DGRAM) hay dạng thô (SOCK_RAW) cho phép truy nhập tới mức thấp của mạng. Ví dụ: AF_UNIX AF_INE T AF_NS SOCK_STREAM Có TCP Có SOCK_DGRAM Có UDP Có SOCK_RAW IP Có

Lời gọi bind()

Khi mới được tạo ra, socket không được gắn với bất cứ địa chỉ nào. Nói riêng với TCP/IP là nó chưa được gán với số hiệu port của tiến trình gửi, số hiệu port của tiến trình nhận cũng như địa chỉ IP. Nhiều chương trình không quan tâm đến số hiệu port và địa chỉ của nó, và dành cho phần mềm giao thức tự gán. Tuy nhiên các chương trình khác vẫn cần biết đến địa chỉ của mình. Lệnh bind gán thiết lập một địa chỉ local cho socket và đăng ký nó với hệ thống. int bind(int sock, struct sockaddr * localaddr, int addrlen)

Lời gọi bind() được sử dụng trong các trường hợp sau:

 Chương trình server đăng ký một địa chỉ thông dụng với hệ thống. Mọi dữ liệu đến địa chỉ này sẽ được chuyển cho nó.

 Một chương trình client đăng ký một địa chỉ đặc biệt.

 Một chương trình client có kiểu không kết nối (connectionless) muốn kiểm tra xem địa chỉ của nó có hợp lệ hay không.

Lời gọi connect

Để thiết lập được một liên kết giữa client với server, tiến trình cần phải gọi lệnh connect.

int connect(int sock, struct sockaddr * seraddr, int addrlen)

Tham số lệnh này tương tự đối với lệnh bind() nhưng seraddr trỏ tới địa chỉ của server (tức là đầu kia của liên kết).

 Đối với các giao thức có liên kết (connection-oriented), connect() sẽ tạo lập một liên kết thực sự giữa hai máy. Các thông tin được trao đổi nhằm thống nhất mọi tham số liên quan. Nó sẽ chưa thoát ra nếu như chưa thiết lập được liên kết hoặc chưa nhận được thông báo lỗi.

 Đối với các giao thức không liên kết (connectionless) lệnh connect() đơn giản chỉ cất giữ địa chỉ server (biến seraddr) để tiến trình sau này sử dụng khi trao đổi dữ liệu. Với giao thức này việc gọi lệnh connect() có thể bỏ

qua nhưng việc sử dụng nó sẽ tạo ra sự thuận tiện đối với người lập trình. Sau khi đăng ký địa chỉ với hệ thống thì sẽ không cần phải xác định lại địa chỉ mỗi khi gửi đi các datagram. Người sử dụng có thể dùng các lệnh read, write, recv, send tương tự như các socket có kết nối.

Ngoài ra, connect còn kiểm tra xem có thực sự một tiến trình nào nhận thông tin mình sẽ gửi hay không. Nếu địa chỉ gửi không tồn tại connect sẽ trả lại lỗi cho tiến trình gọi. Việc này làm cho tiến trình có thể xử lý lỗi tốt hơn so với các giao thức không kết nối (ví dụ UDP). Một datagram có thể chứa một địa chỉ sai và hoàn toàn không có người nhận.

Lời gọi listen()

Ta sẽ xem xét một tiến trình server, tiến trình này đầu tiên tạo ra một socket, gắn nó với một port thông dụng rồi chờ cho đến khi có một yêu cầu được gửi tới. Nếu nó là một liên kết kết nối thực sự, hoặc nó phải xử lý quá lâu đối với mỗi yêu cầu, sẽ xảy ra trường hợp là một yêu cầu mới sẽ tới trong khi yêu cầu cũ chưa được xử lý xong. Để tránh việc các yêu cầu mới bị từ chối, server phải khai báo với phần mềm của giao thức tạo ra cho nó một hàng đợi các yêu cầu. Lệnh listen() sẽ khai báo hàng đợi này.

int listen(int sock, int qlen)

 Tham số sock là socket descriptor được dùng bởi server.

 Tham số qlen quyết định chiều dài hàng đợi các yêu cầu. Giá trị tối đa của tham số này là 5.

Khi hàng đợi này đã đầy, hệ thống sẽ không chấp nhận thêm một yêu cầu nào khác. Mọi yêu cầu tới sau đó sẽ bị huỷ bỏ. listen() chỉ áp dụng với socket kiểu STREAM.

Sau khi tiến trình server gọi các lệnh socket(), bind(), listen() để tạo ra một socket, gắn nó với một port thông dụng, xác định chiều dài hàng đợi, server sẽ gọi lệnh accept() để tạo ra một kết nối hoàn thiện.

int accept(int sock, struct sockaddr * addr, int addrlen)

Tham số addr dùng để trả lại địa chỉ của tiến trình gửi yêu cầu (client). accept() sẽ lấy yêu cầu đầu tiên trong hàng đợi và tạo ra một socket mới có cùng thuộc tính với sock. Nếu trong hàng đợi không có một yêu cầu nào thì nó sẽ chờ cho đến khi có một yêu cầu được gửi tới. Socket sock vẫn được giữ nguyên do vậy server vẫn tiếp tục chấp nhận các yêu cầu khác gửi tới socket này.

Với cách tiếp cận tương tác, server tự xử lý giải quyết yêu cầu sau đó đóng socket này lại và quay về xử lý tiếp các yêu cầu sau đó.

Còn đối với phương pháp đồng thời, sau khi lệnh accept() trả lại kết quả server sẽ sinh ra một tiến trình con để giải quyết yêu cầu. Tiến trình con sẽ nhận được một bản sao của socket mới. Trong khi đó tiến trình server sẽ lập tức đóng socket mới của nó sau khi tạo ra tiến trình con và trở lại gọi accept() để nhận một yêu cầu mới.

Lời gọi close()

Khi một tiến trình thôi không sử dụng socket thì nó gọi lệnh close() int close(int sock) (adsbygoogle = window.adsbygoogle || []).push({});

sock là socket cần huỷ bỏ. Khi một tiến trình kết thúc với mọi lý do, hệ thống sẽ đóng tất cả các socket còn mở. Đối với các giao thức tin cậy, mặc dù socket đã bị đóng nhưng kernel vẫn cố gắng gửi đi các dữ liệu còn lại trong hàng đợi dữ liệu.

I.3.4.

Một số lời gọi gửi dữ liệu qua socket

Có 5 lời gọi được dùng để gửi dữ liệu qua socket. Các lệnh write, writev, send làm việc với các socket đã kết nối vì chúng không cho phép khai báo địa chỉ người nhận.

Lời gọi send()

Lời gọi send có 4 tham số :

int send(int sock, char * buff, int len, int flag)

sock chỉ định socket được sử dụng,

buff trỏ tới vùng dữ liệu được gửi đi,

len xác định số byte trong buff được gửi đi.

flag cho phép ngưòi gửi chọn một số cách thức để gửi dữ liệu ví dụ như gửi dữ liệu out-of-band...

Các tham số này khác nhau đối với mỗi loại socket. send() trả lại mã lỗi cho chương trình gọi để chương trình biết được thao tác có thành công hay không.

Lời gọi write()

Do cách thức truy nhập tới socket tương tự truy nhập đến file nên có thể dùng lệnh hệ thống write để gửi dữ liệu qua socket.

int write(int fd, char * buff, int len)

Tham số fd có thể là một socket descriptor hoặc file descriptor.

Lời gọi writev()

Để gửi đi một danh sách các khối dữ liệu ta dùng int writev(int fd, struct iovec iovector[],int vectorlen)

Được dùng cho các socket không kết nối. Hai lệnh này yêu cầu phải cung cấp địa chỉ người nhận.

int sendto(int sock, char * buff, int len, int flag, struct sockaddr * destaddr, int addrlen)

 Bốn tham số đầu giống như bốn tham số của send().

 Hai tham số sau là địa chỉ của người nhận và độ dài của địa chỉ.

Do sendto() có quá nhiều tham số, lập trình viên có thể sử dụng lời gọi sendmsg()

int sendmsg(int sock, struct msghdr msg[], int flag)

Với tham số msg là một cấu trúc có chứa địa chỉ và danh sách nhiều đoạn dữ liệu cần gửi đi.

I.3.5.

Một số lời gọi nhận dữ liệu từ socket

Tương ứng với 5 lệnh để gửi dữ liệu có 5 lệnh dùng để nhận dữ liệu với tham số và cơ chế hoàn toàn giống nhau.

 int recv(int sock, char * buff, int len, int flag)

 int read(int fd, char * buff, int len) (adsbygoogle = window.adsbygoogle || []).push({});

 int readv(int fd, struct iovec iovector[],int * vectorlen)

 int recvfrom(int sock, char * buff, int len, int flag, struct sockaddr *

destaddr, int *addrlen)

 int recvmsg(int sock, struct msghdr msg[], int flag)

II.

MÔ HÌNH CLIENT-SERVER

Theo cách nhìn của người lập trình, TCP/IP giống như hầu hết các giao thức truyền thông khác cung cấp cơ chế cơ bản để truyền dữ liệu. Đặc biệt, TCP/IP cho phép người lập trình thiết lập truyền thông giữa hai chương trình ứng

dụng và truyền dữ liệu qua lại. TCP/IP cung cấp kết nối bình đẳng giữa các ứng dụng kể cả các ứng dụng chạy trên cùng một máy.

Một phát triển của mô hình peer-to-peer là mô hình Client-Server đã trở thành mô hình cơ bản được sử dụng trên mạng máy tính. Chương trình Server là một chương trình chờ đợi kết nối từ chương trình Client và cung cấp dịch vụ cho Client.

Mô hình được mô tả như sau:

 Tiến trình Server được khởi động trên một hệ thống nào đó sau đó chờ đợi một tiến trình Client kết nối đến yêu cầu dịch vụ.

 Tiến trình Client được khởi động trên cùng hệ thống máy với chương trình Server hoặc từ một hệ thống khác kết nối với hệ thống chủ thông qua mạng. Client gửi yêu cầu qua mạng tới tiến trình Server đòi hỏi một số dịch vụ như:

 Trả lại ngày giờ cho Client

 In một file cho Client

 Đọc hoặc ghi file vào hệ thống file của Server

 Cho phép Client login vào hệ thống Server

 Chạy chương trình của Client trên hệ thống Server

 Sau khi cung cấp dịch vụ cho Client, Server tiếp tục nghỉ và chờ đợi kết nối khác.

Chúng ta có thể chia tiến trình Server làm 2 kiểu:

 Tương tác (Interative Server): Khi Client đòi hỏi những dịch vụ đã đăng ký trước với khoảng thời gian thực hiện nhỏ, Server tự làm công việc đó ví dụ dịch vụ trả lại ngày giờ.

 Đồng thời (Concurrent Server): Với những yêu cầu đòi hỏi thời gian thực hiện lớn, tiến trình Server sinh một tiến trình con để thực hiện yêu cầu còn

recvfrom() recvfrom() bind() bind() socket() sendto()

Server(connectionless protocol)

block until data received from client

process request sendto() socket() Client data (reply) data (request)

Lời gọi socket cho mô hình client-server không kết nối

II.1.

Mô hình Client-Server sử dụng dịch vụ không kết nối

Với mô hình không kết nối, client không kết nối với server mà chỉ gửi dữ liệu đến sử dụng lời gọi sento(), ngược lại, server sử dụng lời gọi recvfrom() chờ đợi dữ liệu từ client chuyển đến. Lời gọi recvfrom() trả lại địa chỉ của client, nhờ đó server có thể gửi kết quả lại cho client.

(adsbygoogle = window.adsbygoogle || []).push({});

Một phần của tài liệu KIẾN THỨC MẠNG CƠ BẢN (Trang 56 - 78)