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 allimportant 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 Javasupported 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 -- Functions to receive data from a socket. These return -- Null on eof. They will block waiting for data. If -- this is not desirable, use PEEK below to see if there -- is any data to read. member function recv return varchar2, member function recv_raw return raw, -- Convienence function. Reads data until a CRLF is found. -- Can strip the CRLF if you like (or not, by default). member function getline( p_remove_crlf in boolean default FALSE ) return varchar2, -- Procedures to connect to a host and disconnect from a host. -- It is important to disconnect, else you will leak resources -- and eventually will not be able to connect. member procedure initiate_connection( p_hostname in varchar2, p_portno in number ), member procedure close_connection, -- Function to tell you how many bytes (at least) might be -- ready to be read. member function peek return number ); / 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 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 p_maxlength in number default -1 ); -- Gets a line of data from the input socket. That is, data -- up to a \n. procedure getline( p_sock in number, p_data out raw ); -- Disconnects from a server you have connected to. procedure disconnect( p_sock in number ); -- Gets the server time in GMT in the format yyyyMMdd HHmmss z procedure get_gmt( p_gmt out varchar2 ); -- Gets the server's timezone. Useful for some Internet protocols. procedure get_timezone( p_timezone out varchar2 ); -- Gets the hostname of the server you are running on. Again, -- useful for some Internet protocols. procedure get_hostname( p_hostname out varchar2 ); -- Returns the number of bytes available to be read. function peek( p_sock in number ) return number; -- base64 encodes a RAW. Useful for sending e-mail -- attachments or doing HTTP which needs the user/password -- to be obscured using base64 encoding. procedure b64encode( p_data in raw, p_result out varchar2 ); end; / 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 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 13 static Socket 14 static DateFormat 15 static DateFormat 16 17 static BASE64Encoder 18 replace and compile JAVA SOURCE socketUsed[] = { 0,0,0,0,0,0,0,0,0,0 }; sockets[] = new Socket[socketUsed.length]; tzDateFormat = new SimpleDateFormat( "z" ); gmtDateFormat = new SimpleDateFormat( "yyyyMMdd HHmmss z" ); encoder = new BASE64Encoder(); 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 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static public int java_connect_to( String p_hostname, int p_portno ) throws java.io.IOException { int i; for( i = 0; i < socketUsed.length && socketUsed[i] == 1; i++ ); if ( i < socketUsed.length ) { sockets[i] = new Socket( p_hostname, p_portno ); socketUsed[i] = 1; } return i<socketUsed.length?i:-1; } 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 35 36 37 38 39 40 static public void java_send_data( int p_sock, byte[] p_data ) throws java.io.IOException { (sockets[p_sock].getOutputStream()).write( p_data ); } static public void java_recv_data( int p_sock, 1248 5254appAQ.pdf 5 2/28/2005 6:50:07 PM UTL_TCP 41 42 43 44 45 46 47 byte[][] p_data, int[] p_length) throws java.io.IOException { p_data[0] = new byte[p_length[0] == -1 ? 4096:p_length[0] ]; p_length[0] = (sockets[p_sock].getInputStream()).read( p_data[0] ); } 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 49 50 51 52 53 54 55 56 static public void java_getline( int p_sock, String[] p_data ) throws java.io.IOException { DataInputStream d = new DataInputStream((sockets[p_sock].getInputStream())); p_data[0] = d.readLine(); if ( p_data[0] != null ) p_data[0] += "\n"; } 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 58 59 60 61 62 63 static public void java_disconnect( int p_sock ) throws java.io.IOException { socketUsed[p_sock] = 0; (sockets[p_sock]).close(); } 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 65 66 67 68 69 static public int java_peek_sock( int p_sock ) throws java.io.IOException { return (sockets[p_sock].getInputStream()).available(); } 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 71 72 73 74 75 76 77 78 79 80 81 82 static public void java_get_timezone( String[] p_timezone ) { tzDateFormat.setTimeZone( TimeZone.getDefault() ); p_timezone[0] = tzDateFormat.format(new Date()); } static public void java_get_gmt( String[] p_gmt ) { gmtDateFormat.setTimeZone( TimeZone.getTimeZone("GMT") ); p_gmt[0] = gmtDateFormat.format(new Date()); } 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 84 85 86 87 static public void b64encode( byte[] p_data, String[] p_b64data ) { p_b64data[0] = encoder.encode( p_data ); } 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 89 90 91 92 93 94 95 static public void java_get_hostname( String[] p_hostname ) throws java.net.UnknownHostException { p_hostname[0] = (InetAddress.getLocalHost()).getHostName(); } } / 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 41 static public void java_recv_data( int p_sock, 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 36 37 38 39 40 41 42 43 44 45 46 47 procedure getline_i( p_sock in number, p_data out varchar2 ) as language java name 'jsock.java_getline( int, java.lang.String[] )'; procedure getline( p_sock in number, p_data out raw ) as l_data long; begin getline_i( p_sock, l_data ); p_data := utl_raw.cast_to_raw( l_data ); 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 procedure disconnect( p_sock in number ) as language java name 'jsock.java_disconnect( int )'; procedure get_gmt( p_gmt out varchar2 ) as language java name 'jsock.java_get_gmt( java.lang.String[] )'; procedure get_timezone( p_timezone out varchar2 ) as language java name 'jsock.java_get_timezone( java.lang.String[] )'; procedure get_hostname( p_hostname out varchar2 ) as language java name 'jsock.java_get_hostname( java.lang.String[] )'; function peek( p_sock in number ) return number as language java name 'jsock.java_peek_sock( int ) return int'; procedure b64encode( p_data in raw, p_result out varchar2 ) as language java name 'jsock.b64encode( byte[], java.lang.String[] )'; end; / 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 31 32 33 34 35 36 37 38 39 40 41 42 is l_offset number default 1; l_length number default dbms_lob.getlength(p_data); l_amt number default 4096; begin loop exit when l_offset > l_length; simple_tcp_client.send( g_sock, utl_raw.cast_to_raw( dbms_lob.substr(p_data,l_amt,l_offset) ) ); l_offset := l_offset + l_amt; end loop; 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 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 72 73 74 75 76 77 78 79 member procedure send_raw( p_data in blob ) is l_offset number default 1; l_length number default dbms_lob.getlength(p_data); l_amt number default 4096; begin loop exit when l_offset > l_length; simple_tcp_client.send( g_sock, dbms_lob.substr(p_data,l_amt,l_offset) ); l_offset := l_offset + l_amt; end loop; end; member function recv return varchar2 is l_raw_data raw(4096); begin simple_tcp_client.recv( g_sock, l_raw_data ); return utl_raw.cast_to_varchar2(l_raw_data); end; member function recv_raw return raw is l_raw_data raw(4096); begin simple_tcp_client.recv( g_sock, l_raw_data ); return l_raw_data; end; member function getline( p_remove_crlf in boolean default FALSE ) return varchar2 is l_raw_data raw(4096); begin 1254 5254appAQ.pdf 11 2/28/2005 6:50:08 PM UTL_TCP 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 simple_tcp_client.getline( g_sock, l_raw_data ); if ( p_remove_crlf ) then return rtrim( utl_raw.cast_to_varchar2(l_raw_data), SocketType.crlf ); else return utl_raw.cast_to_varchar2(l_raw_data); end if; end; member procedure initiate_connection( p_hostname in varchar2, p_portno in number ) is l_data varchar2(4069); begin -- we try to connect 10 times and if the tenth time -- fails, we reraise the exception to the caller for i in 1 .. 10 loop begin g_sock := simple_tcp_client.connect_to( p_hostname, p_portno ); exit; exception when others then if ( i = 10 ) then raise; end if; end; end loop; 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 109 110 111 112 113 114 115 116 member procedure close_connection is begin simple_tcp_client.disconnect( g_sock ); g_sock := NULL; end; end; / 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