- At root level of the project, run:
cmake . -B build
cmake --build build
- Then in one terminal window/session, run the server
./build/src/server
- Open a new terminal window/session, run the client with arguments:
./build/src/client <args>
- one example is to run the Python test script itself:
./src/test_commands.py
- one example is to run the Python test script itself:
- server-side vs. client-side
- Two kinds of mainstream sockets:
- DARPA Internet Socket
- Unix Socket
- Two mainstream types:
- Stream Sockets -
SOCK_STREAM
- reliable two-way connected communication streams
- uses TCP
- Datagram Sockets -
SOCK_DGRAM
- a.k.a. connectionless socket
- uses UDP
- Stream Sockets -
socket()
- syscall that returns an fdbind()
- associates an address to a socket fdlisten()
- enables user to accept connections to that addressaccept()
- takes a listening fd- when a client makes a connection to the listening address,
accept()
returns an fd that represents the connection socket
read()
- receives data from a TCP connectionwrite()
- sends dataclose()
- destroys the resource referred by the fd and recycles fdsend()
andrecv()
might offer better control over data transmission thanread()
andwrite()
connect()
- takes a socket fd and address
- makes a TCP connection to that address
- Used to spilt requests apart from the TCP byte stream
- Current scheme:
+-----+-----+-----+-----+----------
| len | msg1 | len | msg2 | more...
+-----+-----+-----+-----+----------
- Two parts:
- 4-byte little-endian integer - length of the request
- variable-length request
- Text
- human-readable
- HTTP
- hard to parse - variable-length strings
- Avoid unnecessary variable-length components
- protocol parsing currently uses 2
read()
syscalls- could use a buffered I/O
- Three ways to handle concurrent connections in server-side network programming
- forking
- creates new processes for each client connection
- multi-threading
- uses threads instead of processes
- event loops
- polling
- non-blocking I/O
- forking
- Use
poll
to determine which fd can be operated without blocking - when an I/O operation is done on an fd, it should be non-blocking
- In blocking mode,
read
blocks the caller when there are no data in the kernelwrite
blocks when the write buffer is fullaccept
blocks when there are no new connections in the kernel queue
- In non-blocking mode
- operations either success without blocking, or
- fail with errno
EAGAIN
- not ready - non-blocking operations that fail with
EAGAIN
must be retried after the readiness was notified bypoll
poll
is the SOLE blocking operation in an event loop- All blocking networking IO APIs (
read
,write
,accept
) have a nonblocking mode - APIs that do not have a non-blocking mode (
gethostbyname
, disk IOs) should be performed in thread pools - Timers should also implemented within the event loop since we cannot
sleep
inside - Also
select
andepoll
on Linuxepoll
consists of 3 syscalls:epoll_create
epoll_wait
epoll_ctl
- stateful API
- used to manipulate an fd set create by
epoll_create
, which is operated upon byepoll_wait
- Need buffers for reading/writing
- in nonblocking mode, IO operations are often deferred
poll()
poll()
is actually horribly slow for large number of connections- Use a dedicated event lib such as
libevent
instead - ask the OS to let us know when some data is ready to read on which sockets
- Plan of using
poll()
:- Keep an array of
struct pollfd
s with info about:- which socket descriptors should be monitored
- what kind of events we want to monitor for
- OS will block on the
poll()
call until one of the events occurs or user-specified timeout occurs
- Keep an array of
- For a request/response protocol, clients are not limited to sending one request and waiting for the response at a time
- could save some latency
- **pipelining**
- command: list of strings, such as
set key val
- Scheme of the command:
nstr
- number of strings - 4 byteslen
- length of the following string - 4 bytes
+------+-----+------+-----+------+-----+-----+------+
| nstr | len | str1 | len | str2 | ... | len | strn |
+------+-----+------+-----+------+-----+-----+------+
- Scheme of the response:
res
- 4-byte status codedata
- response string
+-----+---------
| res | data...
+-----+---------
- Tow kinds of hashtables:
- chaining
- open addressing
- Main difference: collision resolution
- open addressing: seeks another free slot in the event of a collision
- chaining: groups conflicting keys with a linked list
- When needing more space for the hashtable, we resize
- To avoid stalling the server, keep two hashtables and gradually move nodes between them
- The **Type-Length-Value (TLV)** scheme
- Redis supports querying sorted data by rank
- Redis actually uses skiplist for storing sorted data
- Resources:
- Rank-Based Queries
- the primary use case of sorted sets
- range query - just a regular binary tree look-up, followed by an offset operation
- however, the offset operation is not a regular binary tree walk
- Sorted Set
- a Redis data type
- https://redis.io/docs/data-types/sorted-sets/
- Every networked application needs to handle timeouts
- we need timers
- timeout value of
poll
should be the timeout value of the nearest timer (why..?)
- timeout value of
- Add timers to kick out idle TCP connections
- For each connection there is a timer, set to a fixed timeout into the future
- every time I/O activities occur on the connection, timer is renewed to a fixed timeout
- When a timer is renewed, it becomes the most distant one
- TTL - one way to manage the size of cache
- Heap data structure
- binary tree, packed in to array
- parent is no bigger than children
- thread from the thread pool consumes tasks form a queue and executes them
pthread
APIpthread_mutex_t
pthread_cond_t
- wakes up consumer threads only when the queue is not empty
- consumer threads should be sleeping when idle