Chapter 21. Project: Internet Radio

Broadcast, telephone and network technologies are converging rapidly, blurring the distinction between telephone and television. Software for video and telephone conferencing on the Internet is widely available, and most cable companies now offer high-speed Internet connections. Telephone companies have entered the entertainment business with video-on-demand and content services. The final resolution of these competing forces will probably be determined by politics and regulatory decisions as well as by technical merit. Whatever the outcome, more computers will handle voice, audio and video streams in addition to data. This chapter explores how well network communication protocols such as UDP, multicast and TCP support streaming media applications. The chapter outlines a project to send audio streams over a network under various conditions. The project explores timing, buffering and packet loss, as well as synchronization and a dynamic number of receivers.

Project Overview

Historically, Internet Talk Radio was an outgrowth of the rapid expansion of multimedia facilities on the Internet. Professionally produced audio broadcasts of interest to travelers on the Information Highway were encoded in Sun .au format and spooled to regional servers. Once a show was distributed to regional spool sites, users could listen to the show through a multicast program called radio.

The first Internet Talk Radio program was Geek of the Week, in which leading “network researchers, engineers, implementers, and a wide variety of other troublemakers” were interviewed in 1993 and 1994. Geek of the Week broadcasts have been archived and are available on the Internet for download [40]. A decade later over 3000 radio stations broadcast over the Internet. Some radio stations broadcast the same programming as they do over traditional airwaves; others broadcast solely on the Internet. Most of the Internet-only stations broadcast music, and the survival of these will depend on how royalties are assessed.

This chapter develops both point-to-point and multicast systems for distributing audio to multiple destinations based on the concept of streaming audio. In streaming audio (or video), the receiver plays the data as it receives the information, rather than waiting for the entire broadcast. Both audio and video data must be played at a fixed rate that is independent of network traffic. To compensate for the uneven flow through the network, streaming media receivers buffer a small amount of data, corresponding to a few seconds of a broadcast. Video streams require a much higher data rate than audio streams and generally require more CPU power for decompression. Video streams can also tolerate greater loss before the user perceives a degradation.

As an alternative to streaming, the receiver can save the entire broadcast and play it back later. A 30-minute audio program might contain several megabytes of data. A video program might require several gigabytes, even in a highly compressed format.

The main strategies for handling streaming data to multiple receivers are either to have an independent sending source for each receiver or to have one sending source with receivers that join a program in progress. Live Internet radio broadcasts sometimes use the latter strategy; audio archives use the first strategy.

This chapter compares implementations of streaming Internet audio broadcasts using UDP, multicast and TCP. We examine the need for buffering in the sender and receiver in addition to the buffering that occurs in the network and I/O subsystems.

This chapter assumes that the audio files and the audio device use 8K bytes per second of audio. If this is not the case for your system, you will need to modify various buffer and timing parameters. Section 21.2 shows how to do this project without an audio device. We evaluate the designs from this chapter, using the following tests to compare how the solutions behave.

Test Case 1: Start one receiver and then suspend the receiver process in the middle of the transmission by entering Ctrl-Z in the console window of the receiver. After a few seconds, resume the process by executing fg. Is any of the transmission lost?

Test Case 2: Start one receiver and direct the output to a file rather than to the audio device. Is the received file identical to the input file? Does it take less time for the transmission than it did when outputting to the audio device?

Test Case 3: Start two receivers and suspend one receiver in the middle of the transmission by entering Ctrl-Z in the console window of that receiver. Does the suspension affect the other receiver?

Test Case 4: Start two receivers and direct the output from one receiver to a file rather than to the audio device. Is the received file identical to the input file? Does this affect what the other receiver gets?

This chapter specifies several progressive variations of the sender and the receiver, which are summarized in Table 21.1. Most of the programs are created by modification of previous variations of the sender or receiver as specified in the “start from” column of the table.

Audio Device Simulation

If you do not have access to an audio device on your machine, you can send ordinary text to a simulated audio device. The simulated audio device consists of a named pipe that replaces the /dev/audio device and a program, slowreader, that reads from the pipe at a fixed rate. In this project replace all references to /dev/audio with your named pipe and run the slowreader program with input redirected to the pipe.

The slowreader program is a filter. Eight times a second it reads a 1000-byte block from standard input and writes it to standard output. Use a timer that generates a signal 8 times a second. To see what happens when no data is available, set standard input to nonblocking. Attempt to read 1000 bytes. Output the bytes read. If fewer than 1000 bytes were available, output a message reporting how many bytes were missing.

UDP Implementation with One Program and One Receiver

This section discusses an Internet Radio implementation using UDP. UDP is an unreliable protocol in which messages may be delivered out of order. Start by assuming that the protocol is reliable with in-order delivery (UDP approximately satisfies these assumptions on a LAN) and then modify the programs to take into account the behavior of UDP on the Internet.

Table 21.1. Summary of Internet Radio project variations.

program name

text section

start from

description

server_udp

20.3

 

basic UDP server

client_udp

20.3

 

basic UDP client

UDPSend

21.3.1

server_udp

simple sender of messages

UDPRecv

21.3.1

client_udp

simple receiver of messages

UDPSendEnd

21.3.2

UDPSend

sender transmits end marker

UDPRecvEnd

21.3.2

UDPRecv

receiver detects transmission end

UDPRecvSelect

21.3.3

UDPRecvEnd

buffer with read/write select

UDPRecvThread

21.3.3

UDPRecvEnd

buffer with read/write threads

UDPRecvShared

21.3.3

UDPRecvEnd

shared buffer with child

UDPSendSeq

21.3.4

UDPSendEnd

messages with sequence numbers

UDPSendSeqTest

21.3.4

UDPSendSeq

out-of-order sequence numbers

UDPRecvSeq

21.3.4

UDPRecvSelect

receive messages with sequence numbers

  

UDPRecvThread

 
  

UDPRecvShared

 

UDPSendProg

21.4.1

UDPSendSeq

send a program listing on request

UDPRecvProg

21.4.1

UDPRecvSeq

handle a program listing

UDPSendMult

21.4.2

UDPSendProg

send to multiple receivers

