CS144 学习笔记 00

CS144 学习笔记 00

RayAlto OP

00. telnet 的使用

之前一直以为 telnet 用的是自己的协议呢,实际用起来更像是建立一个传输层连接,具体传输细节需要手打。

HTTP GET

试着模拟 curl http://www.rayalto.org ,先执行 telnet www.rayalto.org http ,效果差不多像这样:

1
2
3
4
rayalto@RayAltoXL ~$ telnet www.rayalto.org http                                   
Trying 19.19.81.0...
Connected to www.rayalto.org.
Escape character is '^]'.
  • 指令最后的 http 表示想要访问 www.rayalto.org 上的 http 服务,其实是端口的别名,可以在 /etc/services 里找到。
  • 最后一行 Escape character is '^]'. 指按 <C-]> 键可以呼出 telnet 的 shell ,比如呼出后执行 close 可以关闭连接。

然后需要手打 HTTP 请求,比如输入下面的三行:

1
2
3
GET / HTTP/1.1
Host: www.rayalto.org
Connection: close

之后再输入一个空行(再按一下 <CR> )表示 HTTP 请求结束,然后就可以收到响应了,整体大概像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
rayalto@RayAltoXL ~$ telnet www.rayalto.org http                                   
Trying 19.19.81.0...
Connected to www.rayalto.org.
Escape character is '^]'.
GET / HTTP/1.1
Host: www.rayalto.org
Connection: close

HTTP/1.1 301 Moved Permanently
Server: nginx/1.18.0
Date: Fri, 27 Sep 2024 03:48:34 GMT
Content-Type: text/html
Content-Length: 169
Connection: close
Location: https://www.rayalto.org/

<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.18.0</center>
</body>
</html>
Connection closed by foreign host.

SMTP

SMTP 也可以手写,实在是太 🤓 辣,查了查网易的文档,下面展示如何借助 telnet 用网易邮箱的 SMTP 服务器发邮件给 Gmail :

网易的 SMTP 服务器是 smtp.163.com ,执行 telnet smtp.163.com smtp ,大概像这样:

1
2
3
4
5
rayalto@RayAltoXL ~$ telnet smtp.163.com smtp   
Trying 111.124.203.45...
Connected to smtp.163.com.
Escape character is '^]'.
220 163.com Anti-spam GT for Coremail System (163com[20141201])

然后输入 HELO 163.com 跟网易打个招呼(向 163.com 认证你的电脑),大概像这样:

1
2
HELO 163.com
250 OK

不确定这一步的 163.com 正不正确,甚至不确定这一步有没有必要。

然后网易邮箱需要认证登陆,输入 AUTH LOGIN

1
2
AUTH LOGIN
334 dXNlcm5hbWU6

返回的 dXNlcm5hbWU6 是 Base64 编码的 username: ,需要回复用户名,比如 foobar@163.com 的用户名就是 foobar ,回复也需要是 Base64 编码的,可以借助 openssl

1
2
rayalto@RayAltoXL ~$ echo -n 'foobar' | openssl enc -e -base64          
Zm9vYmFy

得到 Zm9vYmFy 输进去:

1
2
Zm9vYmFy
334 UGFzc3dvcmQ6

返回的 UGFzc3dvcmQ6 是 Base64 编码的 Password: (不知道为啥 username 没大写而 Password 却首字母大写了),输入 Base64 编码的 SMTP 密钥:

1
2
5LyK5ZC55Y+v54ix5ru05o2P
235 Authentication successful

登陆完成了,然后输入 MAIL FROM: <foobar@163.com> 设置发信人,注意 <> 不可以省略:

1
2
MAIL FROM: <foobar@163.com>
250 Mail OK

输入 RCPT TO: <foobar@gmail.com> 设置收信人, <> 也不可以省略:

1
2
RCPT TO: <foobar@gmail.com>
250 Mail OK

输入 DATA 之后就可以写邮件内容了:

1
2
DATA
354 End data with <CR><LF>.<CR><LF>

意思是 . 作为一行表示邮件内容结束,比如我输入这几行:

1
2
3
4
5
6
From: foobar@163.com
To: foobar@gmail.com
Subject: Hello from CS144 Lab 0!

Foo bar baf!
.

注意标头和正文之间需要空一行,成功时的回复像这样:

1
250 Mail OK queued as gzsmtp1,5LyK5ZC55Y+v54ix5ru05o2P--.114514 1919810

然后执行 QUIT 告诉网易邮箱关闭连接:

1
2
3
QUIT
221 Bye
Connection closed by foreign host.

完整过程像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
rayalto@RayAltoXL ~$ telnet smtp.163.com smtp   
Trying 111.124.203.45...
Connected to smtp.163.com.
Escape character is '^]'.
220 163.com Anti-spam GT for Coremail System (163com[20141201])
HELO 163.com
250 OK
AUTH LOGIN
334 dXNlcm5hbWU6
Zm9vYmFy
334 UGFzc3dvcmQ6
5LyK5ZC55Y+v54ix5ru05o2P
235 Authentication successful
MAIL FROM: <foobar@163.com>
250 Mail OK
RCPT TO: <foobar@gmail.com>
250 Mail OK
DATA
354 End data with <CR><LF>.<CR><LF>
From: foobar@163.com
To: foobar@gmail.com
Subject: Hello from CS144 Lab 0!

