[Stanford Computer Network CS144 Project] Environment Configuration & Lab0: ByteStream

Author: Home Page

My column
C language from 0 to 1
Exploring C++
Data structure from 0 to 1
Exploring Linux
Rookie question collection

Welcome to follow: LikeCollectLeave a message

Code words are not easy. Your likescollectionsfollows are really important to me. If you have any questions, you can ask them in the comment area. Thank you for reading! ! !

Article directory

    • Preface
    • 1. Experimental purpose
    • 2. Experimental instructions
    • 3. Experimental content
    • 4. Experimental experience
    • 5. Code Appendix

Foreword

This article is only for learning and exchange. Please do not copy it directly. You will be responsible for the consequences.

1. Experiment purpose

  1. Learn how to get the content of a web page
  2. Learn how to implement simple client-server synchronization and monitoring
  3. Learn to write simple network programs
  4. Learn to implement simple and reliable byte streaming in memory

2. Experimental instructions

  1. Here we have developed a neat program that takes advantage of the capabilities of VMware Workstation Pro to fetch web pages over the Internet. We successfully used this feature to set up a reliable, bi-directional unordered byte stream communication mechanism, where one program was running on our computer and another program was on another computer on the Internet (such as a web server , such as Apache or nginx, or netcat program). This feature is called a stream socket, and it provides efficient transport services.
  2. Although the Internet itself only provides “unreliable” datagram services, we can observe that the abstraction of reliable out-of-order byte stream communication is implemented on the Internet. Next, we will implement a program in the computer that has this functionality: bytes can be written from the input in order, and read from the output in the same order. The byte stream is infinite, that is, the writer can finish inputting, but no more bytes can be written later. When the reader reaches the end of the stream, it reaches the “EOF” (end of) flag, indicating that there are no more bytes to read. This transmission method is reliable.

3. Experiment content

  1. Get the content of a web page
    1. When we enter the URL http://cs144.keithw.org/hello in the browser, the page shown in Figure 1-1 will be displayed. Now, we need to get the content of this page on the virtual machine via the command line.

image.png
Figure 1-1 Content viewed in the browser

  1. Enter the following command at the command line: telnet cs144.keithw.org http. What this command does is tell the telnet program to establish a reliable byte stream connection between your computer and a server named cs144.keithw.org, and to run a specific service called “http”, which is the HTTP server used for the World Wide Web. Text Transfer Protocol (HTTP). As shown in Figure 1-2.

image.png
Figure 1-2 Enter telnet cs144.keithw.org http in the command line

  1. Follow these steps from the command line:
    1. Enter “GET /hello HTTP/1.1”. This command tells the server that the path part of the page we want to obtain is “/hello”, and uses the GET method to obtain it, and the protocol version used is HTTP/1.1.
    2. Enter “Host: cs144.keithw.org”. This tells the server that the host part of the URL is “cs144.keithw.org”.
    3. Enter “Connection: close”. This tells the server to close the connection after sending the HTML page.
    4. Press the Enter key again. This tells the server that we have completed the HTTP request. The results are shown in Figure 1-3. As you can see, we got the content of the page as “Hello, cs144”.

image.png
Figure 1-3 Command line results

  1. Listen and connect

In previous experiments, we discovered that Telnet can be viewed as a client program that can connect to programs running on other computers. Now we can try to create a simple server program that waits for client connections.
Here are the steps:

  1. Enter the command “netcat -v -l -p 9090”. Netcat can interact between two devices, i.e. listening mode/transmitting mode. The option “-v” means to display all received monitoring information, “-l” means to monitor inbound information in the command line window and act as a server, and “-p 9090” means to use port 9090.
  2. Open another command line window and enter the command “telnet localhost 9090”. At this time, the window acts as a host and shares the same 9090 port with the server, thereby achieving simple communication between the server and the host.
  3. Now we enter information in any window. For example, here we enter hello network on the server side, as shown in Figure 1-4.

image.png
Figure 1-4 The input on the server side is consistent with the display on the host side

  1. Writing network programs using sockets
    1. Enter the command “git clone https://github.com/cs144/minnow” to obtain the source code of a startup code repository named “minnow”.
    2. If the page is successfully obtained, you will see a directory named “minnow”. You can use the “ls” command to view the directory contents. Next, use the “cd” command to enter the “minnow” directory. Then, enter the “cmake -S . -B build” command to create a directory named “build”. Finally, enter the “cmake –build build” command to compile the code. The final display shown in Figure 1-5 indicates success.

