前言:FTP包是国外Oracle ACE大神Tim Hall创建的工具包,本文用于解析FTP.GET方法的实现原理。该方法可以很便捷的实现从远程目录通过TCP协议下载文件到本地目录。该工具包基于Oracle内置UTL_TCP,DBMS_LOB,UTL_FILE包实现。本文不对内置包进行使用说明,可自行查阅文档。
相关链接:
创建语句-FTP包说明
创建语句-FTP包主体
UTL_TCP包Oracle 11g R2官方文档
UTL_FILE包Oracle 11g R2官方文档
DBMS_LOB包Oracle 11g R2官方文档
FTP.GET语法
ftp.get(
p_conn in out nocopy utl_tcp.connection, --传入一个FTP的登录连接
p_from_file in varchar2, --需要下载的文件路径
p_to_dir in varchar2, --服务器的目录
p_to_file in varchar2) ; --生成的文件名
实例
--通过ftp下载远程服务器文件下载到本地
declare
l_conn UTL_TCP.connection; --定义一个连接类型
r_from_file varchar2(255);
l_blob_len integer;
p_reason varchar2(2555) := null; --定义报错信息,默认为null
v_result varchar2(255) := 0; --定义结果信息
v_sign number(4) := 0; --定义结果状态,默认为0
begin
--使用ftp.login方法创建连接,参数依次为IP/端口/用户名/密码
l_conn := ftp.login('127.0.0.1', '21', 'Oracle', 'test');
begin
--获取远程文件绝对路径
r_from_file := '/data/total/'||to_char(add_months(sysdate,-1),'YYYYMM')||'.AVL';
--获取指定文件字节大小
l_blob_len := dbms_lob.getlength(ftp.get_remote_binary_data(l_conn,r_from_file));
--使用ftp.get下载文件
ftp.get(p_conn => l_conn, --传入连接
p_from_file => r_from_file, --件绝对路径
p_to_dir => 'JX_AUDIT_LINE', ---传入oracle服务器的目录,需确保dba_directories表下存在
p_to_file => 'EPBB_DATA.CSV'); --生成的文件名
--判断文件是否为空,如果为空返回失败
if l_blob_len = 0 then
v_sign := 1;
v_result := '失败';
else
v_result := '成功';
end if;
--获取异常
exception
when others then
--dbms_utility.format_error_backtrace方法用于获取报错的行数,sqlerrm用于获取报错信息
p_reason := dbms_utility.format_error_backtrace || sqlerrm;
v_sign := 1;
v_result := '失败';
end;
ftp.logout(l_conn); --关闭fpt连接
utl_tcp.close_all_connections; --关闭tcp连接
insert into gsc_test values(v_sign, v_result || ',数据大小为:' ||l_blob_len, p_reason);
commit;
end;
--执行日志如下
select * from gsc_test;
sign result reason
1 失败,数据大小为:0 "ORA-06512: at ""IRMJX.FTP"", line 299 ORA-06512:at line 16 ORA-20000: 550 Failed to open file."
0 成功,数据大小为:80071357
方法解析-FTP.LOGIN
该方法用于返回一个TCP的连接
function login(p_host in varchar2, --主机IP
p_port in varchar2, --端口
p_user in varchar2,--用户名
p_pass in varchar2,--密码
p_timeout in number := null --超时时间) return utl_tcp.connection is
l_conn utl_tcp.connection;
begin
--删除集合中所有的内容,之前定义了g_reply为is table of varchar2(32767)的嵌套表集合
g_reply.delete;
--utl_tcp.open_connection用法具体参见UTL_TCP包Oracle 11g R2官方文档
l_conn := utl_tcp.open_connection(p_host,
p_port,
tx_timeout => p_timeout);
--调用get_reply方法判断连接信息是否正常
get_reply(l_conn);
--连接TCP成功发送用户名和密码指令进行登录
send_command(l_conn, 'USER ' || p_user);
send_command(l_conn, 'PASS ' || p_pass);
return l_conn;
end;
方法解析-FTP.GET_REPLY
该方法用于判断TPC连接是否正确,并获取主机回复信息
procedure get_reply(p_conn in out nocopy utl_tcp.connection) is
-- --------------------------------------------------------------------------
l_reply_code varchar2(3) := null;
begin
loop
--给集合g_reply添加一个空元素
g_reply.extend;
--获取连接结果写入到g_reply集合最后一个索引中
g_reply(g_reply.last) := utl_tcp.get_line(p_conn, true);
--不知道干什么用的
debug(g_reply(g_reply.last));
if l_reply_code is null then
--截取TPC连接的前三位状态码例如,220,404写入到l_reply_code 中
l_reply_code := substr(g_reply(g_reply.last), 1, 3);
end if;
--如果状态码的首位为4或者5,代表访问失败
if substr(l_reply_code, 1, 1) in ('4', '5') then
--RAISE_APPLICATION_ERROR函数用于自定义错误类型,并返回给控制台
raise_application_error(-20000, g_reply(g_reply.last));
--如果集合中的最后一个索引等于状态码和g_reply的第四个位置为空格便退出
elsif (substr(g_reply(g_reply.last), 1, 3) = l_reply_code and
substr(g_reply(g_reply.last), 4, 1) = ' ') then
exit;
end if;
end loop;
exception
when utl_tcp.end_of_input then
null;
end;
方法解析- FTP.SEND_COMMAND
该方法用于发送TCP写入指令
procedure send_command(p_conn in out nocopy utl_tcp.connection,
p_command in varchar2,
p_reply in boolean := true) is
l_result pls_integer;
begin
--发送信息
l_result := utl_tcp.write_line(p_conn, p_command);
if p_reply then
--调用get_reply获取主机回复信息
get_reply(p_conn);
end if;
end;
方法解析-GET_REMOTE_BINARY_DATA
该方法用于将文件数据写入到一个临时的blob中
function get_remote_binary_data(p_conn in out nocopy utl_tcp.connection,
p_file in varchar2) return blob is
-- --------------------------------------------------------------------------
l_conn utl_tcp.connection;
l_amount pls_integer;
l_buffer raw(32767);
l_data blob;
begin
--dbms_lob.createtemporary过程用于在用户的默认临时表空间中创建一个临时的BLOB或者CLOB及其对应的索引
dbms_lob.createtemporary(lob_loc => l_data,
cache => true,
dur => dbms_lob.call);
--获取下载文件的端口连接
l_conn := get_passive(p_conn);
--发送RETR 命令下载文件
send_command(p_conn, 'RETR ' || p_file, true);
--
begin
loop
--在打开的连接上从服务接收二进制数据
--utl_tcp.read_raw会返回接受数据的长度,l_buffer是接收到的数据,具体使用方法不做具体描述,请查阅文档
l_amount := utl_tcp.read_raw(l_conn, l_buffer, 32767);
--dbms_lob.writeappend会将读取到的数据写入到blob中
dbms_lob.writeappend(l_data, l_amount, l_buffer);
end loop;
exception
when utl_tcp.end_of_input then
null;
when others then
null;
end;
utl_tcp.close_connection(l_conn);
get_reply(p_conn);
return l_data;
exception
when others then
utl_tcp.close_connection(l_conn);
raise;
end;
方法解析-GET_PASSIVE
该方法用于获取PASV模式返回的端口,打开新的传输连接
function get_passive(p_conn in out nocopy utl_tcp.connection)
return utl_tcp.connection is
-- --------------------------------------------------------------------------
l_conn utl_tcp.connection;
l_reply varchar2(32767);
--l_host VARCHAR(100);
l_port1 number(10);
l_port2 number(10);
begin
--发送指令打开PASV模式传输数据
send_command(p_conn, 'PASV');
--获取远程主机返回的端口列表
--227 Entering Passive Mode (10,239,53,98,180,202).
l_reply := g_reply(g_reply.last);
--替换端口信息的,替换成. 10.239.53.98.180.202
l_reply := replace(substr(l_reply,
instr(l_reply, '(') + 1,
(instr(l_reply, ')')) - (instr(l_reply, '(')) - 1),
',',
'.');
--l_host := SUBSTR(l_reply, 1, INSTR(l_reply, '.', 1, 4)-1);
--截取端口:180
l_port1 := to_number(substr(l_reply,
instr(l_reply, '.', 1, 4) + 1,
(instr(l_reply, '.', 1, 5) - 1) -
(instr(l_reply, '.', 1, 4))));
--截取端口202
l_port2 := to_number(substr(l_reply, instr(l_reply, '.', 1, 5) + 1));
--l_conn := utl_tcp.open_connection(l_host, 256 * l_port1 + l_port2);
--打开传输端口连接46282
l_conn := utl_tcp.open_connection(p_conn.remote_host,
256 * l_port1 + l_port2);
return l_conn;
end;
方法解析-PUT_LOCAL_BINARY_DATA
该方法用于将默认临时表空间中临时的BLOB写入到指定文件中
procedure put_local_binary_data(p_data in blob,
p_dir in varchar2,
p_file in varchar2) is
-- --------------------------------------------------------------------------
l_out_file utl_file.file_type;
l_buffer raw(32767);
l_amount binary_integer := 32767;
l_pos integer := 1;
l_blob_len integer;
begin
--获取临时文件的长度
l_blob_len := dbms_lob.getlength(p_data);
--打开要下载到的文件,'wb'代表写入字节模式
l_out_file := utl_file.fopen(p_dir, p_file, 'wb', 32767);
--循环写入数据
while l_pos <= l_blob_len loop
--读取p_data的数据
dbms_lob.read(p_data, l_amount, l_pos, l_buffer);
--写入数据到文件
utl_file.put_raw(l_out_file, l_buffer, true);
--刷新文件信息
utl_file.fflush(l_out_file);
--计算写入数据值
l_pos := l_pos + l_amount;
end loop;
--关闭文件
utl_file.fclose(l_out_file);
exception
when others then
if utl_file.is_open(l_out_file) then
utl_file.fclose(l_out_file);
end if;
raise;
end;
最终控制台输出如下
220 "Authorized users only. All activity may be monitored and reported"
331 Please specify the password.
230 Login successful.
227 Entering Passive Mode (10,239,53,98,180,202).
150 Opening BINARY mode data connection for /irms/data/.AVL (80071357 bytes).
226 Transfer complete.
227 Entering Passive Mode (10,239,53,98,171,105).
150 Opening BINARY mode data connection for /irms/data/.AVL (80071357 bytes).
226 Transfer complete.
221 Goodbye.