- 快召唤伙伴们来围观吧
- 微博 QQ QQ空间 贴吧
- 文档嵌入链接
- 复制
- 微信扫一扫分享
- 已成功复制到剪贴板
10_Pipes_and_Sockets
展开查看详情
1 .Lecture 10 – Pipes and Sockets Learning Goals Understand the basic workings of a simple pipe using a circular buffer Describe the properties of uni-directional pipes and relation to synchronization Provide a brief overview of the client-server model Set up a pipe_server for multiple simultaneous connections Describe what sockets are used for, and how they connect Set up a socket_server for multiple simultaneous connections Describe why an explicit socket communication protocol is necessary Create an example communication protocol and API for an application © Paul Davies, C. Antonio Sanchez. Not to be copied, used, or revised without explicit written permission from the copyright owner.
2 .Pipes Pipes and Sockets 2 RobinOlim – iStockPhotos.com A basic pipe (or pipeline) is a one-way communication channel , providing sequential data transfer on a first-in first-out ( FIFO ) basis. Reading from a pipe is destructive : the data read can be considered lost unless you save a copy of it while reading. Writing always appends to the end of the pipe, where it is held until read. Data In Data Out MASCOT 3 Symbol for a Pipe
3 .Pipes Pipes and Sockets 3 Buffer Top Buffer Bottom Pointer to Reading Position Pointer to Writing Position Pipes are usually implemented using circular buffers : the “beginning” of the buffer advances and wraps around as you read from the buffer. The end also wraps around. Care must be taken not to write so much as to wrap around and overwrite unread data. The pipe implementation generally handles this synchronization internally, blocking writes while the pipe is full.
4 .Pipes: Synchronization Any process attempting to read from an empty pipe will generally block (or wait) until data is available. This will cause the thread to suspend . It will be awoken automatically when data is available. Pipes and Sockets 4 Read Operation Blocked Empty Pipeline Write Operation Blocked FULL Pipeline Any process attempting to write to a full pipe will generally block (or wait) until all contents are written to the pipe. This will cause the thread to suspend . It will be awoken automatically when there is room to write the data.
5 .Pipes – Synchronization Pipes and Sockets 5 The discrepancy: reading to a pipe will return if only some bytes we requested are available, writing will only return when all of the bytes are written. Why? When reading, we don’t always know how many bytes to expect. Instead, we generally read data in blocks into a fixed-length buffer, and parse/assemble them after-the-fact. Since reading/writing can potentially cause the current thread to suspend , it is usually adviced to handle this communication in a separate thread . That way the blocked operation will not interfere with the rest of the process.
6 .Basic Pipe – Usage Pipes and Sockets 6 We create and connect to a basic pipe using a unique name , just like other named resources such as mutexes and shared memory. Rather than using OS-specific calls, we will use the CPEN333 course library. class basic_pipe : public virtual named_resource { public : basic_pipe( const std::string& name, size_t size = 1024 ); bool open(); // open pipe for reading or writing bool close(); // prevent further writes bool write( const void * buff, size_t size); // writes data of a particular size size_t read( void * buff, size_t size); // reads data, returning number of bytes read bool read_all( void * buff, size_t size); // reads exactly size bytes, blocking if needed size_t available(); // number of bytes available bool unlink(); // unlinks name from pipe (Mac/Linux) };
7 .Basic Pipe Example Pipes and Sockets 7 #include <cpen333/process/pipe.h> int main() { // create or connect to pipe cpen333 :: process :: basic_pipe pipe( "shared_pipe" , 1024 ); pipe.open(); // read integer int x; pipe.read_all(&x, sizeof (x)); // read all of an array from the pipe int array[ 10 ]; pipe.read_all(&array[ 0 ], sizeof (array)) ; // read a string until we get a terminating zero std :: string name; char c; while ( (pipe.read(&c, 1) > 0) && c != 0 ) { name.push_back(c); } pipe.close(); return 0 ; } #include <cpen333/process/pipe.h> int main() { // create a shared pipe of size 1024 bytes cpen333 :: process :: basic_pipe pipe( "shared_pipe" , 1024 ); pipe.open(); // write i to pipe int i = 20 ; pipe.write(&i, sizeof (i) ); // write array to pipe int array[ 10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 }; pipe.write(&array[ 0 ], sizeof (array)); // write string to pipe, // including terminating 0 std :: string name = "Snuffaluffagus" ; pipe.write(name.c_str(), name.length()+ 1 ); pipe.close(); return 0 ; }
8 .Notes on Basic Pipes Once the “read” end of a pipe is closed, the writer cannot write any new data to the pipe (the method will return immediately). Once the “write” end is closed, the reader can read whatever data happens to be in the pipe already. Once this is consumed, the reader will read the EOF marker, returning immediately. The basic_pipe implementation: This byte-wise “FIFO” does not explicitly enforce a single reader or single writer. That is your job to keep track of. Multiple readers/writers can lead to interleaved data unless you protect it with a mutex . Pipes and Sockets 8
9 .Pipes: Client-Server Model Pipes and Sockets 9 On Windows, named pipes are bidirectional (like having two pipes – one in each direction). Furthermore, they use a client-server model: The server creates the named pipe, waits for a connection The client connects to the server, establishing the pipe Windows also allows multiple client pipes to be connected to the server using the same name . Server Client Client Client “mypipe” “mypipe” “mypipe”
10 .Pipes: Client-Server Model Pipes and Sockets 10 On POSIX-based systems (Mac/Linux), pipes (or named fifo s) are unidirectional and do not naturally allow for multiple connections. However, we can mimic the Windows behaviour: For every client connection, create two fifos: one for each direction Once a connection is established, unlink the name from the fifo so the server can re-create a new fifo with the same name Server Client Client Client “mypipe” “mypipe” “mypipe”
11 .Pipes: Client-Server Model Pipes and Sockets 11 class pipe : public virtual named_resource { public : pipe(); // creates an empty named pipe instance pipe( const std::string& name); // creates “unconnected” named pipe instance bool open(); // establishes a connection to the named server bool close(); // closes the pipe connection bool write( const void * buff, size_t size); // writes data of a particular size size_t read( void * buff, size_t size); // reads data, returning number of bytes read bool read_all( void * buff, size_t size); // reads until all bytes are available }; class pipe_server : public virtual named_resource { public : pipe_server( const std::string& name); // creates named pipe server bool open(); // starts listening for connections bool close(); // closes the server bool accept(pipe& client) // accepts a client connection, populating client };
12 .Modern C++ “Move” Semantics Pipes and Sockets 12 Pipes cannot (and should not) be copied: if we did create a “copy” of a connected pipe, then we would have two readers or two writers on the same communication channel. This is not what pipes are designed for. However, we can “ move ” a pipe. cpen333 :: process :: pipe pipe1( "mypipe" ); cpen333 :: process :: pipe pipe2 = pipe1; cpen333 :: process :: pipe pipe3 = pipe1; Cannot have multiple copies using same channel cpen333 :: process :: pipe pipe1( "mypipe" ); cpen333 :: process :: pipe pipe2 = std ::move(pipe1); We can “move” it All resources from pipe1 are “moved” over to pipe2 – i.e. pipe1 no longer has a copy of them. We say that pipe1 is now in an unspecified state.
13 .Modern C++ “Move” Semantics Pipes and Sockets 13 Moves are useful for transferring ownership of objects to other methods, such as passing a non-copyable object to another method. A method that accepts a moved object uses the && symbol: void thread_function( cpen333 :: process :: pipe && moved_pipe) { // ... } // moved_pipe is destroyed int main() { cpen333 :: process :: pipe pipe1( "mypipe" ); // create a separate thread for pipe communication std :: thread mythread(thread_function, std ::move(pipe1)); mythread.detach(); return 0; } The pipe now belongs to the thread_function scope.
14 .Server Code Pipes and Sockets 14 #include <cpen333/process/pipe.h> // ... int main() { // create and start server cpen333 :: process :: pipe_server server( "mypipe" ); server.open(); cpen333 :: process :: pipe client; // empty client // keep accepting clients until we fail while (server.accept(client)) { // create detached thread to handle client std :: thread thread(client_handler, std ::move(client)); thread.detach(); } server.close(); // close server return 0 ; } Since reads/writes can block , handling multiple pipes is best accomplished using separate threads . The client is “populated” and connected in accept , then is move d to the handler thread.
15 .Client Code Pipes and Sockets 15 #include <cpen333/process/pipe.h> int main() { cpen333 :: process :: pipe client( "mypipe" ); client.open(); // connect to server // some reads/writes int request = 1 ; int response; client.write(&request, sizeof (request)); client.read_all(&response, sizeof (response)); client.close(); // close connection return 0 ; } If the other end of the pipe is closed, writing to the pipe will fail (return false ). If the other end of the pipe is closed and we try to read, we can continue to read all remaining bytes in the pipe. When done, read() will return 0 to indicate EOF, and read_all(…) will return false .
16 .Sockets Pipes and Sockets 16 RobinOlim – iStockPhotos.com A socket provides a two-way communication channel , with sequential data transfer on a first-in first-out ( FIFO ) basis. Reading from a socket is destructive : the data read can be considered lost unless you save a copy of it while reading. Writing always appends to the end of the socket, where it is held until read.
17 .Sockets Pipes and Sockets 17 THE fundamental technology for communication in a distributed system . The entire internet is based around socket communication. Provide a programming interface to facilitate inter-process communication between a Client Program and a Server Program , usually over a network. The interface is very low level : sequential reads and writes of bytes of data, much like a pipe, but across a network rather than through shared memory.
18 .Socket Server Pipes and Sockets 18 The server opens a port on its local machine and listens for connections. Every computer has a whole bunch of ports that can be used for any kind of network communication. By Joseph A. Carr (http://www.JoeTourist.net/) [Attribution], via Wikimedia Commons Some ports are reserved for specific tasks: Port Description 20 FTP -- Data 21 FTP -- Control 22 SSH 80 HTTP 143 Interim Mail Access Protocol (IMAP) 443 HTTPS 1080 Socks Dynamic ports (private ports) are 49152 to 65535
19 .Socket Server The server opens the port, listens for incoming requests, accepts a client connection, and then communicates with the client using low-level read/write operations. During and after the connection process, the original port is still free to listen for new clients. Pipes and Sockets 19 Server :52001 Client Client Client
20 .Socket Client Tries to connect to a server at a give IP address and port . Pipes and Sockets 20 The IP Address of the server identifies where the remote server is (located anywhere in the world). www.google.ca : 173.194.202.94 www.ubc.ca : 206.87.224.15 cpen333:github.io : 151.101.53.147 localhost : your local machine – 127.0.0.1 Once connected, the client can send and receive messages to/from the server. When communication is completed, the socket is closed to terminate the connection.
21 .Sockets Pipes and Sockets 21 class socket : public virtual named_resource { public : socket(); // creates an empty socket instance socket( const std::string& server, int port); // creates “unconnected” socket instance bool open(); // establishes a connection to the server bool close(); // closes the socket connection bool write( const void * buff, size_t size); // writes data of a particular size size_t read( void * buff, size_t size); // reads data, returning number of bytes read bool read_all( void * buff, size_t size); // reads until all bytes are available }; class socket_server : public virtual named_resource { public : socket_server( int port); // creates a socket server to listen on a port // (0 for auto-detecting of free port) bool open(); // starts listening for connections bool close(); // closes the server bool accept(socket& client) // accepts a client connection, populating client int port(); // determine the server port (only valid after server is started) };
22 .Server Code Pipes and Sockets 22 #include <cpen333/process/socket.h> // ... int main() { // create and start server cpen333 :: process :: socket_server server( 52001 ); server.open(); cpen333 :: process :: socket client; // empty client // keep accepting clients until we fail while (server.accept(client)) { // create detached thread to handle client std :: thread thread(client_handler, std ::move(client)); thread.detach(); } server.close(); // close server return 0 ; } Since reads/writes can block , handling multiple sockets is best accomplished using separate threads . The client is “populated” and connected in accept , then is move d to the handler thread.
23 .Client Code Pipes and Sockets 23 #include <cpen333/process/socket.h> int main() { cpen333 :: process :: socket client( " localhost" ,52001); client.open(); // connect to server // some reads/writes int request = 1 ; int response; client.write(&request, sizeof (request)); client.read_all(&response, sizeof (response)); client.close(); // close connection return 0 ; } If the other end of the socket is closed, writing to the socket will fail (return false ). If the other end of the socket is closed and we try to read, we can continue to read all remaining bytes in the socket. When done, read() will return 0 to indicate EOF, and read_all(…) will return false .
24 .Socket Communication Protocol Pipes and Sockets 24 // some reads/writes int request = 1 ; int response; client.write(&request, sizeof (request)); client.read_all(&response, sizeof (response)); Is there anything potentially wrong with the following? YES! Integers do not have standard sizes across all machines. Furthermore, the order of the bytes can even be inconsistent!!
25 .Endianness Pipes and Sockets 25 Let’s say you want to send a number to someone, one digit at a time. Which digit would you start with first? Send 654321: 6, 5, 4, 3, 2, 1 OR 1, 2, 3, 4, 5, 6 ? 1, 2, 3, 4, 5, 6 makes sense because numbers usually operate from right-to-left (e.g. addition, multiplication) 6, 5, 4, 3, 2, 1 makes sense because we usually read from left-to-right Big Endian : most-significant byte first Little Endian : least-significant byte first Intel, arm SPARC, Power, PowerPC, MIPS
26 .Socket Communication Protocol Pipes and Sockets 26 There is a need to establish an explicit protocol for your application Most web-based communication is text-based (e.g. HTML or JSON) For raw numbers, byte order and size needs to be explicitly stated. E.g. Messages are sent with a 2-byte message-type identifier , followed by a 4-byte little-endian integer indicating the remainder of the message size, followed by UTF-8 text-based content in JSON format that ends with a terminating zero. 0x6a 0x73 0x10 0x00 0x00 0x00 0x7b 0x22 0x6d 0x73 0x67 0x22 0x3a 0x22 0x68 0x65 0x6c 0x6c 0x6f 0x22 0x7d 0x00 type = ‘j’ ‘s’ size = 16 content = {“msg”:“hello”}
27 .Application Program Interface (API) Pipes and Sockets 27 Once the communication protocol is established, the client and server can successfully send and receive data, ensuring it is consistent between the two endpoints. The protocol isn’t concerned with the actual content of the data apart from its structure. In many applications, the data informs the client/server to perform some action . We call the set of allowable actions and their parameters the Application Program Interface ( API ). E.g. The message content may ask the server to add a song to a database, or request directions on a Google Map. After each request there is usually a response to inform the client the status of the action, or return results.
28 .Application Program Interface (API) Pipes and Sockets 28 { "results" : [ { "formatted_address" : "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA" , "geometry" : { "location" : { "lat" : 37.4224764 , "lng" : - 122.0842499 }, "location_type" : "ROOFTOP" , "viewport" : { "northeast" : { "lat" : 37.4238253802915 , "lng" : - 122.0829009197085 }, "southwest" : { "lat" : 37.4211274197085 , "lng" : - 122.0855988802915 } } }, "place_id" : "ChIJ2eUgeAK6j4ARbn5u_wAGqWA" , "types" : [ "street_address" ] } ], "status" : "OK" } https://developers.google.com/maps/documentation/geocoding/start e.g. Google Maps has an API for finding locations. HTTP requests are made of the form https://maps.googleapis.com/maps/api/geocode/json? address=1600+Amphitheatre+Parkway,+Mountain+View,+CA & key=YOUR_API_KEY Google will then respond to your request with a JSON-formatted string according to the API specification.
29 .Homework Use Case Diagrams 29 https://www.youtube.com/watch?v=tLJXJLfLCCM&list=PL05DE2D2EDDEA8D68 Go through the tutorial on Use Case Diagrams in Visual Paradigm Read/compile/run the examples in: examples/q4_pipe examples/q6_socket