查看“核心技术Ⅱ:网络”的源代码
←
核心技术Ⅱ:网络
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:JavaCore]] == 实现服务端 == === 手动实现:telnet === telnet 是一种用于网络编程的调试工具,可以在命令shell中输入telnet来启动它。 * 在Windows中,需要激活telnet:“控制面板”->“程序”->“打开/关闭Windows特性”->“Telnet客户端”。 如: <syntaxhighlight lang="bash"> telnet time-a.nist.gov 13 # 用time-a.nist.gov在端口13上建立telnet会话 </syntaxhighlight> [[File:连接到服务器端口的客户端.png|600px]] === Java实现:socket === <syntaxhighlight lang="java"> import java.io.*; import java.net.*; import java.util.*; public class SocketTest lS { public static void main(String[) args) throwsIOException { try(Socket s = new Socket(”time-a.nist.gov”,13); Scanner in = new Scanner(s.getInputStream(),”UTF-8'’)) { while (in.hasNextline()) { String line = in.nextline(); System.out.println(line); } } } </syntaxhighlight> 如果连接失败,它将抛出一个UnknownHostException异常;如果存在其他问题,它将抛出一个IOException异常。<br/> (<big>[[网络基础]]</big>) ==== 相关方法 ==== jva.net.Socket 1.0 * Socket(String host, int port) *: 构建一个套接字,用来连接给定的主机和端口。 * InputStream getlnputStream() * OutputStream getOutputStream() *: 获取可以从套接字中读取数据的流,以及可以向套接字写出数据的流。 === 套接字超时 === 从套接字读取信息时,在有数据可供访问之前,读操作将会被阻塞。对于不同的应用,应该确定合理的超时值。 # 调用“setSoTimeout”方法设置超时值(单位:毫秒): #: <syntaxhighlight lang="java"> Socket s = new Socket(. . .); s.setSoTimeout(lOOOO); // time out after 10 seconds </syntaxhighlight> # 捕获“SocketTimeoutException”异常: #: 如果已经为套接字设置了超时值,并且之后的读操作和写操作在没有完成之前就超过了时间限制,那么这些操作就会抛出SocketTimeoutException异常; #: <syntaxhighlight lang="java"> try { InputStream in = s.getlnputStream(); //read from in . . . } catch(InterruptedIOException exception) { . . . } </syntaxhighlight> # 使用可以超时的无连接套接字: #* “Socket s = new Socket(String host, int port);”会一直无限期地阻塞下去,直到建立了到达主机的初始连接为止; #: <syntaxhighlight lang="java"> Socket s = new Socket(); s.connect(new InetSocketAddress(host, port), timeout); </syntaxhighlight> ==== 相关方法 ==== jva.net.Socket 1.0 * Socket() 1.1 *: 创建一个还未被连接的套接字。 * void connect(SocketAddress address) 1.4 *: 将该套接字连接到给定的地址。 * void connect(SocketAddress address, int timeoutlnMi11iseconds) 1.4 *: 将套接字连接到给定的地址。如果在给定的时间内没有响应,则返回。 * void setSoTimeout(int timeoutlnMilliseconds) 1.1 *: 设置该套接字上读请求的阻塞时间。如果超出给定时间,则抛出一个InterruptedIOException异常。 * boolean isConnected() 1.4 *: 如果该套接字已被连接,则返回true。 * boolean isClosed() 1.4 *: 如果套接宇已经被关闭,则返回true === 因特网地址 === 如果需要在主机名和因特网地址之间进行转换,那么就可以使用“InetAddress”类: * 只要主机操作系统支持1Pv6格式的因特网地址,java.net包也将支持它; # 静态的“getByName”方法可以返回代表某个主机的InetAddress对象: #: 然后,可以使用“getAddress”方法来访问这些字节; #: <syntaxhighlight lang="java"> InetAddress address = InetAddress.getByName("time-a.nist.gov"); byte[] addressButes = address.getAddress(); </syntaxhighlight> # 调用“getAllByName”方法来获得所有主机: #:(一些访问量较大的主机名通常会对应于多个因特网地址,以实现负载均衡) #: <syntaxhighlight lang="java"> InetAddress[] addresses= InetAddress.getA11ByName(host) ; </syntaxhighlight> # 使用静态的“getlocalHost”方法来得到本地主机的地址: #: <syntaxhighlight lang="java"> InetAddress address = InetAddress.getLocalHost(); </syntaxhighlight> ==== 相关方法 ==== jva.net.InetAddress 1.0 * static InetAddress getByName( String host) * static InetAddress[] getAllByName(String host) *: 为给定的主机名创建一个InetAddress对象,或者一个包含了该主机名所对应的所有因特网地址的数组。 * static InetAddress getlocalHost() *: 为本地主机创建一个InetAddress对象。 * byte[] getAddress() *: 返回一个包含数字型地址的字节数组。 * String getHostAddress() *: 返回一个由十进制数组成的字符串,各数字间用圆点符号隔开,例如,“129.6.15.28”。 * String getHostName() *: 返回主机名。 == 实现服务器 == === 服务器套接字 === # “ServerSocket”类用于建立套接字: #: <syntaxhighlight lang="java"> ServerSocket s = new ServerSocket(8189); </syntaxhighlight> # 建立一个负责监控端口8189的服务器: #: <syntaxhighlight lang="java"> Socket incoming = s.accept(); </syntaxhighlight> # 到输入流和输出流: #: <syntaxhighlight lang="java"> InputStream inStream = incoming.getinputStream(); OutputStream outStream = incoming.getOutputStream(); </syntaxhighlight> # 转换成扫描器和写入器: #: <syntaxhighlight lang="java"> Scanner in= new Scanner(inStream, "UTF-8"); PrintWriterout= new PrintWriter(newOutputStreamWriter(outStream, "UTF-8"), true) ; </syntaxhighlight> # 客户端发送信息: #: <syntaxhighlight lang="java"> out.print1n("Hello! Enter BYE to exit.") </syntaxhighlight> ==== 相关方法 ==== java.net.ServerSocket 1.0 * ServerSocket(int port) *: 创建一个监昕端口的服务器套接字。 * Socket accept() *: 等待连接。该方法阻塞(即,使之空闲)当前线程直到建立连接为止。该方法返回一个Socket对象,程序可以通过这个对象与连接中的客户端进行通信。 * void close() *: 关闭服务器套接字。 === 为多个客户端服务 === 运用线程:每当程序建立一个新的套接字连接,也就是说当调用“accept()”时,启动一个新的线程来处理服务器和该客户端之间的连接,而主程序将立即返回并等待下一个连接。 * 这种方法并不能满足高性能服务器的需求。为使服务器实现更高的吞吐量,可以使用“java.nio”包中一些特性。 <syntaxhighlight lang="java"> package threadad; import java.io.*; import java.net.*; import java.util.*; pub1ic c1ass ThreadedEchoServe { pub1ic static void main (String[] args ) { try(ServerSocket s = new ServerSocket(8189)) { int i = 1; while (true) { Socket incoming = s.accept(); System.out.println("Spawning " + i) ; Runnab1e r = new ThreadedEchoHandler(incoming); Thread t = new Thread(r); t.start(); i++; } } catch(IOException e) { e.printStackTrace(); } } } class ThreadedEchoHandler implements Runnab1e { privete Socket incoming; public ThreadedEchoHandler(Socket incomingSocket) { incoming = incomingSocket; } public void run() { try(InputStream inStream = incoming.getInputStream(); OnputStream onStream = incoming.getOnputStream()) { Scanner in new Scanner(inStream, "UTF-8"); PrintWriter out new PrintWriter( new OutputStreamWriter(outStream, "UTF-8"), true); out.println("Hello! Enter BYE to exit."); } boolean done= false; while(!done && in.hasNextLine()) { String line = in.nextline(); out.println("Echo: "+ line); if (1ine.trim().equals("BYE")) done = true; } catch(IOException e) { e.printStackTrace(); } } } </syntaxhighlight> === 半关闭 === 半关闭(half-close)提供了这样一种能力:套接字连接的一端可以终止其输出,同时仍旧可以接收来自另一端的数据。 客户端使用半关闭方法: <syntaxhighlight lang="java"> try(ServerSocket s = new ServerSocket(host, port)) { Scanner in = new Scanner(socket.getInputStream(), ”UTF-8”); PrintWriter writer = new PrintWriter(socket.getOutputStream()); // send request data Writer.print( . . . ); Writer.flush(); socket.shutdownOutput(); // now socket is ha1f-c1osed // read response data while(in.hasNextLine() != null) { String line = in.nextLine(); . . . } } </syntaxhighlight> 服务器端将读取输入信息,直至到达输入流的结尾,然后它再发送响应。 * 该协议只适用于一站式(one-shot)的服务,例如HTTP服务,在这种服务中,客户端连接服务器,发送一个请求,捕获响应信息,然后断开连接。 ==== 相关方法 ==== java.net.Socket 1.0 * void shutdownOutput() 1.3 *: 将输出流设为“流结束”。 * void shutdownInput() 1.3 *: 将输入流设为“流结束”。 * boo1ean isOutputShutdown() 1.4 *: 如果输出已被关闭,则返回true。 * boo1ean isInputShutdown() 1.4 *: 如果输入已被关闭,则返回true。 == 可中断套接字 == 当连接到一个套接字时,当前线和将会被阻塞且到建立连接或产生超时为止。同样地,当通过套接字读写数据时,当前线程也会被阻塞直到操作成功或产生超时为止。 * 当线程因套接字无法响应而发生阻塞时, 则无法通过调用interrupt来解除阻塞。 为了中断套接字操作,可以使用java.nio包提供的一个特性——“'''SocketChannel'''”类: # 打开SocketChannel: #: 通道(channel)并没有与之相关联的流,它所拥有的read和write方法都是通过使用Buffer对象来实现的(ReadableByteChannel接口和WritableByteChannel接口都声明了这两个方法)。 #: <syntaxhighlight lang="java"> SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port)); </syntaxhighlight> ## 如果不想处理缓冲区, 可以使用Scanner类从SocketChannel中读取信息: ##: <syntaxhighlight lang="java"> Scanner in = new Scanner(channel, "UTF-8"); </syntaxhighlight> ## 通过调用静态方法“Channels.newOutputStream”可以将通道转换成输出流: ##: <syntaxhighlight lang="java"> OutputStream outStream = Channels.newOutputStream(channel); </syntaxhighlight> ==== 相关方法 ==== java.net.InetSocketAddress 1.4 * InetSocketAddress(String hostname, int port) *: 用给定的主机和端口参数创建一个地址对象,并在创建过程中解析主机名。如果主机名不能被解析,那么该地址对象的unresolved 属性将被设为true。 * boolean isUnresolved() *: 如果不能解析该地址对象, 则返回true。 java.nio.channels.SocketChannel 1.4 * static SocketChannel open(SocketAddress address) *: 打开一个套接字通道,并将其连接到远程地址。 java.nio.channels.Channels 1.4 * static InputStream newinputStream(ReadableByteChannel channel) *: 创建一个输入流,用以从指定的通道读取数据 * static OutputStream newOutputStream(WritableByteChannel channel) *: 创建一个输出流, 用以向指定的通道写入数据 == 获取Web 数 == 为了在Java程序中访问Web服务器,在更高的级别上进行处理,而不只是创建套接字连接和发送HTTP诸求。 === URL 和 URI === java.net包对统一资源定位符(Uniform Resource Locator, URL)和统一资源标识符(Uniform Resource Identifier, URI)作了非常行用的区分: # URI是个纯粹的语法结构,包含用来指定Web资源的字符串的各种组成部分; # URL是URI的一个特例,它包含了用于定位Web资源的足够信息; # URN (uniform resource name, 统一资源名称),不属于定位符,因为根据该标识符我们无法定位任何数据; '''URL'''句法: <syntaxhighlight lang="java"> [scheme:]schemeSpecificPart[#fragment] </syntaxhighlight> * “[...]”表示可选部分,并且“:”和“#”可以被包含在标识符内; # 包含“scheme:”部分的URI称为“绝对URI”。否则,称为“相对URI”。 # 如果绝对URI的schemeSpecificPart不是以“/”开头的,就称它是“不透明的URI”。如: #: <syntaxhighlight lang="java"> mailto:cay@horstmann.com </syntaxhighlight> # 所有绝对的透明URI和所有相对URI都是分层的(hierarchical)。 如: #: <syntaxhighlight lang="java"> http://horstmann.com/index.html ../../java/net/Socket.html#Socket() </syntaxhighlight> # 一个分层URl的schemeSpecificPart具有以下结构: #: <syntaxhighlight lang="java"> [//authority][path][?query] </syntaxhighlight> #* “[. . .]”同样表示可选的部分。 # 对于那些基于服务器的URI,authority 部分具有以下形式: #: <syntaxhighlight lang="java"> [user-info@]host[:port] </syntaxhighlight> '''URI'''类,作用: # 解析标识符并将它分解成各种不同的组成部分: #: <syntaxhighlight lang="java"> getScheme getSchemeSpecificPart getAuthority getUserlnfo getHost getPort getPath getQuery getFragment </syntaxhighlight> # 处理绝对标识符和相对标识符: #: <syntaxhighlight lang="java"> combined = base.reso1ve(relative); // 解析相对URL relative = base.relativize(combined); // 相对化URL </syntaxhighlight> ## 解析相对URL: ##: <syntaxhighlight lang="java"> 示例: 1、绝对URI: http://docs.mycompany.com/api/java/net/ServerSocket.html 2、相对URI: ../../java/net/Socket.html#Socket() 由1、2 组合出一个绝对URI: http://docs.mycompany.com/api/java/net/Socket.html#Socket() </syntaxhighlight> ## 相对化(relativization): ##: <syntaxhighlight lang="java"> 示例: 1、URI: http://docs.mycompany.com/api 2、URI: http://docs.mycompany.com/api/java/1ang/String.html 相对化之后的URI: java/1ang/String.htm1 </syntaxhighlight> === 使用URLConnection 获取信息 === 使用“'''URLConnection'''”类,通过它得到比基本的URL类更多的控制功能。<br/> 操作步骤: # 调用URL类中的“openConnection”方法获得“URLConnection”对象: #: <syntaxhighlight lang="java"> URLConnection connection = url.openConnection(); </syntaxhighlight> # 使用以下方法来设置任意的请求属性: ## “setDoInput”: ## “setDoOutput”: ## “setIfModifiedSince”: ## “setUseCaches”: ## “setAl1owUserInteraction”: ## “setRequestProperty”: ## “setConnectTimeout”: ## “setReadTimeout”: # 调用connect方法连接远程资源: #: <syntaxhighlight lang="java"> connection.connect(); </syntaxhighlight> #* 除了与服务器建立套接字连接外, 该方法还可用于向服务器查询头信息(headerinformation)。 # 与服务器建立连接后,可以查询头信息。 ## “getHeaderFieldKey”和“getHeaderField”这两个方法枚举了消息头的所有字段。 ## “getHeaderFields”方法返回一个包含了消息头中所有字段的标准Map对象。 #: 以下方法可以查询各标准字段: ## “getContentType”: ## “getContentlength”: ## “getContentEncoding”: ## “getDate”: ## “getExpiration”: ## “getLastModified”: # 最后,访问资源数据。 #: 使用“getInputStream”方法获取一个输入流用以读取信息(这个输人流与URL类中的“openStream”方法所返回的流相同)。 #:(另一个方法getContent在实际操作中并不是很有用) #* 由标准内容类型(比如“text/plain”和“image/gif”) 所返回的对象需要使用“com.sun”层次结构中的类来进行处理。 ==== 相关方法 ==== java.net.URL 1.0 * InputStream openStream() *: 打开一个用于读取资源数据的输入流。 * URLConnection openConnection(); *: 返回一个URLConnection对象, 该对象负责管理与资源之间的连接。 java.net.URLConnection 1.0 * void setDolnput(boolean doInput) * boolean getDoInput() *: 如果doInput为true, 那么用户可以接收来自该URLConnection的输入。 * void setDoOutput(boolean doOutput) * boolean getDoOutput() *: 如果doOutput为true, 那么用户可以将输出发送到该URLConnection * void setIfModifiedSince(long time) * long getIfModifiedSince() *: 属性ifModifiedSince用于配置该URLConnection对象,使它只获取那些自从某个给定时间以来被修改过的数据。调用方法时需要传入的time 参数指的是从格林尼治时间1970年1月1日午夜开始计葬的秒数。 * void setUseCaches(boolean useCaches) * boolean getUseCaches() *: 如果useCaches为true, 那么数据可以从本地缓存中得到。 诮注意,URLConnection本身并不维护这样一个缓存, 缓存必须由浏览片眨之类的外部程序提供。 * void setAllowUserlnteraction(boolean allowUserinteraction) * boolean getAl1owUserinteraction() *: 如果allowUserlnteraction为true, 那么可以查询用户的口令。 请注意,URLConnection本身并不提供这种查询功能。 查询必须由浏览器或浏览器插件之类的外部程序实现。 void setConnectTimeout(int timeout) 5.0 * int getConnectTimeout() 5. 0 *: 设置或得到连接超时时限(单位:毫秒)。 如果在连接建立之前就已经达到了超时的时限,那么相关联的输入流的connect方法就会抛出一个SocketTimeoutException异常。 void setReadTi meout(int ti me out) 5. O * int getReadTimeout () 5. 0 *: 设笠读取数据的超时时限(单位:亳秒)。 如果在一个读操作成功之前就已经达到了超时的时限, 那么read 方法就会抛出一个 SocketTi meoutExcept ion异常。 * void setRequestProperty(String key, String value) *: 设置请求头的一个字段。 * Map<String, List<String>> getRequestProperties() 1. 4 *: 返回诸求头屈性的一个映射表。 相同的键对应的所有值被放嚣在同一个列表中。 * void connect() *: 连接远程资源并获取响应头信息。 * Map<String, List<String>> getHeaderFields() 1. 4 *: 返回响应的一个映射表。 相同的键对应的所有值被放翌在同一个列表中。 * String getHeaderFieldKey(int n) *: 得到响应头第n个字段的键。 如果n 小于等于0或大于响应头字段的总数, 则该方法返回null值。 * String getHeaderField(int n) *: 得到响应头第n个字段的值。 如果n 小于等于0或大于响应头字段的总数, 则该方法返回null值。 * int getContentlength() *: 如果内容长度可获得, 则返回该长度值, 否则返回-1。 * String getContentType() *: 获取内容的类型 , 比如text/plain 或 image/gif。 * String getContentEncoding() *: 获取内容的编码机制,比如gzip。这个值不太常用,因为默认的identity 编码机制并不是用Content-Encoding头来设定的。 * long getDate() * long getExpiration() * long getlastModified() *: 获取创建日期 、 过期日以及最后一次被修改的日期。 这些日期指的是从格林尼治时间1970年1月1日午夜开始计算的秒数。 * InputStream getInputStream() * OutputStream getOutputStream() *: 返回从资源读取信息或向资源写入信息的流。 * Object getContent() *: 选择适当的内容处理器,以便读取资源数据并将它转换成对象。该方法对于读取诸如“text/plain”或“image/gif”之类的标准内容类型并没有什么用处,除非你安装了自己的内容处理器。 === 提交表单数据 === <pre> 有许多技术可以让Web服务器实现对程序的调用。 其中最广人所知的是Java Servlet、 JavaServer Face、 微软的ASP (Active Server Pages, 动态服务器主页)以及CGI (Common Gateway Interface, 通用网关接口)脚本。 </pre> [[File:执行服务器端脚本过程中的数据流.png|600px]] <pre> 当表单数据被发送到Web服务器时,数据到底由谁来解释并不重要,可能是Servlet或CGI脚本,也可能是其他服务器端技术。客户端以标准格式将数据发送给Web服务器,而Web服务器则负责将数据传递给具体的程序以产生响应。 </pre> 在向Web服务器发送信息时, 通常有两个命令会被用到:“'''GET'''”和“'''POST'''”: '''GET''':在使用GET命令时,只需将参数附在URL的结尾处即可。这种URL的格式如下: <syntaxhighlight lang="java"> http://host/path?query </syntaxhighlight> # 每个参数都具有“名字=值”的形式,参数之间用“&”字符分隔开; # 参数的值将遵循下面的规则: ## 保留字符“A — Z 、a — z 、0 — 9 ”,以及“.”、“-”、“~”、“_”; ## 用“+”字符替换所有的空格; ## 将其他所有字符编码为“UTF-8”,并将每个字节都编码为“%”后面紧跟一个两位的十六进制数字。 * 老式的浏览器和代理对在“GET”请求中能够包含的字符数扯做出了限制。正因为此,POST请求经常用来处理具有大批数据的表单。 '''POST''':在POST请求中,不会在 URL 上附着参数,而是从“URLConnection”中获得输出流,并将“名/值”对写入到该输出流中。 * 仍旧需要对这些值进行URL编码,并用“&”字符将它们隔开; # 创建一个“'''URLConnection'''”对象: #: <syntaxhighlight lang="java"> URL url = new URL("http://host/path"); URLConnection connection = url.openConnection(); </syntaxhighlight> # 调用“setDoOutput”方法建立一个用于输出的连接: #: <syntaxhighlight lang="java"> connection.setDoOutput(true); </syntaxhighlight> # 接着,调用“getOutputStream”方法获得一个流,可以通过这个流向服务器发送数据: #: 如果要向服务器发送文本信息,那么可以非常方便地将流包装在“PrintWriter”对象中。 #: <syntaxhighlight lang="java"> PrintWriter out = new PrintWriter(connection.getOutputStream(), "UTF-8"); </syntaxhighlight> # 向服务器发送数据: #: <syntaxhighlight lang="java"> out.print(namel + "=" + URLEncoder.encode(valuel, "UTF-8") + "&"); out.print(name2 + "=" + URLEncoder.encode(value2, "UTF-8")); </syntaxhighlight> # 关闭输出流: #: <syntaxhighlight lang="java"> out.close(); </syntaxhighlight> # 最后,调用“getInputStream”方法读取服务器的响应。 '''人工实现重定向''': # 在连接到服务器之前,将自动重定向关闭: #: <syntaxhighlight lang="java"> connection.setlnstanceFollowRedirects(false); </syntaxhighlight> # 在发送请求之后,获取响应码: #: <syntaxhighlight lang="java"> int responseCode = connection.getResponseCode(); </syntaxhighlight> #: 检查它是否是下列值之一: ## HttpURLConnection.HTTP_MOVED_PERM ## HttpURLConnection.HTTP_MOVED_TEMP ## HttpURLConnection.HTTP_SEE_OTHER # 如果是这些值之一,那么获取Location响应头,以获得通定向的URL。然后,断开连接,并创建到新的URL的连接: #: <syntaxhighlight lang="java"> String location = connection.getHeaderFie1d("Location"); if (location != nu11) { URL base = connection.getURL(); connection.disconnect(); connection = (HttpURLConnection) new URL(base, 1ocation).openConnection(); . . . } </syntaxhighlight> ==== 相关方法 ==== java.net.HttpURLConnection 1.0 * InputStream getErrorStream() *: 返同一个流,通过这个流可以读取Web服务器的错误信息。 java.net.URLEncoder 1.0 * static String encode(String s, String encoding) 1.4 *: 采用指定的字符编码模式(惟荐使用"UTF-8")对字符串 s进行编码, 并返回它的URL编码形式。在 URL 编码中,'A '-'Z','a'- 'z','0'- '9' '-' , _','.'和 '*'等字符保持不变,空格被编码成 '+', 所有其他字符被编码成 "%XY"形式的字节序列,其中OxXY为该字节十六进制数。 java.net.URLDecoder1.2 * static string decode(String s, String encoding) 1.4 *: 采用指定编码模式对已编码宁符串s进行解码,并返回结果。 == 发送E-mail ==
返回至“
核心技术Ⅱ:网络
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息