IV.2.1 MƠ HÌNH LIÊN KẾT VÀ TRAO ĐỔI DỮ LIỆU
Chương trình dùng giao thức TCP/IP làm giao thức giao tiếp. Việc thiết lập liên kết cũng như trao đổi dữ liệu đều tuân theo các cấp của giao thức này. Việc gọi và thiết lập liên kết được thực hiện theo mơ hình client/server, việc trao đổi dữ liệu được thực hiện thơng qua socket theo giao thức TCP.
Cĩ hai ý tưởng được đưa ra trong việc dùng socket để trao đổi dữ liệu.
Dùng 1 socket :
Mỗi máy dùng một socket để truyền nhận dữ liệu. Theo giao thức TCP sau khi hai socket connect được với nhau thì việc tiến hành trao đổi dữ liệu sẽ bắt đầu. Chúng ta sẽ dùng cặp socket này. Như vậy, một socket trên một máy đồng thời đảm nhận việc truyền dữ liệu đi cũng như nhận dữ liệu về.[3]
socket socket Yêu c u ầ truy n d ề ữ li uệ Yêu c u ầ nh n d ậ ữ li uệ socket
Hình IV.1 Mơ hình dùng 1 socket
Cách dùng này cĩ đặc điểm là việc tạo liên kết đơn giản, quá trình tạo liên kết hồn tồn giống như các bước trong việc tạo liên kết giữa các socket dùng giao thức TCP. Chương trình chạy và lắng nghe ở một port xác định. Khi cĩ một yêu cầu gọi liên kết đến, chương trình sẽ tạo ra một socket để nối kết với socket gọi. Sau khi thiết lập liên kết thì các socket bắt đầu gửi nhận dữ liệu. Socket sẽ gửi dữ liệu âm thanh đi đồng thời nhận dữ liệu truyền tới và chuyển cho hệ thống xử lý.
Socket làm việc theo cách này sẽ nhận hai thơng báo cùng một lúc. Khi cĩ dữ liệu từ mạng truyền tới, hệ thống sẽ thơng báo cho socket để tiến hành việc nhận dữ liệu. Cũng tương tự như vậy, khi cĩ dữ liệu âm thanh sẵn sàng, hệ thống cũng sẽ gọi socket để truyền đi.
Như vậy, khi thực thi socket sẽ nhận được hai thơng báo của hệ thống. Vì việc truyền nhận dữ liệu âm thanh là dạng dữ liệu liên tục cho nên tần suất mà hệ thống thơng báo cho socket là rất thường xuyên. Vì vậy, socket trong cùng một lúc cĩ thể nhận được cả hai yêu cầu truyền dữ liệu đi và nhận dữ liệu về. Thêm vào đĩ các hoạt động truyền nhận dữ liệu là các hoạt động bị tắc nghẽn. Do đĩ chúng ta phải lưu ý đến hiện tượng này, socket cĩ thể đáp ứng khơng kịp nhu cầu của hệ thống.
Chúng ta lấy một trường hợp ví dụ. Khi socket nhận được yêu cầu truyền dữ liệu đi, nĩ sẽ lấy dữ liệu từ các buffer và truyền đi. Do quá trình truyền dữ
dữ liệu trên mạng truyền về. Với các yêu cầu dồn dập như vậy, hệ thống cĩ thể sẽ đáp ứng khơng kịp và chương trình cĩ thể bị treo.
Vì vậy, khi dùng một socket để truyền nhận dữ liệu, chúng ta phải tính tốn cân đối thời gian giữa việc truyền dữ liệu đi và việc nhận dữ liệu về sao cho hợp lý để hệ thống cĩ thể làm việc liên tục được. Chúng ta cĩ thể qui định thời gian cho việc truyền nhận. Trong một thời điểm socket cĩ thể chỉ làm việc truyền dữ liệu đi, các yêu cầu nhận dữ liệu sẽ bị ngưng lại. Sau đĩ socket sẽ chỉ xử lý các yêu cầu nhận dữ liệu. Chiến lược này giúp giảm nhẹ hoạt động của socket. Tuy nhiên, chúng ta cần áp dụng cho cả hai socket liên kết. Trong một thời điểm, một socket sẽ truyền cịn socket cịn lại sẽ nhận dữ liệu, và thời điểm sau thì quá trình sẽ diễn ra theo chiều ngược lại.
Dùng 2 socket :
Xuất phát từ ý tưởng trên, chúng ta cĩ thể dùng hai socket trong việc trao đổi dữ liệu. Một liên kết hình thành giữa hai máy sẽ gồm hai cặp socket liên kết với nhau. Một socket chỉ đảm nhận việc truyền dữ liệu trong khi socket cịn lại đảm nhận việc nhận dữ liệu.[3] Socket truy nề Socket truy nề Socket nh nậ Socket nh nậ
Hình IV.2 Mơ hình dùng 2 socket
Vì mỗi socket chỉ nhận một tín hiệu nhất định. Socket truyền sẽ chỉ chú ý tới tín hiệu báo cĩ dữ liệu của hệ thống để tiến hành truyền dữ liệu đi. Trong khi đĩ, socket nhận sẽ chỉ lưu ý đến tín hiệu báo cĩ dữ liệu của hệ thống. Hai socket sẽ hoạt động độc lập với nhau và cơng việc của một socket sẽ nhẹ nhàng hơn mơ hình trên.
Tuy nhiên, trong mơ hình này, việc thiết lập liên kết giữa hai máy sẽ trở nên phức tạp hơn. Theo mơ hình client/server, khi một socket gọi và thiết lập liên kết với chương trình ở máy remote xong thì máy remote cũng phải tạo ra một socket và tiến hành liên kết ngược lại. Sau khi cặp socket hồn tồn liên kết
Yêu c u truy n ầ ề d li uữ ệ Yêu c u nh n ầ ậ d li uữ ệ Socket truy nề Socket nh nậ
Một khía cạnh khác cần lưu ý là tuy hai socket hoạt động độc lập với nhau nhưng chúng đều thuộc cùng một chương trình và chúng đều tiến hành việc gửi nhận dựa trên các giao thức lớp dưới chung. Do đĩ, trong một thời điểm chỉ cĩ một hoạt động diễn ra hoặc là truyền dữ liệu hoặc là nhận dữ liệu. Vì vậy thật ra hai socket cũng phải hoạt động phụ thuộc nhau. Socket gửi dữ liệu phải chờ socket nhận nhận xong dữ liệu rồi mới bắt đầu truyền đi và ngược lại việc truyền dữ liệu phải được hồn tất thì việc nhận dữ liệu mới cĩ thể tiến hành được.
Một vấn đề khác nẩy sinh do đặc điểm của dữ liệu. Dữ liệu tiếp nhận là dạng dữ liệu liên tục do đĩ, các tín hiệu mà hệ thống báo cho hai socket cũng xảy ra liên tục, vì vậy thực sự rằng tuy chỉ làm một cơng việc nhưng khối lượng cơng việc mà socket phải đảm nhận là rất lớn. Thêm vào đĩ, hai socket đều phụ thuộc vào một process do đĩ thật sự xét về mặt thực thi của quá trình thì khả năng giảm nhẹ cơng việc là khơng bao nhiêu. Và khả năng hệ thống bị treo do quá tải cũng vẫn cĩ thể xảy ra.
Chúng ta cĩ những cách giải quyết để giảm nhẹ việc thực thi của chương trình như dùng cơ chế xử lý song song (thread) hay dùng cơ chế phân chia thời gian cho các hoạt động như đã nĩi ở trên.
IV.2.2 CƠ CHẾ GỌI VÀ XÁC LẬP LIÊN KẾT
Khi liên kết được xác lập, chúng ta sẽ bắt đầu tiến hành trao đổi dữ liệu. Tuy nhiên trước hết chúng ta cần khảo sát phương pháp gọi cũng như thiết lập liên kết. Chương trình được hiện thực dựa trên cơ chế client/server cho nên việc tạo liên kết cũng dựa trên cơ chế này. Ý tưởng chính là: khi chương trình bắt đầu thực thi, nĩ cũng bắt đầu lắng nghe lời gọi liên kết ở một port xác định. Thực sự, trong chương trình chúng ta sẽ tạo ra một socket server và lắng nghe ở
một port qui ước trước. Khi một socket khác muốn tạo liên kết, nĩ sẽ tiến hành gọi liên kết với socket server ở giá trị port này.[3]
Trong giao thức TCP/IP, một quá trình giao tiếp thơng qua mơi trường mạng phải cĩ một chỉ số port xác định. Các quá trình khác nhau phải cĩ port khác nhau. Khi thiết kế mơ hình client/server, các nhà thiết kế đã tạo ra một số dịch vụ thơng dụng trên mạng như: finger, echo, mail, ftp . . . Các server của các dịch vụ này được dành sẵn các port xác định mà khơng một quá trình nào được phép sử dụng. Các port này được gọi là well-known port và do hệ thống cấp phát và quản lý. Thơng thường, các chỉ số well-known port cĩ giá trị từ 0 đến 1023. Các ứng dụng khơng được phép sử dụng giá trị port trong khoảng này. Ứng dụng cĩ thể dùng các giá trị port từ 1024 trở đi. Ví dụ: khi chúng ta cần tạo một socket mà khơng cần quan tâm đến giá trị port, chúng ta cĩ thể nhờ hệ thống cấp cho một giá trị port cịn trống. Thơng thường các giá trị port mà hệ thơng cung cấp cho ứng dụng khi cĩ yêu cầu nằm trong khoảng từ 1024 đến 5000. Cịn khi chúng ta muốn chỉ định một giá trị port cho socket, chúng ta sẽ cĩ thể chọn giá trị từ 5000 trở đi. Vì trong vùng này xác suất mà port đĩ đã bị chiếm là rất hiếm.
Vì vậy, khi thiết kế chúng ta muốn tạo một port cố định thì nên chọn socket lắng nghe ở một port cĩ giá trị lớn hơn 5000. Giá trị được chọn là 7699 nhưng mơ hình của chúng ta là : trong một chương trình vừa cĩ đĩng vai trị là client vừa là server nên ta chọn port cĩ thể thay đổi được trong khoảng từ 1024 đến 5000.
Khi muốn tạo liên kết, chúng ta sẽ tạo một socket và tiến hành connect vào socket đang lắng nghe ở một địa chỉ và port lắng nghe. Khi socket listen nhận thấy cĩ yêu cầu liên kết, nĩ sẽ thơng báo cho người sử dụng biết. Nếu nguời sử dụng đồng ý thì nĩ sẽ tiến hành connect và việc trao đồi dữ liệu bắt
Chúng ta nĩi thêm về địa chỉ khi liên kết. Do chương trình hiện thực trên mơi trường mạng Windows là mơi trường mạng workgroup. Mỗi máy được xem như một host riêng lẻ. Nếu trên mạng khơng cĩ các server như server novell hay server NT thì chúng ta khơng thể biết được các thơng tin về một máy remote nếu chúng ta khơng tạo liên kết với máy đĩ. Vì vậy, trong cơ chế liên kết, chúng ta chọn việc định địa chỉ để liên kết. Một máy muốn thiết lập liên kết với một máy khác thì phải nhập thơng số là địa chỉ IP của máy đĩ.
IV.2.3 CƠ CHẾ TRUYỀN NHẬN DỮ LIỆU
Khi viết một ứng dụng trên mơi trường Windows, chúng ta phải lưu ý đến đặc điểm của mơi trường Windows là mơi trường cĩ kiến trúc message-driven. Windows được xem là một mơi trường cĩ kiến trúc message-driven hay event- driven vì khơng một chương trình nào trên windows cĩ thể thực thi nếu khơng cĩ một Thơng báo hay một sự kiện kích khởi nĩ. Trong mơi trường Windows luơn tồn tại một vịng lặp message loop. Vịng message loop này sẽ truy xuất các Thơng báo từ các hàng chờ của các chương trình và tùy theo loại Thơng báo hay sự kiện, nĩ cho phép window procedure tương ứng thực thi. Vì vậy trên mơi trường Windows cĩ thể tồn tại nhiều ứng dụng cùng một lúc mỗi ứng dụng cĩ một hàng chờ Thơng báo riêng. Khi cĩ một sự kiện xảy ra, hệ thống sẽ xác định xem sự kiện đĩ tuơng ứng với ứng dụng nào và chuyển Thơng báo đến hàng chờ của ứng dụng tương ứng đĩ. Tùy theo loại Thơng báo mà ứng dụng sẽ gọi chương trình tương ứng thực thi.
Mơi trường windows 16 bits là mơi trường nonpreemptive, cĩ nghĩa là khi một ứng dụng đang xử lý một Thơng báo thì khơng một ứng dụng nào cĩ thể thực thi được. Phải chờ cho đến khi procedure của ứng dụng tiến hành xong cơng việc và trả về thì lúc đĩ procedure tương ứng với Thơng báo tiếp theo trong hàng chờ mới được thực thi. Trong khi đĩ các mơi trường Win32 như
trường này, việc procedure nào được thực thi là do hệ thống quyết định. Thật ra, trong mơi trường Win32, hệ thống định thời cho các thread thực thi. Thread chính là đoạn mã thực thi của một chương trình nên các chương trình đều cĩ cơ hội thực thi.
Khi winsock được thiết kế lần đầu tiên, các mơ hình thiết kế được làm cho phù hợp với cơ chế “ message-driven” và “ nonpreemptive” của Windows 16bits. Một số hàm socket nguyên thủy khi thực thi cần một khoảng thời gian tương đối. Khi hàm thực thi rơi vào tình trạng này, nĩ được gọi là bị tắc nghẽn. Khi một hàm bị tắc nghẽn, nĩ sẽ ngăn trở việc thực thi của các hàm khác trong hệ thống. Trong hệ thống UNIX, mơi trường mà socket được thiết kế đầu tiên, các hàm blocking này khơng gây trở ngại cho hệ thống vì hệ thống sẽ chiếm giữ các quá trình bị blocking và cho phép các quá trình khác thực thi.
Trong khi đĩ, hệ thống Windows 16 bits khơng cĩ khả năng chiếm giữ các quá trình blocking. Dẫn đến việc hệ thống khơng tiếp tục thực thi được vì các quá trình khác khơng cĩ cơ hội thực thi. Hệ thống phải chờ cho đến khi quá trình blocking hồn tất cơng việc thì mới tiếp tục thực thi được. Khi thiết kế winsock, các nhà thiết kế đã tính đến khả năng này. Vì vậy họ cĩ một giải pháp là đưa một đoạn mã đặc biệt vào hàm blocking để cho phép các quá trình khác kiểm tra được hàng chờ Thơng báo của mình. Tuy nhiên đây khơng phải là một giải thuật hiệu quả.
Trong hệ thống socket của Berkeley các nhà nghiên cứu cũng đã lưu ý đến vấn đề này khi thiết kế, và họ đã thiết kế các hàm nonblocking bên cạnh các hàm blocking. Chúng ta xét một ví dụ là hàm send() của socket. Khi hoạt động ở chế độ blocking, hàm send() sẽ gửi dữ liệu đi, hàm sẽ bị tắc nghẽn và nĩ chỉ trả về khi hồn tất việc truyền dữ liệu, tức là dữ liệu đã được nhận hồn tồn. Cịn nếu socket được tạo ra ở cơ chế bất đồng bộ, hàm send() sẽ hoạt động ở chế
dữ liệu. Trên mơi trường Windows chúng ta cũng cĩ thể sử dụng các hàm non- blocking. Tuy nhiên các nhà thiết kế winsock cịn đưa ra các hàm bất đồng bộ. [3]
Các hàm bất đồng bộ được đưa ra dựa trên cơ chế hoạt động message- driven của mơi trường Windows. Chúng ta lấy ví dụ là các hàm gửi nhận dữ liệu. Việc gửi dữ liệu khơng nhất thiết phải diễn ra ngay lập tức, và việc nhận dữ liệu sẽ bắt buộc chương trình phải chờ trừ phi nĩ nhận được một hằng đặc biệt. Bằng cách tạo socket ở chế độ non-blocking để dùng các hàm non- blocking và kết hợp với hàm WSAAssyncSelect(), ứng dụng sẽ nhận được các message thơng báo sự kiện để báo cho chương trình biết khi nào chương trình cĩ thể gửi dữ liệu đi hoặc đã cĩ dữ liệu truyền đến cần đọc ra từ socket. Trong các khoảng thời gian cịn lại, khi khơng cĩ thơng báo các phần khác của hệ thống cĩ thể thực thi được.
Các hàm bất đồng bộ rất phù hợp cho các hoạt động diễn ra trên mơi trường Windows 16 bits là mơi trường nonpreemptive. Trong mơi trường Win32 như Windows NT hay Windows98 là mơi trường preemptive các hàm blocking vẫn cĩ thể sử dụng được. Tuy nhiên việc dùng các hàm bất đồng bộ trên mơi trường Win32 giúp chương trình đáp ứng tốt hơn cho việc tương tác với người sử dụng. Một hàm blocking sẽ ngăn trở hệ thống đáp ứng kịp thời cho các thao tác của người sử dụng. Điều này rất quan trọng trên một mơi trường giao diện như Windows. Vì vậy các hàm bất đồng bộ vẫn được sử dụng.
Vì mơi trường Windows98 cĩ hỗ trợ cơ chế lập trình song song thơng qua việc định thời thực thi cho các thread, do đĩ trong việc thiết kế, chúng ta chọn dùng cơ chế blocking và thực hiện việc lập trình socket bằng các đối tượng do MFC cung cấp là các lớp CAsyncSocket, CSocket, CSocketFile, CArchive. Việc chọn lập trình bằng cơng cụ này vì cĩ nhữnh đặc điểm sau:
các lớp đối tượng MFC bằng các cơng cụ AppWizard, ClassWizard. Việc viết ứng dụng sẽ dễ dàng và đơn giản hơn. Và khi ứng dụng cĩ hỗ trợ socket thơng qua các lớp đối tượng socket của MFC ở trên, việc lập trình sẽ trở nên tiên lợi hơn.
Việc lập trình socket trên các lớp đối tượng thường dễ dàng và đơn giản hơn so với việc lập trình bằng các hàm socket nguyên thủy được hỗ trợ bởi Windows SDK. Chúng ta lấy một ví dụ như sau: tạo một socket và lắng nghe ở một port xác định.[6]