第一个 Erlang 程序: Ftp 下载客户端
学习 Erlang 一周的习作:
1 -module(ftp_down).
2 -export([get/6,test/0,get_data/4]).
3
4 test() ->
5 get("ftp.ftpplanet.com", 21, "anonymous", "anonymous", "images/image001_42.jpg", "d:/").
6
7 client_message(Format, Data) ->
8 io:format("C: " ++ Format ++ "~n", Data).
9
10 server_message(Format, Data) ->
11 io:format("S: " ++ Format ++ "~n", Data).
12
13 server_verify_message(Data, ExpectedCode) ->
14 {Code, Message} = Data,
15 ExpectedCode = Code,
16 io:format("S: ~s~n", [Message]),
17 ok.
18
19 recv_until_EOL(Sock, StrSoFar) ->
20 Len = string:len(StrSoFar),
21 NotALine = Len < 2 orelse not string:equal("/r/n", string:substr(StrSoFar, Len - 1, 2)),
22 if
23 NotALine ->
24 {ok, Msg} = gen_tcp:recv(Sock, 1),
25 recv_until_EOL(Sock, StrSoFar ++ Msg);
26 true ->
27 StrSoFar
28 end.
29
30 recv_until_EOL(Sock) ->
31 recv_until_EOL(Sock, "").
32
33 recv_until_xxxend(Sock, Num, Line, LinesSoFar) ->
34 ExpectedTail = integer_to_list(Num) ++ " end",
35 ExpectedTailLen = string:len(ExpectedTail),
36 Pos = string:len(Line) - ExpectedTailLen - 1,
37 Eq = Pos > 0 andalso string:equal(ExpectedTail, string:to_lower(string:substr(Line, Pos, ExpectedTailLen))),
38 if
39 Eq ->
40 {Num, LinesSoFar ++ Line};
41 true ->
42 recv_response(Sock, Num, LinesSoFar ++ Line)
43 end.
44
45 recv_response(Sock, LinesSoFar) ->
46 Line = recv_until_EOL(Sock),
47 {Num, [H | _]} = string:to_integer(Line),
48 if
49 H == $- ->
50 recv_until_xxxend(Sock, Num, Line, LinesSoFar);
51 true ->
52 {Num, LinesSoFar ++ Line}
53 end.
54
55 recv_response(Sock, ResponseNo, LinesSoFar) ->
56 Line = recv_until_EOL(Sock),
57 recv_until_xxxend(Sock, ResponseNo, Line, LinesSoFar).
58
59 recv_response(Sock) ->
60 recv_response(Sock, []).
61
62 send_command(Sock, Msg) ->
63 ok = gen_tcp:send(Sock, Msg ++ "/r/n"),
64 client_message("~s", [Msg]).
65
66 parse_pasv(Msg) ->
67 Index1 = string:chr(Msg, $(),
68 Index2 = string:chr(Msg, $)),
69 [A1, A2, A3, A4, B1, B2] = lists:map(fun(S) -> string:strip(S) end, string:tokens(string:substr(Msg, Index1 + 1, Index2 - Index1 -1), ",")),
70 {Nb1, _} = string:to_integer(B1),
71 {Nb2, _} = string:to_integer(B2),
72 <<Port:16>> = <<Nb1:8, Nb2:8>>,
73 {lists:append([A1, ".", A2, ".", A3, ".", A4]), Port}.
74
75
76 recv_until_transfer_complete(Sock, {ok , Msg}, MsgSoFar) ->
77 if
78 length(Msg) > 0 ->
79 recv_until_transfer_complete(Sock, gen_tcp:recv(Sock, 0), MsgSoFar ++ Msg);
80 true ->
81 receive
82 transfer_complete ->
83 MsgSoFar
84 after 200 ->
85 recv_until_transfer_complete(Sock, gen_tcp:recv(Sock, 0), MsgSoFar ++ Msg)
86 end
87 end;
88
89
90 recv_until_transfer_complete(_, {error , closed}, MsgSoFar) ->
91 MsgSoFar.
92
93 recv_until_transfer_complete(Sock) ->
94 receive
95 start_transfer ->
96 recv_until_transfer_complete(Sock, gen_tcp:recv(Sock, 0), [])
97 after 30000 ->
98 "150 timeout"
99 end.
100
101 get_data(Addr, Port, LocalFilePath, Control) ->
102 {ok, DataSock} = gen_tcp:connect(Addr, Port, [{active, false}]),
103 Data = recv_until_transfer_complete(DataSock),
104 client_message("<I GOT>: ~w Bytes~n", [length(Data)]),
105 file:write_file(LocalFilePath, list_to_binary(Data)),
106 gen_tcp:close(DataSock),
107 Control ! bye.
108
109
110 get(Host, Port, User, Pass, FilePath, LocalPath) ->
111 Slash = string:rchr(FilePath, $/),
112 Path = string:left(FilePath, Slash - 1),
113 File = string:right(FilePath, string:len(FilePath) - Slash),
114
115 {ok, Sock} = gen_tcp:connect(Host, Port, [{active, false}]),
116 client_message("connect to ~s:~w ok.", [Host, Port]),
117 server_verify_message(recv_response(Sock), 220),
118 send_command(Sock, "USER " ++ User),
119 server_verify_message(recv_response(Sock), 331),
120 send_command(Sock, "PASS " ++ Pass),
121 server_verify_message(recv_response(Sock), 230),
122 send_command(Sock, "CWD " ++ Path),
123 server_verify_message(recv_response(Sock), 250),
124 send_command(Sock, "TYPE I"),
125 server_verify_message(recv_response(Sock), 200),
126 send_command(Sock, "PASV"),
127 {_, Msg} = recv_response(Sock),
128 server_message(Msg, ""),
129 {Addr, NewPort} = parse_pasv(Msg),
130
131 DataPid = spawn(ftp_down, get_data, [Addr, NewPort, LocalPath ++ File, self()]),
132
133 send_command(Sock, "RETR " ++ File),
134 % 150 Opening BINARY mode data connection for 150 File status okay; about to open data connection.
135 % 125 Downloading in BINARY file 125 Data connection already open; transfer starting.
136 % 550 Failed to open file
137 {Num, Text} = recv_response(Sock),
138 V = (Num == 150) or (Num == 125),
139 if
140 V ->
141 server_message("~s", [Text]),
142 DataPid ! start_transfer,
143 server_verify_message(recv_response(Sock), 226),
144 DataPid ! transfer_complete,
145 receive
146 bye ->
147 ok
148 end;
149 true ->
150 if Num == 550 ->
151 server_message("~w", Text);
152 true ->
153 ok
154 end
155 end,
156
157 gen_tcp:close(Sock).
浙公网安备 33010602011771号