You are on page 1of 21

Appendix A

UTL_TCP

Oracle 8.1.6 introduced for the first time, the UTL_TCP package. This package allows PL/SQL to open a
network socket connection over TCP/IP to any server accepting connections. Assuming you know the
protocol of a server, you can now 'talk' to it from PL/SQL. For example, given that I know HTTP
(Hyper Text Transfer Protocol), I can code in UTL_TCP the following:

test_jsock@DEV816> DECLARE
2 c utl_tcp.connection; -- TCP/IP connection to the web server
3 n number;
4 buffer varchar2(255);
5 BEGIN
6 c := utl_tcp.open_connection('proxy-server', 80);
7 n := utl_tcp.write_line(c, 'GET http://www.apress.com/ HTTP/1.0');
8 n := utl_tcp.write_line(c);
9 BEGIN
10 LOOP
11 n:=utl_tcp.read_text( c, buffer, 255 );
12 dbms_output.put_line( buffer );
13 END LOOP;
14 EXCEPTION
15 WHEN utl_tcp.end_of_input THEN
16 NULL; -- end of input
17 end;
18 utl_tcp.close_connection(c);
19 END;
20 /
HTTP/1.1 200 OK
Date: Tue, 30 Jan 2001 11:33:50 GMT
Server: Apache/1.3.9 (Unix) mod_perl/1.21
ApacheJServ/1.1
Content-Type: text/html

<head>
<title>Oracle
Corporation</title>

1244

5254appAQ.pdf 1 2/28/2005 6:50:07 PM


UTL_TCP

