IV.2 KỸ THUẬT TRUYỀN NHẬN ÂM THANH TRÊN MẠNG IP

Một phần của tài liệu Tìm hiều môi trường lập trình SDK Windows và ứng dụng trên mạng (Trang 30 - 36)

Thiết bị và dữ liệu

IV.2 KỸ THUẬT TRUYỀN NHẬN ÂM THANH TRÊN MẠNG IP

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]

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 hoàn toà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ữ liệu có thể bị tắc

socket socket

Yêu cầu truyền

nghẽn, socket sẽ phải chờ. Đồng thời trong lúc này, nó lại nhận được tín hiệu thông báo có buffer kế tiếp cần truyền đi và tín hiệu thông báo có 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 toá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]

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 hoàn toàn liên kết xong thì hai máy mới coi như đã connect và tiến hành truyền nhận dữ liệu.

Socket truyền Socket truyền Socket nhận Socket nhận 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 hoà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 đầu. Nếu người sử dụng từ chối thì ứng dụng sẽ thông báo cho phía gọi lời từ chối và đóng liên kết lại. (adsbygoogle = window.adsbygoogle || []).push({});

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ư Windows98, WindowsNT lại thực thi theo cơ chế preemptive. Trong môi 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 hoà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 hoàn tất việc truyền dữ liệu, tức là dữ liệu đã được nhận hoàn toàn. Còn nếu socket được tạo ra ở cơ chế bất đồng bộ, hàm send() sẽ hoạt động ở chế độ non-blocking. Hàm send() sau khi gửi dữ liệu đi sẽ trả về ngay lập tức. Và hệ thống sẽ phải gọi một hàm khác như select() để quan sát tình trạng của việc gửi 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 đều do MFC hỗ trợ, phù hợp với cấu trúc chương trình được xây dựng dựa trên các lớp đối tượng MFC. Ứng dụng được xây dựng trên 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

Một phần của tài liệu Tìm hiều môi trường lập trình SDK Windows và ứng dụng trên mạng (Trang 30 - 36)