- Requirements
- Server Design
- Process-Based Concurrency
- Thread-Based Concurrency
- Iterative-Based Concurrency
- Tests
- Problems
- TCP is a Stream Protocol
- A Looping Read Problem
- Client sends a message, server sends a reply, done.
- The server should support maximum concurrency.
- Not to useful in this part of the project.
- The server should be resilient.
- Nothing a client does should be able to stop the server.
- An iterative, non-multiplexed server would seem to do.
- Not much concurrency to handle.
- One (C) person used
fork()
, nobody used threads.
- What happens for mis-behaving clients?
- Remember, a server must be able to handle arbitrary behaviors.
- What kind of mis-behavior?
- Wrong message types or invalid messages.
- Not a problem: read the tag byte then reject.
- Slow or incomplete message sends.
- A problem: the read could block indefinitely.
- No concurrency; the server will eventually have to juggle (at least)
two clients at the same time.
- Slow or incomplete message sends need a timed-read.
- Try to read for so long, then time out. See
tip 25
in Snader.
- For concurrency the choices are three.
- Processes are simple and easy, but expensive.
- Threads not as simple and easy as are processes, but are also less
expensive than processes are.
- Getting the I-O right could be a problem.
- Iterative multiplexing is the most complicated of the three.
static void reaper(int sig)
while (wait3(NULL, WNOHANG, NULL) > 0) { }
signal(SIGCHLD, reaper)
int main(int argc, char * argv[])
signal(SIGCHLD, reaper)
ms = tcp_srvr_open(port)
while true
ss = accept_tcp(ms)
switch fork()
case 0
close(ms)
handle_client(ss)
return EXIT_SUCCESS
case -1
errexit("fork() failed:")
default
(void) close(ss)
return EXIT_SUCCESS
int main(int argc, char * argv[])
ms = tcp_srvr_open(port)
pthread_t th
pthread_attr_t ta
pthread_attr_setdetachstate(&ta, DETACHED)
while true
ss = accept_tcp(ms)
create_pthread(th, ta, handle_client, ss)
return EXIT_SUCCESS
handle_client(skt, msg-info)
int main(int argc, char * argv[])
const int ls = srvr-tcp-open(port)
skt-set::add(ls)
while true
skts = skt-set::ready()
for s = skts
if s == ls
s = accept_tcp(ls)
skt-set::add(s)
msg-map[s] = new-msg
else
handle_client(s, msg-map[s])
return EXIT_SUCCESS
- A known client with an unknown server.
- An unknown client with an unknown server.
- Two known clients with an unknown server.
- Other tests were less useful (but still important).
- A partially sending client.
- A slowly sending client.
- Command-line options not implemented correctly.
- Not treating TCP as a stream protocol.
- Message boundaries are not preserved.
- Not protecting read loops against partial sends.
- Not timing out slow or incomplete reads.
- The following code fails
#define MAX 50
char buf[MAX]
sd = recv(s, buff, sizeof(buf), 0)
- TCP is a stream protocol, it need not maintain message boundaries.
- See Tip 6 in Snader.
- What about
char buf[1]
sd = recv(s, buff, sizeof(buf), 0)
- This works, but is way expensive.
- Two system calls (
select()
and recv()
) per byte.
- What about
long left = bufLen, rcvd
char * p = bufp
while left > 0
rcvd = recv(s, p, left, 0)
if rcvd < 0
if errno == EINTR
continue
return -1
if rcvd == 0
break
p += rcvd
left -= rcvd
- Better, but susceptible to DOS attacks.
- The client stops sending in the middle of the message.
- Understand the problem you're trying to solve.
- Make your design choices wisely.
- "Wisely" can be interpreted many ways.
- Servers should be non-destructable.
- Know your tools.
- This is hard to do with TCP/IP and Unix.
This page last modified on 3 March 2004.