This lets me open a connection to a server, in this case a proxy server named proxy-server. It lets me
get through our firewall to the outside Internet. This happens on line 6. I then request a web page on
lines 7 and 8. On lines 10 through to 13, we receive the contents of the web page, including the all-
important HTTP headers (something UTL_HTTP, another supplied package, won't share with us) and
print it. When UTL_TCP throws the UTL_TCP.END_OF_INPUT exception, we are done, and we break out
of the loop. We then close our connection and that's it.

This simple example demonstrates a majority of the functionality found in the UTL_TCP package. We
didn't see functions such as AVAILABLE, which tells us if any data is ready to be received. We skipped
FLUSH, which causes any buffered output to be transmitted (we didn't use buffering, hence did not need
this call). Likewise, we did not use every variation of READ, WRITE, and GET to put and get data on the
socket, but the example above shows how to use UTL_TCP fairly completely.

The one thing I don't necessarily like about the above is the speed at which it runs. It is in my
experience that UTL_TCP, while functional, is not as high performing as it could be in this release
(Oracle 8i). In Oracle 8.1.7.1, this performance issue is fixed (bug #1570972 corrects this issue).

So, how slow is slow? The above code, to retrieve a 16 KB document, takes anywhere from four to ten
seconds, depending on platform. This is especially bad considering the native UTL_HTTP function can
do the same operation with a sub-second response time. Unfortunately, UTL_HTTP doesn't permit access
to cookies, HTTP headers, binary data, basic authentication, and the like, so using an alternative is
many times useful. I think we can do better. To this end, we will implement our own UTL_TCP package.
However, we will use the Object Type metaphor we discussed in Chapter 20 on Using Object Relational
Features. What we will do is to implement a SocketType in PL/SQL with some of the underlying 'guts'
in Java. In the UTL_HTTP section, we put this SocketType we created to use in building a better
UTL_HTTP package for ourselves as well. Since our functionality will be modeled after the functionality
available in the UTL_TCP package, when Oracle9i is released with native and faster UTL_TCP support,
we can easily re-implement our type body using the real UTL_TCP package, and stop using our Java-
supported one.

The SocketType
Our SocketType Object Type will use the following specification:

tkyte@TKYTE816> create or replace type SocketType


2 as object
3 (
4 -- 'Private data', rather than you
5 -- passing a context to each procedure, like you
6 -- do with UTL_FILE.
7 g_sock number,
8
9 -- A function to return a CRLF. Just a convenience.
10 static function crlf return varchar2,
11
12 -- Procedures to send data over a socket.
13 member procedure send( p_data in varchar2 ),
14 member procedure send( p_data in clob ),
15
16 member procedure send_raw( p_data in raw ),
17 member procedure send_raw( p_data in blob ),

1245

5254appAQ.pdf 2 2/28/2005 6:50:07 PM


Appendix A

18
19 -- Functions to receive data from a socket. These return
20 -- Null on eof. They will block waiting for data. If
21 -- this is not desirable, use PEEK below to see if there
22 -- is any data to read.
23 member function recv return varchar2,
24 member function recv_raw return raw,
25
26 -- Convienence function. Reads data until a CRLF is found.
27 -- Can strip the CRLF if you like (or not, by default).
28 member function getline( p_remove_crlf in boolean default FALSE )
29 return varchar2,
30
31 -- Procedures to connect to a host and disconnect from a host.
32 -- It is important to disconnect, else you will leak resources
33 -- and eventually will not be able to connect.
34 member procedure initiate_connection( p_hostname in varchar2,
35 p_portno in number ),
36 member procedure close_connection,
37
38 -- Function to tell you how many bytes (at least) might be
39 -- ready to be read.
40 member function peek return number
41 );
42 /

Type created.

This set of functionality is pretty much modeled after the UTL_TCP package, and provides much of the
same interface. In fact, it could be implemented on top of that package if you wanted. We are going to
implement it on top of a different package however, one which I call the SIMPLE_TCP_CLIENT. This is
a ordinary PL/SQL package that the SocketType will be built on. This is really our specification of a
UTL_TCP package:

tkyte@TKYTE816> CREATE OR REPLACE PACKAGE simple_tcp_client


2 as
3 -- A function to connect to a host. Returns a 'socket',
4 -- which is really just a number.
5 function connect_to( p_hostname in varchar2,
6 p_portno in number ) return number;
7
8 -- Send data. We only know how to send RAW data here. Callers
9 -- must cast VARCHAR2 data to RAW. At the lowest level, all
10 -- data on a socket is really just 'bytes'.
11
12 procedure send( p_sock in number,
13 p_data in raw );
14
15 -- recv will receive data.
16 -- If maxlength is -1, we try for 4k of data. If maxlength
17 -- is set to anything OTHER than -1, we attempt to
18 -- read up to the length of p_data bytes. In other words,
19 -- I restrict the receives to 4k unless otherwise told not to.
20 procedure recv( p_sock in number,
21 p_data out raw,

1246

5254appAQ.pdf 3 2/28/2005 6:50:07 PM


UTL_TCP

22 p_maxlength in number default -1 );


23
24 -- Gets a line of data from the input socket. That is, data
25 -- up to a \n.
26 procedure getline( p_sock in number,
27 p_data out raw );
28
29
30 -- Disconnects from a server you have connected to.
31 procedure disconnect( p_sock in number );
32
33 -- Gets the server time in GMT in the format yyyyMMdd HHmmss z
34 procedure get_gmt( p_gmt out varchar2 );
35
36 -- Gets the server's timezone. Useful for some Internet protocols.
37 procedure get_timezone( p_timezone out varchar2 );
38
39 -- Gets the hostname of the server you are running on. Again,
40 -- useful for some Internet protocols.
41 procedure get_hostname( p_hostname out varchar2 );
42
43 -- Returns the number of bytes available to be read.
44 function peek( p_sock in number ) return number;
45
46 -- base64 encodes a RAW. Useful for sending e-mail
47 -- attachments or doing HTTP which needs the user/password
48 -- to be obscured using base64 encoding.
49 procedure b64encode( p_data in raw, p_result out varchar2 );
50 end;
51 /

Package created.

Now, as none of these functions can actually be written in PL/SQL, we will implement them in Java
instead. The Java for doing this is surprisingly small. The entire script is only 94 lines long. We are
using the native Socket class for Java, and will maintain a small array of them, allowing PL/SQL to
have up to ten connections open simultaneously. If you would like more than ten, just make the
socketUsed array larger in the code below. I've tried to keep this as simple, and as small as possible,
preferring to do the bulk of any work in PL/SQL. I'll present the small class we need, and then
comment on it:

tkyte@TKYTE816> set define off

tkyte@TKYTE816> CREATE or replace and compile JAVA SOURCE


2 NAMED "jsock"
3 AS
4 import java.net.*;
5 import java.io.*;
6 import java.util.*;
7 import java.text.*;
8 import sun.misc.*;
9
10 public class jsock
11 {
12 static int socketUsed[] = { 0,0,0,0,0,0,0,0,0,0 };
13 static Socket sockets[] = new Socket[socketUsed.length];
14 static DateFormat tzDateFormat = new SimpleDateFormat( "z" );
15 static DateFormat gmtDateFormat =
16 new SimpleDateFormat( "yyyyMMdd HHmmss z" );
17 static BASE64Encoder encoder = new BASE64Encoder();
18

1247

5254appAQ.pdf 4 2/28/2005 6:50:07 PM


Appendix A

This class has some static variables the two arrays, socketUsed and sockets are the main ones.
When returns are called from PL/SQL, we must return to it something it can send to us on subsequent
calls, to identify the socket connection it wants to use. We cannot return the Java Socket class to
PL/SQL, so I am using an array in which to store them, and will return to PL/SQL an index into that
array. If you look at the java_connect_to method, it looks in the socketsUsed array for an empty
slot, and allocates this to the connection. That index into socketsUsed is what PL/SQL will see. We
use this in the remaining sockets routines to access the actual Java class that represents a socket.

The other static variables are there for reasons of performance. I needed some date format objects, and
rather than NEW them each time you call java_get_gmt or java_get_timezone, I allocate them
once, and just reuse them. Lastly is the base 64 encoder object. For the same reason I allocate the date
formatter objects, I allocate the encoder.

Now for the routine that connects over TCP/IP, to a server. This logic loops over the socketUsed
array looking for an empty slot (where socketUsed[I] is not set to 1). If it finds one, it uses the Java
Socket class to create a connection to the host/port combination that was passed in, and sets the
socketUsed flag for the array slot to 1. It then returns a -1 on error (no empty slots), or a non-negative
number upon success:

19 static public int java_connect_to( String p_hostname, int p_portno )


20 throws java.io.IOException
21 {
22 int i;
23
24 for( i = 0; i < socketUsed.length && socketUsed[i] == 1; i++ );
25 if ( i < socketUsed.length )
26 {
27 sockets[i] = new Socket( p_hostname, p_portno );
28 socketUsed[i] = 1;
29 }
30 return i<socketUsed.length?i:-1;
31 }
32
33

The next routines are the two most frequently called Java routines. They are responsible for sending and
receiving data on a connected TCP/IP socket. The java_send_data routine is straightforward it
simply gets the output stream associated with the socket, and writes the data. The java_recv_data is
slightly more complex. It uses OUT parameters, hence the use of int[] p_length for example, in order
to return data. This routine inspects the length that was sent in by the caller, and if the length was -1, it
will allocate a 4 KB buffer to read into, else it will allocate a buffer of the size specified. It will then try
to read that much data from the socket. The actual amount of data read (which will be less than or equal
to the amount requested) is placed in p_length as a return value:

34 static public void java_send_data( int p_sock, byte[] p_data )


35 throws java.io.IOException
36 {
37 (sockets[p_sock].getOutputStream()).write( p_data );
38 }
39
40 static public void java_recv_data( int p_sock,

1248

5254appAQ.pdf 5 2/28/2005 6:50:07 PM


UTL_TCP

41 byte[][] p_data, int[] p_length)


42 throws java.io.IOException
43 {
44 p_data[0] = new byte[p_length[0] == -1 ? 4096:p_length[0] ];
45 p_length[0] = (sockets[p_sock].getInputStream()).read( p_data[0] );
46 }
47

java_getline is a convenience function. Many Internet protocols respond to operations 'a line at a
time', and being able to get a simple line of text is very handy. For example, the headers sent back in
the HTTP protocol are simply lines of ASCII text. This routine works by using the
DataInputStream.readLine method, and if a line of text is read in, it will return it (putting the new
line, which readLine strips off, back on). Otherwise, the data will be returned as Null:

48 static public void java_getline( int p_sock, String[] p_data )


49 throws java.io.IOException
50 {
51 DataInputStream d =
52 new DataInputStream((sockets[p_sock].getInputStream()));
53 p_data[0] = d.readLine();
54 if ( p_data[0] != null ) p_data[0] += "\n";
55 }
56

java_disconnect is very straightforward as well. It simply sets the socketUsed array flag back to
zero, indicating we can reuse this slot in the array, and closes the socket down for us:

57 static public void java_disconnect( int p_sock )


58 throws java.io.IOException
59 {
60 socketUsed[p_sock] = 0;
61 (sockets[p_sock]).close();
62 }
63

The java_peek_sock routine is used to see if data on a socket is available to be read. This is useful for
times when the client does not want to block on a receive of data. If you look to see if anything is
available, you can tell if a receive will block, or return right away:

64 static public int java_peek_sock( int p_sock )


65 throws java.io.IOException
66 {
67 return (sockets[p_sock].getInputStream()).available();
68 }
69

Now we have our two time functions. java_get_timezone is used to return the time zone of the
database server. This is particularly useful if you need to convert an Oracle DATE from one time zone to
another using the NEW_TIME built-in function, or if you just need know the time zone in which the
server is operating. The second function, java_get_gmt, is useful for getting the server's current date
and time in GMT (Greenwich Mean Time):

1249

5254appAQ.pdf 6 2/28/2005 6:50:07 PM


Appendix A

70 static public void java_get_timezone( String[] p_timezone )


71 {
72 tzDateFormat.setTimeZone( TimeZone.getDefault() );
73 p_timezone[0] = tzDateFormat.format(new Date());
74 }
75
76
77 static public void java_get_gmt( String[] p_gmt )
78 {
79 gmtDateFormat.setTimeZone( TimeZone.getTimeZone("GMT") );
80 p_gmt[0] = gmtDateFormat.format(new Date());
81 }
82

The b64encode routine will base 64 encode a string of data. Base 64 encoding is an Internet-standard
method of encoding arbitrary data into a 7bit ASCII format, suitable for transmission. We will use this
function in particular when implementing our HTTP package, as it will support basic authentication
(used by many web sites that require you to log in via a username and password).

83 static public void b64encode( byte[] p_data, String[] p_b64data )


84 {
85 p_b64data[0] = encoder.encode( p_data );
86 }
87

The last routine in this class simply returns the hostname of the database server. Some Internet
protocols request that you transmit this information (for example, SMTP simple mail transfer
protocol):

88 static public void java_get_hostname( String[] p_hostname )


89 throws java.net.UnknownHostException
90 {
91 p_hostname[0] = (InetAddress.getLocalHost()).getHostName();
92 }
93
94 }
95 /

Java created.

The Java methods themselves are rather straightforward. If you recall from Chapter 19 on Java Stored
Procedures, in order to get OUT parameters, we must send, what appears to be an array, to Java. Hence,
most of the procedures above take the form of:

40 static public void java_recv_data( int p_sock,


41 byte[][] p_data, int[] p_length)

This allows me to return a value in p_data, and return a value in p_length. Now that we have our
Java class, we are ready to build our package body for the SIMPLE_TCP_CLIENT package. It consists
almost entirely of bindings to Java:

1250

5254appAQ.pdf 7 2/28/2005 6:50:07 PM


UTL_TCP

tkyte@TKYTE816> CREATE OR REPLACE PACKAGE BODY simple_tcp_client


2 as
3
4 function connect_to( p_hostname in varchar2,
5 p_portno in number ) return number
6 as language java
7 name 'jsock.java_connect_to( java.lang.String, int ) return int';
8
9
10 procedure send( p_sock in number, p_data in raw )
11 as language java
12 name 'jsock.java_send_data( int, byte[] )';
13
14 procedure recv_i ( p_sock in number,
15 p_data out raw,
16 p_maxlength in out number )
17 as language java
18 name 'jsock.java_recv_data( int, byte[][], int[] )';
19
20 procedure recv( p_sock in number,
21 p_data out raw,
22 p_maxlength in number default -1 )
23 is
24 l_maxlength number default p_maxlength;
25 begin
26 recv_i( p_sock, p_data, l_maxlength );
27 if ( l_maxlength <> -1 )
28 then
29 p_data := utl_raw.substr( p_data, 1, l_maxlength );
30 else
31 p_data := NULL;
32 end if;
33 end;

Here, I have a RECV_I and a RECV procedure. RECV_I is a private procedure (the _I stands for
internal), not directly callable out of this package. It is called by RECV. RECV provides a 'friendly'
internal on top of RECV_I it checks to see if any data was read from the socket and if so, it sets the
length correctly. If you recall from the Java code above, we allocated a fixed size buffer in the RECV
routine, and read up to that many bytes from the socket. We need to resize our buffer to be exactly that
size here, and this is the purpose of the UTL_RAW.SUBSTR function. Otherwise, if no data was read, we
simply return Null.

34
35 procedure getline_i( p_sock in number,
36 p_data out varchar2 )
37 as language java
38 name 'jsock.java_getline( int, java.lang.String[] )';
39
40 procedure getline( p_sock in number,
41 p_data out raw )
42 as
43 l_data long;
44 begin
45 getline_i( p_sock, l_data );
46 p_data := utl_raw.cast_to_raw( l_data );
47 end getline;

1251

5254appAQ.pdf 8 2/28/2005 6:50:07 PM


Appendix A

Again, much like RECV_I/RECV above, GETLINE_I is an internal function called only by GETLINE. The
external PL/SQL interface exposes all data as the RAW type, and the GETLINE function here simply
converts the VARCHAR2 data into a RAW for us.

48
49 procedure disconnect( p_sock in number )
50 as language java
51 name 'jsock.java_disconnect( int )';
52
53 procedure get_gmt( p_gmt out varchar2 )
54 as language java
55 name 'jsock.java_get_gmt( java.lang.String[] )';
56
57 procedure get_timezone( p_timezone out varchar2 )
58 as language java
59 name 'jsock.java_get_timezone( java.lang.String[] )';
60
61 procedure get_hostname( p_hostname out varchar2 )
62 as language java
63 name 'jsock.java_get_hostname( java.lang.String[] )';
64
65 function peek( p_sock in number ) return number
66 as language java
67 name 'jsock.java_peek_sock( int ) return int';
68
69 procedure b64encode( p_data in raw, p_result out varchar2 )
70 as language java
71 name 'jsock.b64encode( byte[], java.lang.String[] )';
72 end;
73 /

Package body created.

We are now ready to test some of our functions to see that they are installed, and actually work:

tkyte@TKYTE816> declare
2 l_hostname varchar2(255);
3 l_gmt varchar2(255);
4 l_tz varchar2(255);
5 begin
6 simple_tcp_client.get_hostname( l_hostname );
7 simple_tcp_client.get_gmt( l_gmt );
8 simple_tcp_client.get_timezone( l_tz );
9
10 dbms_output.put_line( 'hostname ' || l_hostname );
11 dbms_output.put_line( 'gmt time ' || l_gmt );
12 dbms_output.put_line( 'timezone ' || l_tz );
13 end;
14 /
hostname tkyte-dell
gmt time 20010131 213415 GMT
timezone EST

PL/SQL procedure successfully completed.

1252

5254appAQ.pdf 9 2/28/2005 6:50:08 PM


UTL_TCP

An important point for running the TCP/IP components of this package is that we need special
permission to use TCP/IP in the database. For more information on the DBMS_JAVA package and
privileges associated with Java, please see the DBMS_JAVA section in this appendix. In this case, we
specifically we need to execute:

sys@TKYTE816> begin
2 dbms_java.grant_permission(
3 grantee => 'TKYTE',
4 permission_type => 'java.net.SocketPermission',
5 permission_name => '*',
6 permission_action => 'connect,resolve' );
7 end;
8 /

PL/SQL procedure successfully completed.

Refer to the section on DBMS_JAVA for more details on what, and how, this procedure works. In a
nutshell, it allows the user TKYTE to create connections and resolve hostnames to IP addresses to any
host (that's the '*' above). If you are using Oracle 8.1.5, you will not have the DBMS_JAVA package.
Rather, in this version you would grant the JAVASYSPRIV to the owner of jsock. You should be aware
that the JAVASYSPRIV is a very 'broad' privilege. Whereas DBMS_JAVA.GRANT_PERMISSION is very
granular, JAVASYSPRIV is very broad, and conveys a lot of privileges at once. Now that I have this
permission, we are ready to implement and test our SocketType, similar to the way we tested in which
we tested UTL_TCP initially. Here is the body of SocketType. The type body contains very little actual
code, and is mostly a layer on the SIMPLE_TCP_CLIENT package we just created. It hides the 'socket'
from the caller:

tkyte@TKYTE816> create or replace type body SocketType


2 as
3
4 static function crlf return varchar2
5 is
6 begin
7 return chr(13)||chr(10);
8 end;
9
10 member function peek return number
11 is
12 begin
13 return simple_tcp_client.peek( g_sock );
14 end;
15
16
17 member procedure send( p_data in varchar2 )
18 is
19 begin
20 simple_tcp_client.send( g_sock, utl_raw.cast_to_raw(p_data) );
21 end;
22
23 member procedure send_raw( p_data in raw )
24 is
25 begin
26 simple_tcp_client.send( g_sock, p_data );
27 end;
28
29 member procedure send( p_data in clob )

1253

5254appAQ.pdf 10 2/28/2005 6:50:08 PM


Appendix A

30 is
31 l_offset number default 1;
32 l_length number default dbms_lob.getlength(p_data);
33 l_amt number default 4096;
34 begin
35 loop
36 exit when l_offset > l_length;
37 simple_tcp_client.send( g_sock,
38 utl_raw.cast_to_raw(
39 dbms_lob.substr(p_data,l_amt,l_offset) ) );
40 l_offset := l_offset + l_amt;
41 end loop;
42 end;

The SEND routine is overloaded for various data types, and takes a CLOB of arbitrary length. It will
break the CLOB into 4 KB chunks for transmission. The SEND_RAW routine below is similar, but
performs the operation for a BLOB:

43
44 member procedure send_raw( p_data in blob )
45 is
46 l_offset number default 1;
47 l_length number default dbms_lob.getlength(p_data);
48 l_amt number default 4096;
49 begin
50 loop
51 exit when l_offset > l_length;
52 simple_tcp_client.send( g_sock,
53 dbms_lob.substr(p_data,l_amt,l_offset) );
54 l_offset := l_offset + l_amt;
55 end loop;
56 end;
57
58 member function recv return varchar2
59 is
60 l_raw_data raw(4096);
61 begin
62 simple_tcp_client.recv( g_sock, l_raw_data );
63 return utl_raw.cast_to_varchar2(l_raw_data);
64 end;
65
66
67 member function recv_raw return raw
68 is
69 l_raw_data raw(4096);
70 begin
71 simple_tcp_client.recv( g_sock, l_raw_data );
72 return l_raw_data;
73 end;
74
75 member function getline( p_remove_crlf in boolean default FALSE )
76 return varchar2
77 is
78 l_raw_data raw(4096);
79 begin

1254

5254appAQ.pdf 11 2/28/2005 6:50:08 PM


UTL_TCP

80 simple_tcp_client.getline( g_sock, l_raw_data );


81
82 if ( p_remove_crlf ) then
83 return rtrim(
84 utl_raw.cast_to_varchar2(l_raw_data), SocketType.crlf );
85 else
86 return utl_raw.cast_to_varchar2(l_raw_data);
87 end if;
88 end;
89
90 member procedure initiate_connection( p_hostname in varchar2,
91 p_portno in number )
92 is
93 l_data varchar2(4069);
94 begin
95 -- we try to connect 10 times and if the tenth time
96 -- fails, we reraise the exception to the caller
97 for i in 1 .. 10 loop
98 begin
99 g_sock := simple_tcp_client.connect_to( p_hostname, p_portno );
100 exit;
101 exception
102 when others then
103 if ( i = 10 ) then raise; end if;
104 end;
105 end loop;
106 end;

We try the connection ten times in order to avoid issues with 'server busy' type messages. It is not
entirely necessary, but makes it so the caller doesn't get errors as often as it otherwise might on a busy
web server, or some other service.

107
108 member procedure close_connection
109 is
110 begin
111 simple_tcp_client.disconnect( g_sock );
112 g_sock := NULL;
113 end;
114
115 end;
116 /

Type body created.

As you can see, these are mostly convenience routines layered on top of SIMPLE_TCP_CLIENT to make
this package easier to use. It also serves as a nice way to encapsulate the functionality of the
SIMPLE_TCP_CLIENT in an object type. Using SocketType instead of UTL_TCP, our simple 'get a web
page via a proxy' routine looks like this:

1255

5254appAQ.pdf 12 2/28/2005 6:50:08 PM


Appendix A

tkyte@TKYTE816> declare
2 s SocketType := SocketType(null);
3 buffer varchar2(4096);
4 BEGIN
5 s.initiate_connection( 'proxy-server', 80 );
6 s.send( 'GET http://www.oracle.com/ HTTP/1.0'||SocketType.CRLF);
7 s.send( SocketType.CRLF);
8
9 loop
10 buffer := s.recv;
11 exit when buffer is null;
12 dbms_output.put_line( substr( buffer,1,255 ) );
13 end loop;
14 s.close_connection;
15 END;
16 /
HTTP/1.1 200 OK
Date: Thu, 01 Feb 2001 00:16:05 GMT
Server: Apache/1.3.9 (Unix) mod_perl/1.21
ApacheJServ/1.1
yyyyyyyyyy: close
Content-Type: text/html

<head>
<title>Oracle Corporation</title>

This code is not radically different from using UTL_TCP directly, but it does show how encapsulating
your packages with an Object Type can add a nice feeling of object-oriented programming to your
PL/SQL. If you are a Java or C++ programmer, you feel very comfortable with the above code,
declaring a variable of type SocketType and then calling methods against that type. This is as opposed
to declaring a variable of some record type that you pass down to each routine as UTL_TCP does. The
above is more object-oriented than the procedural method first shown.

Summary
In this section we looked at the new functionality provided by the UTL_TCP package. We also
investigated an alternative implementation in Java. Additionally, we packaged this functionality into a
new Object Type for PL/SQL, fully encapsulating the capabilities of the TCP/IP socket nicely. We saw
how easy it is to integrate networking functionality into our PL/SQL applications using this facility, and
in an earlier section on UTL_HTTP, we saw how we can make use of this to provide full access to the
HTTP protocol.

1256

5254appAQ.pdf 13 2/28/2005 6:50:08 PM


UTL_TCP

1257

5254appAQ.pdf 14 2/28/2005 6:50:08 PM

You might also like