7(($J2}KQ7IHIRFRAXW57$6.png
Figure 1-5 Test successful

  1. It should be noted that you may encounter such an error, as shown in Figure 1-6.

Figure 1-6 Error display
At this time, you need to add “#include ” and “using std::uint64_t;” to the header file.

  1. Open the webget.cc file and modify the code, as shown in Figure 1-7. See the appendix for the code.

image.png
Figure 1-7 Code details

  1. In the build directory, enter make to compile. The compilation result is shown in Figure 1-8.

image.png
Figure 1-8 Compilation results

  1. Enter “./apps/webget cs144.keithw.org /hello” to test. The test results are shown in Figure 1-9.

image.png
Figure 1-9 Test results

  1. Enter “make check_webget” test sample, the test results are shown in Figure 1-10. As you can see, all test samples passed.

image.png
Figure 1-10 Test results

  1. Implementing reliable byte streams in memory
    1. Open and modify the “byte_stream.hh” and “byte_stream.cc” files as shown in Figure 1-11 and 1-12

image.png
Figure 1-11 byte_stream.hh file code details
image.png
Figure 1-12 byte_stream.cc file code details

  1. Enter “make cheat0” in the build directory to compile and check the file. You can see that all test points pass. The results are shown in Figure 1-13.

image.png
Figure 1-13 check results

4. Experimental experience

In this experiment, we learned how to obtain the content of a web page and implemented simple client-server synchronization and monitoring. At the same time, we also learned to write simple network programs and implement reliable byte streaming in memory. The following are some of my experiences and summary of the experiment:

  1. Through experiments, I gained a deep understanding of the basic principles and workings of the Internet. Understand the process of obtaining web content through the HTTP protocol, as well as the use of tools such as Telnet and Netcat.
  2. In the experiment, we used Telnet to establish a reliable byte stream connection and successfully obtained the content of the specified web page. This made me more familiar with the functions and usage of Telnet.
  3. We also learned how to create a simple server program that waits for client connections. By using Netcat tool, we successfully established simple communication between server and client.
  4. The writing network programs portion of the lab gave me a deeper understanding of the basic concepts of socket programming and network communication. By using the startup code library and compiling the code, I successfully implemented a simple network program.
  5. Finally, we also implemented reliable byte stream transmission in memory. By modifying and compiling the files “byte_stream.hh” and “byte_stream.cc”, we successfully completed the transmission of byte streams in memory. .

Through this experiment, I have a deeper understanding of network programming and byte stream transmission, and have mastered some practical tools and skills, which will be of great help to my future study and work in the network field.

5. Code Appendix

  1. Appendix 1: webget.cc
void get_URL( const string & amp; host, const string & amp; path )
{<!-- -->
  TCPSocket socktest;
  socktest.connect( Address( host, "http" ) );
  socktest.write("GET " + path + " HTTP/1.1\r\\
" // Request line
             "Host: " + host + "\r\\
" // Tell the server the host name
             "Connection: close\r\\
" // Notify the server to close the connection
             "\r\\
"); // Blank line
  socktest.shutdown( SHUT_WR ); // Close the write end

  while ( !socktest.eof() ) {<!-- --> // Read all data
    std::string tmp;
    socktest.read(tmp);
    cout << tmp;
  }
  socktest.close();
}
  1. Appendix 2: byte_stream.hh
#pragma once

#include <deque>
#include <stdexcept>
#include <string>
#include <string_view>
#include <cstdint>
using namespace std;
using std::uint64_t;

class Reader;
class Writer;

class ByteStream
{<!-- -->
protected:
  enum State {<!-- --> CLOSED, ERROR };//CLOSED 0, ERROR 1
  uint64_t capacity_;
  uint64_t bytes_pushed_ {<!-- -->}; // Number of bytes written
  uint64_t bytes_popped_ {<!-- -->}; //The number of bytes that have been popped {} means initialized to 0. It can also be initialized like this
  /*unsigned char flag = {};: Use curly braces for null initialization.
    unsigned char flag = 0;: Directly specify the initial value as 0.
    unsigned char flag{};: Use the curly brace initialization form in C++11.
    unsigned char flag(0);: Use traditional constructor syntax to initialize. */

  unsigned char flag {<!-- -->}; // 0: normal, 1: closed, 2: error
  std::deque<std::string> buffer_data {<!-- -->};
  std::string_view buffer_view {<!-- -->};//Introduction to string_view’s blog: https://blog.csdn.net/hepangda/article/details/80821567?ops_request_misc=%7B%22request%5Fid%22 %3A%22169875684216800184134794%22%2C%22scm%22%3A%2220140713.130102334..%22%7D &request_id=169875684216800184134794 &biz_id=0 & utm_medium=distribute.pc_search_result.none-task-blog-2 ~all~top_positive~default-1-80821567-null-null.142^v96^pc_search_result_base8 & amp;utm_term=string_view & amp;spm=1018.2226.3001.4187
  //string_view is actually a read-only string equivalent to "aliasing"

public:
  explicit ByteStream( uint64_t capacity );

  // Provide auxiliary functions for ByteStream's reader and writer interfaces
  Reader & reader();
  const Reader & amp; reader() const;
  Writer & writer();
  const Writer & amp; writer() const;
};

class Writer : public ByteStream
{<!-- -->
public:
  void push( std::string data ) noexcept; // Write data to the stream within the scope of available capacity.

  void close() noexcept; // Close the stream and no more data is allowed to be written to the stream.
  void set_error() noexcept; // An error occurs in the stream, set the error flag

  bool is_closed() const noexcept; // Determine whether the stream has been closed
  uint64_t available_capacity() const noexcept; // Calculate the remaining available capacity in the stream
  uint64_t bytes_pushed() const noexcept; // Count the number of bytes written in the stream
};

class Reader : public ByteStream
{<!-- -->
public:
  std::string_view peek() const noexcept; // Returns a read-only view of the next block of data in the stream
  void pop( uint64_t len ) noexcept; // Pop a data block of specified length from the stream

  bool is_finished() const noexcept; // Determine whether the stream has been closed and all data blocks have been popped
  bool has_error() const noexcept; // Determine whether an error occurs in the stream

  uint64_t bytes_buffered() const noexcept; // Calculate the number of bytes remaining in the current stream
  uint64_t bytes_popped() const noexcept; // Count the number of bytes that have been popped in the stream
};

/*
 * read: A (provided) helper function thats peeks and pops up to `len` bytes
 * from a ByteStream Reader into a string;
 */
void read( Reader & amp; reader, uint64_t len, std::string & amp; out );
  1. Appendix 3: byte_stream.cc
#include <stdexcept>

#include "byte_stream.hh"
using namespace std;
using std::uint64_t;

ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ) {<!-- -->}

void Writer::push( string data ) noexcept
{<!-- -->
  if(writer().is_closed())return;//If it is closed, it cannot be written
  auto len = min( data.size(), available_capacity() ); // Determine the length of data that can be written
  if (len == 0) {<!-- --> // If the writable data length is 0, it means it is full, return
    return;
  } else if ( len < data.size() ) {<!-- --> // If the length of the writable data is less than the length of data, it means that only part of the data can be written
    data.resize(len); //Truncate the length of data to a writable length
  }
  
  //Use deque<string>
  //Write data into buffer
  buffer_data.push_back( move( data ) ); //Before writing, buffer_data only contains the element just pushed, that is, buffer_data is empty and has no other elements. So it is judged whether it is 1
  if ( buffer_data.size() == 1) // buffer_view needs to be updated when it is empty before writing
    buffer_view = buffer_data.front();


  /*Use deque<char>
  //Write data into buffer
  for(size_t i = 0;i<data.size(); + + i){
    buffer_data.push_back( data[i] ); //Before writing, buffer_data only contains the element just pushed, that is, buffer_data is empty and has no other elements. So it is to determine the length of data
  }
  if ( buffer_data.size() == data.size()) // buffer_view needs to be updated when it is empty before writing
  {
    //Method 1 string_view has a constructor as follows, one parameter is const char* and a length: constexpr basic_string_view( const CharT* s, size_type count);
    buffer_view=std::basic_string_view( & amp;buffer_data.front(), 1 );
    //Method Two
    std::string tmp="";
    tmp.push_back(buffer_data.front());
    buffer_view = tmp;
  }
  */

  //Update the written data length
  bytes_pushed_ + = len;
}

void Writer::close() noexcept
{<!-- -->
  flag |= ( 1 << CLOSED );
}

void Writer::set_error() noexcept
{<!-- -->
  flag |= ( 1 << ERROR );
}

bool Writer::is_closed() const noexcept
{<!-- -->
  return flag & amp; ( 1 << CLOSED );
}

uint64_t Writer::available_capacity() const noexcept
{<!-- -->
  return capacity_ - reader().bytes_buffered();
  //Remaining writable capacity = total capacity - unread
}

uint64_t Writer::bytes_pushed() const noexcept
{<!-- -->
  return bytes_pushed_;
}

string_view Reader::peek() const noexcept
{<!-- -->
  return buffer_view;
  //deque<char> return { & amp;buffer_data.front(),1};
}

bool Reader::is_finished() const noexcept
{<!-- -->
  return writer().is_closed() & amp; & amp; ( bytes_buffered() == 0 );
}

bool Reader::has_error() const noexcept
{<!-- -->
  return flag & amp; ( 1 << ERROR );
}

void Reader::pop( uint64_t len ) noexcept
{<!-- -->
  if ( len > bytes_buffered() ) {<!-- -->
    return;
  }
  //Update the length of the popped data
  bytes_popped_ + = len;

  /*deque<char>
  // Pop the data in the buffer
  while(len--){
    buffer_data.pop_front();
  }*/

  //deque<string>
  while ( len > 0 ) {<!-- -->
    if ( len >= buffer_view.size() ) {<!-- --> //len: the length you want to delete bytes_buffered: the current length that can be deleted
      len -= buffer_view.size();
      buffer_data.pop_front();
      buffer_view = buffer_data.front(); // Ensure that buffer_data is not empty from the beginning
    } else {<!-- -->
      buffer_view.remove_prefix(len);
      len = 0;
    }
  }
}

uint64_t Reader::bytes_buffered() const noexcept
{<!-- -->
  return writer().bytes_pushed() - bytes_popped();
}

uint64_t Reader::bytes_popped() const noexcept
{<!-- -->
  return bytes_popped_;
}