UDPSendBcast

21.5

UDPSendSeq

broadcast in progress

UDPRecvBcast

21.5

UDPRecvSeq

join broadcast in progress

UDPSendMcast

21.6

UDPSendBcast

multicast in progress

UDPRecvMcast

21.6

UDPRecvBcast

join multicast in progress

TCPSend

21.7.1

serverp.c

parent-server transmission

TCPRecv

21.7.1

client.c

simple transmission receiver

TCPSendProg

21.7.2

TCPSend

send a program listing

TCPRecvProg

21.7.2

TCPRecv

get a program listing

TCPSendBcast

21.7.3

TCPSend

send to multiple receivers

TCPRecvMime

21.8.1

TCPRecv

receive stream through a browser

Simple implementation

Copy Program 20.2, (client_udp.c) on page 699 into UDPRecv.c and compile it as UDPRecv. UDPRecv sends a message to a server and reads a response. Modify UDPRecv to receive messages of at most 1000 bytes after sending the initial message. The program should take an optional third command-line argument, the name of a file. If called with three command-line arguments, UDPRecv writes the received information to the specified file. Otherwise, UDPRecv writes the received information to /dev/audio.

Copy Program 20.1 (server_udp) on page 698 into UDPSend.c and compile it as UDPSend. Modify UDPSend to take two command-line arguments: a port number and a file name. The UDPSend listens on the specified port for client requests. When UDPSend receives a request (any message), it opens the file and copies the file contents to the requesting host in 1000-byte messages. Since the Internet Radio application sends 8000 byte/second audio, UDPSend should sleep for one second after sending each eight messages. After sending the entire file, UDPSend waits for another request. For each of these programs remember to change the value of BUFSIZE from 1024 to 1000.

Example 21.1. 

How would the message size, number of messages per block and sleep time between blocks change for a file containing CD-quality audio rather than voice-quality audio?

Answer:

CD-quality audio consists of two channels of 16-bit values played at a rate of 44.1 kHz, which translates to a throughput of 176,400 bytes/sec or 1.4112 Mbps. With a 1000-byte message size, the sender must write an average of 176.4 packets per second. Sending 176 packets per second can result in underflow for long transmissions, whereas sending 177 packets per second can result in receiver buffer overflow. A packet size of 1225 bytes evenly divides the data rate and still fits within a typical Ethernet packet. In this case, the sender should send 144 packets per second.

Example 21.2. 

How does UDPRecv open the output file?

Answer:

The UDPRecv function opens the file by calling open with three parameters: the pathname, flags and permissions. The flags are O_WRONLY, O_CREAT and O_TRUNC. Set the permissions appropriately.

Example 21.3. 

How can you use UDPSend to send the file myaudio.au to remote receivers?

Answer:

Decide on a port number to use, say, 16001, and start the program with the following command.

UDPSend 16001 myaudio.au

Example 21.4. 

Suppose the program from Exercise 21.3 is running on os1.cs.utsa.edu. How should you start UDPRecv to receive the transmission?

Answer:

UDPRecv os1.cs.utsa.edu 16001

Example 21.5. 

What happens if you omit the call to sleep from UDPSend?

Answer:

The program sends the file as fast as it can, limited by the speed of the network, the speed of disk access and the size of the network buffers. A client like UDPRecv does not buffer its input and the audio device can only handle eight messages a second. Once the buffers of the audio device are full, UDPRecv blocks while outputting to the audio device and may miss some of the transmission from the server.

Example 21.6. 

Suppose that as in Exercise 21.5 the input file contains a 30-minute radio program. How many bytes of buffer space does the receiver need to fully buffer the transmission?

Answer:

For 8000 byte per second audio, the receiver needs 30*60*8K = 14 megabytes.

Example 21.7. 

Modify UDPSend so that it sleeps for one second after every nine messages instead of eight. What does the output of UDPRecv sound like? What if it sleeps after every seven messages? Which is more annoying?

Answer:

If UDPSend sleeps after nine messages, it sends about nine messages a second. Since the receiver can only process eight messages a second, it misses about 1/8-second of sound every second over a long transmission, causing jumps in the audio. If UDPSend sleeps after only seven messages, the receiver sometimes blocks while waiting for input from the network and cannot keep the local audio buffer full. There would be pauses in the audio, sometimes in the middle of a word. The loss of 1/8 second of audio every second is annoying, but understandable for spoken audio. The brief pauses caused by sending the audio too slowly is sometimes more annoying. Of course, this judgment is subjective and you may judge differently.

Example 21.8. 

What drawbacks does sleeping for 1 second after every eight messages have in controlling flow?

Answer:

This sleep strategy does not take into account the overhead in transmitting the messages or the scheduling delays caused by other processes in the system. For example, if these delays average 100 ms for every eight messages, the server sends 1 second of audio every 1.1 seconds, causing slight pauses in the audio at the receiver. The POSIX description of sleep states that the suspension time may be longer than requested because of scheduling or other activity by the system.

Example 21.9. 

How could you fix the problem of an inaccurate data rate because of the inaccuracy of sleep?

Answer:

Use a timer that generates an interrupt every second. Each time the interrupt occurs, send the required messages. You should avoid sending the information from the interrupt service routine. Have the interrupt server routine set a flag, and use sigsuspend to wait for the interrupt, as in Example 8.26. Consider using absolute time as discussed in Section 9.6.

Example 21.10. 

How would the original implementation behave under Test Case 1 and Test Case 2?

Answer:

Under Test Case 1, the receiver loses some of the transmission if it is delayed long enough. The amount lost depends on the amount of time the process is suspended, the size of the receiver buffers and the size of the network subsystem buffers on the receiver host. Under Test Case 2, the output file at the receiver is usually identical to the input file for senders and receivers on the same uncongested local area network. It takes the same time to write the data to a file as to the audio device because the sender limits the rate at which it transmits the data.

Termination of the receiver

Connectionless communication protocols such as UDP do not indicate when the transmission is complete, so the receiver does not know when to terminate. There are several ways to handle the termination problem.

The sender can transmit a special message reporting that it has sent all of the data. The special message must be distinguishable from audio data. Since in most audio formats, any data is possible, the special message might be embedded in the audio data.