Foo bar baf
.
250 Mail OK queued as gzsmtp1,5LyK5ZC55Y+v54ix5ru05o2P--.114514 1919810
QUIT
221 Bye
Connection closed by foreign host.

然后就可以在 Gmail 的垃圾邮件里找到刚刚发的邮件了:

01. netcat

这里用的是 openbsd-netcat ,让 netcat 监听 9090 :

1
nc -vlp 9090

然后再用 telnet 连接:

1
telnet localhost 9090

两边就可以双向通信了。

其实 telnet 那里也可以用 netcatnc -N localhost 9090 也可以达成同样的效果。

02. 实现 webget

git clone https://github.com/CS144/minnow.git ,编辑 ./apps/webget.cc 完成 get_URL 方法:

1
2
3
4
5
6
7
8
9
10
11
12
string buf;
TCPSocket s;
s.connect( { host, "http" } );
s.write( "GET " + path + " HTTP/1.1\r\n" );
s.write( "HOST: " + host + "\r\n" );
s.write( "Connection: close\r\n" );
s.write( "\r\n" );
while ( !s.eof() ) {
s.read( buf );
cout << buf;
}
s.close();

它这个 clang-format 调的我生理不适

03. 实现内存上的可靠字节流

就是完成 minnow 的 src/byte_stream.{hh,cc} ,我的答案(他们的 clang-format 真的辣眼睛):

class ByteStreamprotected 下加上这些成员:

1
2
3
4
std::string buf_;
uint64_t nreads_ {};
uint64_t nwrites_ {};
bool closed_ {};

初始化列表初始化 buf_

1
ByteStream::ByteStream( uint64_t capacity ) : capacity_( capacity ), buf_( capacity_, '\0' ) {}

各成员函数定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
bool Writer::is_closed() const
{
return closed_;
}

void Writer::push( string data )
{
if ( is_closed() ) {
return;
}
size_t data_len = data.length();
const uint64_t avai_cap = available_capacity();
if ( data_len == 0 || avai_cap == 0 ) {
return;
}
string_view d { data };
if ( data_len > avai_cap ) {
d = d.substr( avai_cap );
data_len = avai_cap;
}
const size_t cursor = nwrites_ % capacity_;
const size_t len_left = capacity_ - cursor;
if ( data_len > len_left ) {
buf_.replace( cursor, len_left, data, 0, len_left );
buf_.replace( 0, data_len - len_left, data, len_left, data_len - len_left );
} else {
buf_.replace( cursor, data_len, data, 0, data_len );
}
nwrites_ += data_len;
return;
}

void Writer::close()
{
closed_ = true;
}

uint64_t Writer::available_capacity() const
{
return ( capacity_ - ( nwrites_ - nreads_ ) );
}

uint64_t Writer::bytes_pushed() const
{
return nwrites_;
}

bool Reader::is_finished() const
{
return closed_ && bytes_buffered() == 0;
}

uint64_t Reader::bytes_popped() const
{
return nreads_;
}

string_view Reader::peek() const
{
return string_view { buf_ }.substr( nreads_ % capacity_, bytes_buffered() );
}

void Reader::pop( uint64_t len )
{
nreads_ += len;
}

uint64_t Reader::bytes_buffered() const
{
return ( nwrites_ - nreads_ );
}

我的测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
rayalto@RayAltoXL ~/Projects/cpp/minnow$ cmake --build ./build --target check0
Test project /home/rayalto/Projects/cpp/minnow/build
Connected to MAKE jobserver
Start 1: compile with bug-checkers
1/10 Test #1: compile with bug-checkers ........ Passed 0.20 sec
Start 2: t_webget
2/10 Test #2: t_webget ......................... Passed 1.59 sec
Start 3: byte_stream_basics
3/10 Test #3: byte_stream_basics ............... Passed 0.01 sec
Start 4: byte_stream_capacity
4/10 Test #4: byte_stream_capacity ............. Passed 0.01 sec
Start 5: byte_stream_one_write
5/10 Test #5: byte_stream_one_write ............ Passed 0.01 sec
Start 6: byte_stream_two_writes
6/10 Test #6: byte_stream_two_writes ........... Passed 0.01 sec
Start 7: byte_stream_many_writes
7/10 Test #7: byte_stream_many_writes .......... Passed 0.05 sec
Start 8: byte_stream_stress_test
8/10 Test #8: byte_stream_stress_test .......... Passed 0.03 sec
Start 37: compile with optimization
9/10 Test #37: compile with optimization ........ Passed 0.11 sec
Start 38: byte_stream_speed_test
ByteStream throughput: 18.27 Gbit/s
10/10 Test #38: byte_stream_speed_test ........... Passed 0.18 sec

100% tests passed, 0 tests failed out of 10

Total Test time (real) = 2.21 sec
Built target check0