Chơng IV. Xây dựng ứng dụng truyền tệp
.I.3.2. Địa chỉ Socket
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
};
Trờng address family sẽ quyết định cấu trúc địa chỉ của trờng Address bytes phía sau vì socket hỗ trợ cho nhiều giao thức khác nhau nên ứng với mỗi giao thức trờng địa chỉ sẽ có cấu trúc khác nhau.
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
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) 65
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)
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.
SOCK_STREA
M Có TCP Có
SOCK_DGRAM Có UDP Có
SOCK_RAW IP Có
Do đó tham số thứ ba xác định chính xác giao thức nào đợc sử dụng cho socket. Tuy vậy trong rất nhiều ứng dụng tham số này không đợc sử dụng.
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ó cha đợ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() nhng 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ẽ cha thoát ra nếu nh cha thiết lập đợc liên kết hoặc cha 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 nhng 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ũ cha đợc xử lý xong. Để tránh việc các yêu
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.
Lời gọi accept()
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)
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 nhng 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...
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ác lời gọi sendto(), sendmsg()
Đợ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) 71
◊ int read(int fd, char * buff, int len)
◊ 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