Alternatively, the receiver can use the timeout capability of UICI. If it receives no data in a certain length of time, say, 5 seconds, the receiver assumes that the transmission has ended and terminates. Receivers using this approach could terminate prematurely unless they use a very large timeout value.

Another method relies on the atomic nature of UDP messages—a UDP message is either received in its entirety or is not received at all. The receiver assumes that all messages except the last one are exactly 1000 bytes. If the size of the file is not a multiple of 1000 bytes, the last message has fewer than 1000 bytes and the receiver knows that this is the last message. If the file is a multiple of 1000 bytes, the sender transmits a message of length 0 after the last message.

Copy UDPSend.c into UDPSendEnd.c, and copy UDPRecv.c into UDPRecvEnd.c. Modify these programs to transmit a zero-length message to signify the end of the transmission.

Example 21.11. 

Propose another method of termination that uses the atomicity of UDP messages.

Answer:

The receiver uses a receive buffer of size 1001 instead of 1000. The sender transmits a message of size 1001 after the last message containing data. If the receiver reads a message of size 1001, it knows that the transmission has completed.

Example 21.12. 

Under what circumstances does the solution proposed in Exercise 21.11 cause the receiver to not terminate? How can you fix this?

Answer:

UDP is an unreliable protocol, so the receiver never terminates if the last message never arrives. You can handle this problem by using u_recvfrom_timed with a very long value of the timeout, say, 30 seconds.

Buffering by the receiver to handle network latency

One of the problems with streaming audio (or video) is that the transmission time may not be constant. Periods of heavy network traffic cause messages to be delayed or lost. For now we assume that the messages are received in order and never lost, but there may be short periods (equivalent to the time to play a few messages) in which no messages are received. The receiver can compensate for this uneven transmission by allocating buffers and filling many of the buffers before starting to play the first message. The receiver must fill the buffers from incoming network messages concurrently with the emptying and playing of the buffers.

Example 21.13. 

A naive approach to handling the buffers is for the receiver to alternate between reading from the network and writing to the audio device. What is wrong with this idea?

Answer:

Depending on the rate at which messages arrive, there may be times when a network message is available but the receiver blocks while waiting to write to the audio device. At other times, the audio device buffers may be empty and the receiver blocks while waiting for input from the network, even though there are process buffers containing data for the audio device.

Example 21.14. 

How would you implement a solution in which the receiver forks a child process that reads only from the network, filling the process buffers. The parent receiver process empties the process buffers, sending to the audio device.

Answer:

The process buffers must reside in shared memory, and the receiver parent and child must use interprocess synchronization mechanisms to access the shared memory.

The receiver buffer problem is a standard producer-consumer problem involving two file descriptors that must be monitored concurrently. We discuss three possible solutions to this problem—select, multiple threads and parent-child processes.

In the first solution, the receiver calls select to determine which file descriptor is ready. The receiver has one descriptor for reading and one for writing in contrast to Program 4.12, which monitors two file descriptors for reading.

Copy UDPRecvEnd.c into UDPRecvSelect.c and compile it as UDPRecvSelect. Modify the program to call select to monitor the two file descriptors. Preallocate NUMBUF buffers. Use a value of NUMBUF that corresponds to about 10 seconds of audio, and do not start sending anything to the audio device until at least half the process buffers are filled.

Example 21.15. 

What is a buffer overflow and what is a buffer underflow? How would you handle these conditions?

Answer:

A buffer overflow means that the network has available data but the receiver does not have a free buffer. Overflows occur when the sender produces data faster than the receiver uses it. A buffer underflow means that the audio device requires data but the receiver does not have any filled buffers. Underflows occur when the audio device uses data faster than the sender transmits it or when the network incurs heavy packet loss.

When select reports that data is available from the network but no buffer is free, the receiver should block on writing to the audio device until a buffer can be freed. If this does not happen soon enough, the network subsystem may drop messages because its buffers are full. Similarly, when select reports that a write to the audio device would not block but there is no data to write, the receiver should block while waiting for data from the network.

Since only a single process accesses the process buffers, the receiver does not have a critical section in the select implementation. However, the programming is still tricky, since the program can be in one of three states: only reading from the network (initially and when the buffers are empty), only writing to the audio device (when the buffers are full), and using select.

The threaded solution uses a producer thread responsible for reading from the network and a consumer thread responsible for outputting to the audio device. Each thread just blocks when its input or output is not ready. Use Program 16.14 and Program 16.11 as models for your solution. Take care that the producer thread does not obtain exclusive access to the buffer before blocking for network input. Similarly, the consumer thread should not hold exclusive access to the buffer before waiting for audio output to return from its previous write. Copy UDPRecvEnd.c into UDPRecvThread.c and compile it as UDPRecvThread. Modify UDPRecvThread so that it implements the threaded solution.

A third solution uses a child process to output to the audio device while the parent reads from the network. The process buffers can be implemented with shared memory, as described in Section 15.3. As in the threaded implementation, the critical sections that access the shared buffer must be protected. Copy UDPRecvEnd.c into UDPRecvShared.c and compile it as UDPRecvShared. Modify UDPRecvShared so that it implements the parent-child solution. The child process terminates when it has finished reading from the network. The parent terminates when the process buffers are empty and the child has terminated.

Example 21.16. 

How can the parent determine whether the child process has terminated?

Answer:

The parent checks to see if the child has terminated only when the process buffers are empty. A simple wait call blocks until the child finishes, leading to a deadlock when the buffers fill. Use waitpid with the NOHANG option, or catch the SIGCHLD signal and set a flag when the child terminates.

Example 21.17. 

What happens when the process that is sending to the audio device terminates while the audio device still has data in its buffer?

Answer:

The outcome depends on the system you are using. On some systems, if you exit while the audio buffer contains data, the audio stops. An explicit call to close on the audio device may block until the audio device buffers are empty.

Example 21.18. 

How would the three implementations of the buffered receiver behave under Test Case 1 and Test Case 2?

Answer:

All three implementations behave similarly under Test Case 1 and Test Case 2. Under Test Case 1, the receiver loses some of the transmission if it is delayed long enough; however, the amount lost would be decreased by the amount stored in the input buffer. Under Test Case 2, the output file should usually be identical to the input file if run on a local area network that was not too busy. Because the rate is determined by the sender, it takes about the same time to save the data to a file as to write it to the audio device.

Buffering by the receiver to handle out-of-order delivery

The UDP protocol does not force in-order delivery of packets. Out-of-order packets are seldom observed on a LAN in which there is only one path between sender and receiver, but UDP packets are often delivered out of order on the Internet.

The usual way to handle out-of-order transmission is with sequence numbers. Each message starts with a header containing a sequence number that is incremented by the sender for each message sent. For this part of the project, we assume that a 32-bit sequence number is sufficient.

Example 21.19. 

Suppose an 8000 byte per second audio stream uses 1000-byte messages. How long does it take for the audio stream to overflow a 32-bit sequence number?

Answer:

A 32-bit unsigned sequence number representation has 232 possible values. Since the audio stream sends one message every 1/8 second, it takes 229 seconds (approximately 17 years) to wrap around.

Example 21.20. 

Suppose a 2-gigabyte per hour stream of video uses 1000-byte messages. How long does it take for the video stream to overflow a 32-bit sequence number?

Answer:

Using unsigned 32-bit integers, the video stream takes about 2100 hours (about 3 months) to overflow its sequence number.

Example 21.21. 

How would you design a message to contain a sequence number and 1000 bytes of audio?

Answer:

Prepend a 4-byte header to the message body so that messages are now 1004 bytes. The header represents the message sequence number in network byte order.

Example 21.22. 

The code segment below reads 1000 bytes of audio from the open file descriptor filefd and sends it along with a 32-bit sequence number to a remote host as a single UDP message. Aside from the lack of error checking, what is wrong with this implementation?

#define BUFSIZE 1000
char buf[BUFSIZE+4];
uint32_t seq;

r_read(filefd, buf+4, BUFSIZE);
*(uint32_t *)buf = htonl(seq++);
u_sendtohost(sendfd, buf, BUFSIZE+4, hostn, port);

Answer:

Some systems force integers to be aligned on word boundaries, and the declaration of buf does not guarantee word alignment. You can fix the alignment problem by using memcpy rather than statement assignment, as illustrated by the following code.

uint32_t seqn;
seqn = htonl(seq++);
memcpy(buf, &seqn, 4);

Example 21.23. 

What happens if the sequence number is sent in one 4-byte message followed immediately by a 1000-byte message containing the audio data?

Answer:

This approach does not solve the out-of-order delivery problem. Even assuming the receiver reads a 4-byte message followed by a 1000-byte message, the sequence number in the first message might not correspond to the audio data in the second message.

Example 21.24. 

Design a buffer scheme for a receiver to store messages that might arrive out of order.

Answer:

A receiver with NUMBUF buffers places message number n into buffer slot n % NUMBUF. Each buffer slot has a flag, filled, that specifies whether the corresponding buffer slot contains unsent audio data. The receiver must prevent the following errors.

  • Insert an item into a filled buffer slot.

  • Remove an item from an empty or previously consumed buffer slot.

Example 21.25. 

How should you modify the synchronization of a threaded implementation to support the buffer scheme described in Exercise 21.24?

Answer:

A typical threaded implementation based on Program 16.11 blocks the producer if no buffer slots are available. Modify this code so that the producer blocks after reading an item from the network if the corresponding buffer is not available. The consumer no longer blocks when totalitems is 0, but instead blocks if the next buffer slot is empty.

Example 21.26. 

The solution described in Exercise 21.25 has a potential deadlock. How could this deadlock happen and how could it be avoided?

Answer:

Suppose there are eight buffers. Sequence numbers 0 and 1 have been processed by the producer and the consumer . The producer receives messages with sequence numbers 3, 4, 5, 6, 7, 8, 9, and 11, missing both 2 and 10. The consumer blocks while waiting for message number 2 from slot 2 to be filled. The producer blocks while waiting for slot 3 to be emptied so that it can insert sequence number 11. The consumer should time out and move on to the next slot if the current item is not available when it is time to send the next packet to the audio device. For the scenario described in this exercise, the consumer removes message number 3, allowing the writer to put message 11 in the buffer.

Copy UDPSendEnd.c into UDPSendSeq.c and compile it as UDPSendSeq. Modify UDPSendSeq to send 1004-byte messages with sequence numbers. Copy one of your implementations from Section 21.3.3 into UDPRecvSeq and compile it as UDPRecvSeq. Modify UDPRecvSeq to handle out-of-order delivery of messages. Test these together.

Example 21.27. 

How does the termination criterion change when sequence numbers are used?

Answer:

The receiver knows that the last message has been received if the message length is not 1004. A message of length 4 specifies the end, with no audio data in the message. The receiver terminates after reading this message once the buffer is empty. The receiver should also terminate under a long timeout condition when the buffers are empty.

Example 21.28. 

How does the synchronization in the threaded implementation of the receiver change when messages can be received out of order?

Answer:

The synchronization of the consumer is almost the same. The consumer now blocks when the filled flag of the next slot is clear rather than when nitems is 0. The producer does not know which slot is needed until it reads the message. One solution is to have the producer read a message into a local buffer, check the sequence number, and block if the corresponding slot is not available. Be sure to implement producer and consumer blocking in a loop that checks whether the blocking condition has changed.

Testing the out-of-order receiver on a LAN is difficult since programs rarely receive out-of-order UDP messages. To test receiver handling of out-of-order messages usually requires that the messages actually be sent out of order. Copy UDPSendSeq.c into UDPSendSeqTest.c and compile it as UDPSendSeqTest.

Modify UDPSendSeqTest to occasionally delay a message for 1, 2 or 3 messages. Use a separate buffer for a delayed message and an integer counter specifying how long to delay. You should also pick a threshold value between 0 and 1. If the threshold is 0, the sender transmits packets in order. For thresholds greater than 0, the sender transmits a greater fraction of the packets out of order. The sender sets the counter to 0 when it starts and checks the counter each time it is ready to send a message. A nonzero counter indicates that a delayed message exists. If the counter is greater than 1, the sender decrements it and sends the current message. If the counter is equal to 1, the sender decrements it and sends the current message followed by the delayed message in the buffer. If the counter is 0, the sender picks a pseudorandom number between 0 and 1. If the value is not below the threshold, the sender transmits the current message. If the value is less than the threshold, the sender places the message in the buffer and sets the counter to 1, 2 or 3 (at random).

UDP Implementation with Multiple Programs and Receivers

This section describes an implementation that allows both multiple programs and multiple receivers.

Multiple programs and one receiver

Copy UDPSendSeq.c into UDPSendProg.c and compile it as UDPSendProg. Modify UDPSendProg to interpret the filename command-line argument as a program listing of available audio files. Each line of the program listing has the name of an audio file and a description. When the sender receives a request message consisting of a 0 byte, the sender transmits the contents of the program listing file to the receiver as a single message. (Assume that the listing file is small enough to be sent as a single UDP message.) When the sender receives a message containing a single nonzero integer in network byte order, the sender begins to transmit the audio file identified by that integer. A value of 1 represents the first file in the program listing. Any value out of range causes the sender to ignore the request message and resume listening for another request.

Copy UDPRecvSeq.c into UDPRecvProg.c and compile it as UDPRecvProg. UDPRecvProg begins by sending a single 0 byte to the sender and reading the program listing. UDPRecvProg presents the listing to the user and prompts for the user’s selection. UDPRecvProg then sends the request number to the sender and plays the audio file as before.

Example 21.29. 

What happens if UDPRecvProg’s initial 0 byte is lost? How can you modify UDPRecvProg to deal with the possibility of such a loss? What other types of loss are possible?

Answer:

If the initial 0 byte is lost (or the program listing returned by the sender is lost), UDPRecvProg hangs while waiting for the sender’s reply. You can modify UDPRecvProg to time out and retry the initial byte a specified number of times before giving up. Similarly, the request number may be lost. Again, UDPRecvProg should time out and retry a specified number of times. UDPRecvProg should ignore loss of individual audio packets. However, if UDPRecvProg detects that the audio packet loss rate is too high, it should probably inform the user of a problem.

Multiple programs and multiple receivers

Copy UDPSendProg.c into UDPSendMult.c and compile it as UDPSendMult. Modify the program to work with multiple copies of the receiver. This modification is similar to changing a serial server into a parallel server.

Example 21.30. 

How does UDPSendMult behave under Test Case 3 and Test Case 4?

Answer:

In this implementation the receivers are independent and receive independently generated data streams. One receiver does not affect another.

UDP Implementation of Radio Broadcasts

The simplest strategy for handling multiple receivers of the same audio program is to treat them as completely independent, as described in Section 21.4.2. An alternative strategy, used by some radio stations on the Internet, is to multicast the program in a single stream. Listeners “tune in” at any time and receive the program as it is being broadcast on the air. A third strategy, used by video-on-demand (VOD) providers, broadcasts multiple copies of the same stream (a movie). Each copy starts a few minutes later than the previous one. Customers tune in to the stream that starts next so that they don’t miss anything.

Copy UDPSendSeq.c into UDPSendBcast.c and compile it as UDPSendBcast. Modify UDPSendBcast to begin “sending” the file when it starts up. At first the sender has no receivers, so it just reads from the file and sleeps after reading each eight blocks. If the sender has receivers, it sends each message to every receiver. As receiver requests come in, the sender adds these receivers to its list. When a receiver requests the broadcast, the sender responds with a message containing a description of the audio broadcast (in this case, just the name of the file) and the elapsed time (in minutes and seconds) since program transmission started.

Logically, the sender consists of two distinct operations. One operation accepts new requests, and the other transmits the audio program. A possible implementation of both operations with a single process (or thread) generates a signal once per second. The signal handler sends eight messages to all of the receiving hosts, and the main program handles new receivers. The main program and the signal handler share the list of receiving hosts. A correct implementation with signals is only possible if the socket calls and name resolution calls that UICI uses are async-signal-safe or if the main program blocks signals at appropriate points.

Example 21.31. 

Describe an appropriate data structure for the list of receivers.

Answer:

The data type of a receiver could be a u_buf_t structure that holds all the information needed to describe a receiver of a UDP message. (See Section 20.2 for a description.) If the sender sets a maximum number of receivers, it can use an array. Otherwise, the sender can use a linked list of u_buf_t items.

An implementation that does not require the async-signal safety of the UICI calls and that does not use threads has a parent process receiving connection requests and a child process sending the audio stream to a list of remote hosts. The parent process could send u_buf_t messages through a pipe to its child to keep it informed about receivers. The child can set the pipe for nonblocking reads and could attempt to read new receivers from the pipe each time it is awakened by the periodic signals for transmitting messages. The algorithm is as follows.

  1. While the pipe is not empty, do a nonblocking read of a u_buf_t item and update the list of receivers.

  2. Read eight blocks from the audio file and send them to all receivers.

  3. Suspend until the next signal.

Example 21.32. 

How can the parent process determine how far along the child’s transmission is so that it can send the information to the requesting receiver?

Answer:

The sender can record the time it starts and calculate the difference between the current time and the start time of the broadcast.

Copy UDPRecvSeq into UDPRecvBcast.c and compile it as UDPRecvBcast. Modify UDPRecvBcast to receive audio from UDPSendBcast. The UDPRecvBcast program displays the initial message from the sender (rather than sending the message to the audio device) and adjusts its state to start in the middle of a broadcast.

Example 21.33. 

Describe a strategy for initially partially filling the receive buffer before sending audio.

Answer:

Care must be taken so that the receiver does not wait for a message that has previously been sent. Since messages can be received out of order, the message after the one with the lowest sequence number may never arrive. Record the first sequence number that comes in and start filling the receive buffer according to the sequence numbers until a message comes in that would overflow the buffer. Then throw away the earliest half of the receive buffer. This should make room for the message just received.

Example 21.34. 

What happens if the sender’s first message giving the description of the broadcast is lost?

Answer:

The first message received contains binary audio data. The result of the receiver outputting this type of information to a terminal is unpredictable. The receiver should do a sanity check on the first message and display the message only if it consists of printing characters.

Example 21.35. 

How does the UDP implementation of the radio broadcast behave under the four basic test cases?

Answer:

UDPRecvBcast behaves similarly to the other implementations. The receiver loses data if it is suspended long enough, and the receivers are independent.

Multicast Implementation of Radio Broadcasts

Copy UDPSendBcast.c into UDPSendMcast.c and compile it as UDPSendMcast. Modify UDPSendMcast to take a multicast address as an additional command-line argument. The port argument is now the multicast port for sending. The sender does not need to know anything about the receivers and does not have any direct contact with them. The sender’s only responsibility is to send.

Copy UDPRecvBcast.c into UDPRecvMcast.c and compile it as UDPRecvMcast. Modify UDPRecvMcast to receive audio from UDPSendMcast. The first command-line argument of UDPRecvMcast is a multicast address, and the second command-line argument is a multicast port. The UDPRecvMcast program now only receives messages and does not send anything over the network.

Example 21.36. 

How would you incorporate into the receiver the ability to display a message indicating how far along the audio transmission is when it joins?

Answer:

The receiver can estimate the time from first sequence number of the first audio packet that it receives, given that eight sequence numbers corresponds to one second of audio.

Example 21.37. 

How does UDPRecvMcast behave under the four basic test cases?

Answer:

UDPRecvMcast behaves as the other UDP implementations did. The receiver loses data if it is suspended long enough, and the receivers are independent.

TCP Implementation Differences

All the differences between UDP and TCP discussed in Section 20.8 factor into the implementation of Internet Radio. The main drawback of the UDP implementation is its unreliability. Messages can be lost or delivered out of order. While the problem of out-of-order receipt of messages can be solved simply by buffering at the receiver end, message loss is more difficult to handle with UDP. TCP handles this automatically.

The case of a single sender and a single receiver is simpler in TCP because TCP already ensures that information will be received in order. Sequence numbers are not needed. Since the receiver can send information to the audio device no faster than 8000 bytes/second on average, the receiver cannot read faster than this rate on average. Because TCP has flow control, the sender’s network subsystem automatically forces the sender to slow down if it tries to send too quickly. The sender, therefore, does not have to sleep to limit the rate at which it sends, as in Section 21.3.1. Also, because of the connection-oriented nature of TCP, the sender can close the connection when finished, and the receiver can detect this. The issues discussed in Sections 21.3.221.3.4 are all either irrelevant or are easily handled with TCP, though the receiver may still want to buffer the data to handle variation in network latency.

Multiple programs with a single receiver can be handled in a simple way, as in Section 21.4.2, with the server sending the list of programs to the receiver. However, because TCP provides byte streams rather than messages or datagrams, the receiver may not receive the entire list with a single read, even if the buffer is large enough and the sender sends the list with a single write. The information must contain a well-defined terminator, such as a blank line. The receiver must keep reading until it receives this terminator. Once the sender and receiver agree on an audio file to transmit, the implementation reduces to the single-program case.

With multiple receivers and multiple audio files, the transmissions can be considered independent and can be done by separate processes or threads.

Implementing the capacity to tune in while the transmission is in progress, as in Section 21.5, makes TCP more complicated to use, even with a single program and multiple receivers. With UDP, the sender just sends to all the receivers, one after another. This works because a problem with the network connection to a given receiver does not affect the sender’s ability to send to other receivers. With TCP, if a server is sending audio to more than one host, network congestion or a busy receiver can cause write to block, delaying transmission to subsequent receivers. To handle this, use select, multiple processes, or multiple threads. In any of these cases, different receivers might be receiving at a temporarily different rate, and so the audio data must be buffered at the sender. Sender buffering is different from the buffering done by a receiver to account for network latency or out-of-order receipt. The following sections discuss these issues in more detail.

TCP implementation of one program and one receiver

Copy serverp.c from Program 18.2 on page 623 to TCPSend.c and compile it as TCPSend. Modify TCPSend to take a second command-line argument, the name of an audio file. After the sender accepts a network connection, it forks a child that opens the audio file and transmits its contents to the remote host. Since the child transmits the file and the parent resumes waiting for another request, TCPSend can handle multiple receiver requests for the same file. Note that the original program transfers data from the network to standard output, whereas this program transfers information from a file to the network.

Copy client.c from Program 18.3 on page 624 into TCPRecv.c and compile it as TCPRecv. Modify TCPRecv to take an optional third command-line argument. When called with two command-line arguments, TCPRecv copies data from the network to the audio device. When called with three command-line arguments, TCPRecv copies data from the network to the file named by the third argument. Open the output file as in Exercise 21.2.

TCPSend and TCPRecv can be used together to transfer audio from the sender machine to the receiver machine.

Example 21.38. 

How does TCPRecv behave under Test Case 1 and Test Case 2?

Answer:

If the receiver is suspended, the sender eventually blocks. No data is lost. If the receiver writes the data to a file, the file should be identical to the input audio file. If the network and the disk drive are faster than the audio device (a likely occurrence), transmission to a file completes much more rapidly than transmission to an audio device.

Example 21.39. 

What happens if the parent of TCPSend opens the audio file before forking any children?

Answer:

In this case, all children share the same file descriptor and have the same offset into the file for reading. The children would transmit mutually disjoint pieces of the audio file rather than each transmitting the complete file.

TCP implementation of multiple programs with one receiver

Copy TCPSend.c into TCPSendProg.c and compile it as TCPSendProg. Modify TCPSendProg to send multiple audio files. Now, as in UDPSendProg, the file command-line argument specifies the name of a file containing the program listing. Each line of the program listing has the name of an audio file and a description of the file. When the sender accepts a connection from the receiver, it sends the program listing to the receiver, followed by an empty line. The sender waits for another message from the receiver containing the number of the audio file in network byte order. The value 1 represents the first file. Any value out of range causes the sender to close the connection.

Copy TCPRecv.c into TCPRecvProg.c and compile it as TCPRecevProg. Modify TCPRecvProg to be used with TCPSendProg. After reading the list of audio files from the sender, TCPRecvProg presents the information to the user as a numbered list and prompts the user to make a selection by entering a number. TCPRecvProg sends the user’s selection to the sender and plays the audio file as before. The sender terminates its initial message by an empty line. Do not assume that the receiver can receive the entire list with a single read.

Example 21.40. 

How can you test that TCPRecvProg correctly handles the initial message?

Answer:

Temporarily modify the sender so that it sends the initial message in two pieces with a sleep in between.

TCP implementation of radio broadcasts

With TCP and a single receiver per process, the sender can rely on TCP flow control to regulate the rate at which it sends data. A receiver that malfunctions and cannot read from the network does not delay the other receivers. Similarly, a receiver that just throws away data rather than writing to the audio device can still receive data at the rate of the network, which may be much faster than the audio devices of other receivers. In this case, too, the faulty receiver does not affect the other receivers because the sending to different receivers is independent.

When broadcasts can be joined in progress, only one process or thread is reading from the audio file and the data must be sent to all receivers. Different receivers may be able to handle the data at slightly different rates, at least over short time intervals. The sender can handle the uneven rates by using a shared buffer that contains blocks of the file. In a threaded implementation, the sender’s writer fills the buffer at the rate of the audio device and the various reader threads access the buffer to transmit audio. If a reader thread reads too quickly, it must wait for the buffer to be filled. If a reader thread reads too slowly, then buffer slots are overwritten before being read by that reader thread.

Copy TCPSend.c into TCPSendBcast.c and compile it as TCPSendBcast. Modify TCPSendBcast to use multiple threads. The main thread starts by creating a writer thread to handle the filling of the buffer. The main thread is responsible for accepting connections. For each connection, the main thread creates a reader thread that is responsible for sending the data from the buffer to a particular remote host. The writer fills the buffer at a rate corresponding to the audio device. Use a timer that generates a signal at a given rate compensated for timer drift (Section 9.6). The simplest implementation has all threads blocking the signal while the writer uses sigwait to wait for the particular signal. No signal handler is necessary for this implementation. If no buffer slots are available, the writer writes over the oldest buffer slot.

Reader threads do not remove items from the buffer since each reader should be able to read all of the data. Each reader thread attempts to send data as fast as possible, blocking only on the write to the network and after it has accessed all items currently in the buffer.

Example 21.41. 

What type of synchronization should TCPSendBcast use to protect its buffer?

Answer:

Since audio is time critical, writers should have priority. Each buffer slot should have reader/writer synchronization with strong writer preference.

Example 21.42. 

How can the individual readers keep track of which packets they have already accessed?

Answer:

It is not sufficient to just keep track of which slots have been accessed, since the writer writes new items over existing ones. Each buffer slot keeps the sequence number of the packet it currently holds. Each reader keeps track of the sequence number of the last packet it sent and blocks if the sequence number in the next buffer slot is not greater than this value. TCPSendBcast does not need to send the sequence numbers to remote receivers. TCP handles missing packets and out-of-order delivery on transmission, and the sender controls the rate of play.

Example 21.43. 

Which buffer entry should a new reader thread send when it starts?

Answer:

If the reader thread sends the item with the lowest sequence number, it may have some of the next buffers overwritten before it can access them. If the newly created reader thread starts eight items later, it is guaranteed that the writer will sleep for at least one second before overwriting any of the next buffers. Assuming a buffer size of at least 16, starting halfway through the buffers would be a reasonable choice.

Example 21.44. 

How should you modify TCPRecv to work with TCPSendBcast?

Answer:

TCPRecv works without modification. Since sequence numbers are not attached to the data, the receiver does not care that it is receiving from the middle of the broadcast.

Example 21.45. 

What is wrong with the following scheme for having a reader thread of the sender protect the buffers?

  1. Obtain a read lock for the slot buffer.

  2. Copy the data from the appropriate buffer slot to the network.

  3. Unlock the buffer slot.

Answer:

With TCP, writing to the network can block if the remote receiver is slow in processing the data. The reader’s lock would prevent the writer from accessing the shared buffer.

Example 21.46. 

A correct method for the reader thread to access the shared buffer is as follows.

  1. Obtain a read lock for the buffer slot.

  2. Copy the data from the appropriate buffer into a local memory.

  3. Unlock the buffer slot.

  4. Write the data from the local memory to the network.

This implementation ensures that the buffers will only be locked for a short time and that a remote receiver cannot affect access to the buffer by the writer thread.

Example 21.47. 

Suppose each buffer slot holds 1000 bytes and that it takes 10 ns to copy a byte from one memory location to another. Estimate the maximum time that a reader would have the buffer locked for a single transfer.

Answer:

The nominal answer is 10 microseconds plus the time for locking and unlocking. However, since the thread may lose the CPU during the transfer, the actual time may be longer.

Example 21.48. 

How does TCPRecv behave under the four test conditions of Section 21.1?

Answer:

The maximum rate of output is independent of whether the result goes to a file or to the audio device since the rate is controlled by the sender. Suspending a receiver may cause the reader thread for this receiver to skip packets, but the suspension should not affect the other receivers if the synchronization at the sender is correct.

Receiving Streaming Audio Through a Browser

This section discusses how to run the Internet Radio programs from a browser. Create a web page containing a list of links to the broadcasts that are available. When a user clicks on a link, the browser launches a receiver helper program to receive and play the audio program.

Using browser helper applications

You may have noticed that when you click on certain links, the corresponding file does not appear in your browser window, but rather the browser launches a separate program, called a helper application, to handle the data sent by the server. For example, if you have a Real Audio Player installed on your machine and have set your browser to use this application, clicking on a link for a file with extension ram causes the browser to store the corresponding file as a temporary file on the local machine. The browser then launches the Real Audio Player application, passing the temporary file name to the application as a command-line argument. The file contains the information the Real Audio Player needs to locate the audio program.

Browsers use one of two methods to identify the type of resource being sent and the application that should handle this resource. Some browsers use the file extension to determine the type of resource; others rely on a Content-Type header line in the server response. Browsers that use file extensions store the correspondence between resource types and filename extensions in a file, typically named mime.types. The word MIME is an acronym for Multipurpose Internet Mail Extensions and was originally intended for mail attachments. Applications now interpret mime types more generally to associate an application type with a file extension. Web server responses often include a header line that describes the type of resource being sent.

Example 21.49. 

For an ordinary text document in HTML format, a server might send the following header line.

Content-Type: text/html

For a file with the ram extension, the server might send the following.

Content-Type: audio/x-pn-realaudio

When the browser receives this header line, it checks to see if a helper application has been set up with type audio/x-pn-realaudio, and if so, it puts the resource sent by the web server in a temporary file and calls that application with the name of the temporary file as a command-line argument.

When classifying resources on the basis of file extensions, the browser looks for an entry in its mime.types file corresponding to the ram extension such as the following.

audio/x-pn-realaudio   ram rm

The preceding command specifies that both the ram and rm extensions should be associated with audio applications of type x-pn-realaudio.

Start with one of your receiver programs, say, TCPRecv.c from Section 21.7.1 and copy it into TCPRecvMime.c. Modify TCPRecvMime to take one command-line argument, the name of a file containing the host name and port number of the sender.

Example 21.50. 

Suppose TCPRecvMime uses the following to read the host name and port tokens from the file specified on its command line.

scanf("%s %d", hostname, &port);

What problems might occur, assuming that hostname is an array of char and that port is an integer?

Answer:

The TCPRecvMime program has no way of telling in advance how long the host name is. Although valid host names cannot be too long, anything can appear in the resource file referenced on a web page. A bad resource file could generate a buffer overflow with potentially serious security implications. One solution is to allocate a buffer of prespecified size, say, 80 bytes, for the host name and use the following line.

scanf("%79s %d", hostname, &port);

The numerical qualifier on %s prevents scanf from filling hostname with more than 79 characters and the string terminator.

Test TCPRecvMime with TCPSend by creating a file containing the host and port number. Setting TCPRecvMime to be launched through a browser requires the following three steps that are described in the subsections below.

  1. Set the web server to handle a new mime type and send the appropriate Content-Type line. (This step needs a system administrator and is necessary for browsers that use this line to determine the application type.)

  2. Set your browser to handle the new mime type by launching TCPRecvMime when it receives a resource of the appropriate type.

  3. Create a web page for testing.

Setting a new mime type in your web server

Setting up your web server to handle a new mime type requires that you have administrative access to the web server. If you do not have administrative access, ask your system administrator to do this step for you. Alternatively, you can use one of the mime types already set up for your browser. We discuss this option in Section 21.8.5.

Depending on your web server, you can set a new server mime type by modifying a file of mime types or by modifying the configuration file. For example, if your web server configuration directory has a file with a name similar to mime.types, add the following line to this file.

application/uspir      uspir

The preceding line allows the web server to associate an application type called application/uspir with the file extension .uspir. Alternatively, you might be able to just add the following line to the web server configuration file, possibly a file called httpd.conf.

AddType application/uspir      uspir

You must restart the web server after changing this file.

You can use the client2 program from Program 18.5 on page 629 to verify that your web server is set correctly for this mime type. Create a small file called test.uspir in a directory accessible to the web server. If the web server is running on host webhost and this file is in the directory mydir relative to the web root directory, start client2 with the following command.

client2 webhost 80

Type the following line terminated by an empty line.

GET /mydir/test.uspir HTTP/1.0

You should see the file after a few header lines. A correct response should have a header line similar to the following.

Content-Type: application/uspir

Setting your browser to handle a new mime type

The method for setting a new mime type for a browser depends on which browser you are using. For Netscape 6 or 7, go to Edit → Preferences → Navigator → Helper Applications. Click on New Type and fill in the information requested. The Description can be any phrase. The File extension should be uspir and the MIME type should be application/uspir. For the application, put the full pathname for your TCPRecvMime program.

Creating a web page

Create a file with extension .uspir containing the host name and port number of your TCPSend program. The values should specify a server that is distinct from the web server. Make the file accessible to your web server and create a web link to the file. Start TCPSend. When you click on the link, you should start hearing the audio program.

Using a predefined mime type

If you cannot add a new mime type to your web server, you can use one of the predefined types that your browser is not using or does not use often. Some suggested extensions to try are ez, hqx, cpt, oda, smi and mif. You can test these by creating a file with the appropriate extension in a place accessible to your web server and issuing the appropriate GET command from client2. You should get back a Content-Type line giving the corresponding application type.

Set your browser to call your TCPRecvMime program for this application type. Follow the procedure in Section 21.8.3. If the application type is already defined for your web browser, click EDIT and modify the values.

Additional Reading

Many radio and television stations now support streaming archives of their programming. A favorite of ours is the National Public Radio Archive that can be accessed at www.npr.org. The Web page of Internet Talk Radio is http://town.hall.org/radio. We often use the Geek of the Week programs to test our projects. Historical streaming media are freely available in many areas. For example, the Oyez Project of Northwestern University maintains the US Supreme Court Multimedia Database at http://oyez.nwu.edu. The site archives original recordings of famous cases as well oral arguments and oral opinions in streaming audio format.

Understanding networked multimedia applications and technology by Fluckiger [37] is dated but gives a good overview of terminology and applicable standards. The Technology of Video and Audio Streaming by Austerberry and Starks [8] and Streaming Media Bible by Mack [75] are newer guides to actually using streaming media with current products. For a technical guide to multicast and multicast applications, see Multicast Communication: Protocols, Programming, and Applications by Wittmann and Zitterbart [131].

Many of the current streaming media tools use RTSP (Realtime Streaming Protocol) built over RTP (Realtime Transport Protocol). You can find a good overview of RTP and its enhancements in the article “Timer reconsideration for enhanced RTP scalability,” by Rosenberg and Schulzrinne [100]. The Multiparty Multimedia Session Control (mmusic) Working Group [84] of the IETF (Internet Engineering Task Force) [55] is in charge of maintaining and revising the RTSP and RTP specifications. This working group also oversees the development of the Session Initiation Protocol (SIP) for supporting voice over IP (VOIP) applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset