网络(Java核心技术卷Ⅱ)

网络

3.1 连接到服务器

  • 打开一个套接字:Socket s=new Socket(address,port); 套接字是网络软件中的一个抽象概念,负责启动程序内部和外部之间的通信。将远程地址和端口号传递给套接字的构造器,如果连接失败,将抛出UnknownHostException异常。如果存在其他的问题,它将抛出一个IOException.
  • 一旦套接字被打开,getInputStream方法就会返回一个InputStream对象,该对象可以像其他任何流对象一样使用。

套接字超时

  • 从套接字读取信息时,在有数据可供访问之前,读操作将会被阻塞。如果此时主机不可达,那么应用将要等待很长的时间并且因为受底层操作系统的限制而最终导致超时。
  • 对于不同应用,应该确定合理的超时值。然后用setSoTimeout方法设定超时值:socket.setSoTimeout(10000);
  • 另外一个问题:Socket(String host,int port)会一直无限期地阻塞下去,直到建立了到达主机的初始连接为止。可以通过先构建一个无连接的套接字,然后再使用一个超时来进行连接的方法解决这个问题。Socket s=new Socket(); s.connect(new InetSocketAddress(host,port),timeout);

互联网地址

  • 如果需要在主机名和互联网地址之间进行转换,可以使用InetAddress类。静态的getByName方法可以返回代表某个主机的InetAddress对象,该对象封装了一个4字节的序列(IPv4)。InetAddress address=InetAddress.getByName(".."); 可以使用getAddress方法来访问这些字节:byte[] bytes=address.getAddress();
  • 一些访问量较大的主机名通常会对应多个互联网地址,以实现负载均衡。可以调用getAllByName方法获得所有主机:InetAddress[] addresses=InetAddress.getAllByName("..");
  • 得到本地主机的地址:InetAddress address=InetAddress.getLocalHost();

3.2 实现服务器

  • 实现一个简单的服务器,可以向客户端发送信息。一旦启动了服务器程序,它便等待某个客户端连接到它的端口。这里选择端口8189,因为标准服务都不使用这个端口。
  • 建立一个负责监控端口8189的服务器:ServerSocket s=new ServerSocket(8189);
  • 让服务器等待,直到有客户端连接到这个端口:Socket incoming=s.accept(); 一旦有人通过网络发送了正确的连接请求,并以此连接到了端口上,该方法就会返回一个表示连接已经建立的Socket对象。
  • 可以使用Socket对象得到输入流和输出流:InputStream inStream=incoming.getInputStream(); OutputStream outStream=incoming.getOutputStream();
  • 给客户端发送一条问候信息:Scanner in=new Scanner(inStream); PrintWriter out=new PrintWriter(outStream,true); //自动刷新 out.println("Hello!Enter BYE to exit.");
  • 读取用户输入,并发送回去,表明已接收到输入:String line=in.nextLine(); out.println("Echo:"+line); if(line.trim().equals("BYE")) done=true;
  • 最后关闭连接的套接字:incoming.close();

为多个客户端服务

  • 前面的简单客户端会拒绝多客户端连接,使某个用户会因长时间地连接服务而独占服务。其实可以通过线程完成多客户端的连接。
  • 每当程序建立一个新的套接字连接,也就是调用accept时,将启动一个新的线程来处理服务器和该客户端之间的连接,而主程序立即返回并等待下一个连接。为了实现这个机制,服务器应该有类似下列代码的循环操作:
while(true){
    Socket incoming=s.accept();
    Runnable r=new ThreadedEchoHandler(incoming);
    Thread t=new Thread(r);
    t.start();
}
  • ThreadedEchoHandler类实现了Runnable接口,而且在run方法中包含了和客户端循环通信的代码。

半开闭(half-close)

  • 半开闭提供了这样一种能力:套接字连接的一端可以终止其输出,同时仍旧可以接收来自另一端的数据。可以通过关闭一个套接字的输出流来表示发送给服务器的请求数据已经结束,但是必须保持输入流处于打开状态。
  • 在客户端使用半开闭方法:
Socket socket=new Socket(host,port);
Scanner in=new Scanner(socket.getInputStream());
PrintWriter writer=new PrintWriter(socket.getOutputStream());

writer.print(...);
writer.flush();
socket.shutdownOutput();

while(in.hasNextLine()){String line=in.nextLine();..}
socket.close();
  • 这个协议只适用于一站式(one-shot)服务,例如HTTP服务。在这种服务中,客户端连接服务器,发送一个请求,捕获响应信息,然后断开连接。

3.3 可中断套接字

  • 当连接到一个套接字时,当前线程将会被阻塞直到建立连接或产生超时为止。同样地,当通过套接字读写数据时,当前线程也会被阻塞直到操作成功或产生超时为止。当线程因套接字无法响应而阻塞时,无法通过interrupt来解除阻塞。
  • 为了中断套接字操作,可以使用java.nio包提供的SocketChannel类。打开SocketChannel:SocketChannel channel=SocketChannel.open(new InetAddress(host,port)); 通道并没有与之相关联的流。实际上,它所拥有的read和write方法都是通过使用Buffer对象来实现的。ReadableByteChannel和WritableByteChannel接口都声明了这两个方法。
  • 如果不想处理缓冲区,可以使用Scanner类从SocketChannel读取信息,因为Scanner有一个带ReadableByteChannel参数的构造器:Scanner in=new Scanner(channel);
  • 通过调用静态方法Channels.newOutputStream,可以将通道转换成输出流。OutputStream outStream=Channels.newOutputStream(channel);
  • 假设线程正在执行打开、读取或写入操作,此时如果线程发生中断,那么这些操作将不会陷入阻塞,而是以抛出异常的方式结束。

3.4 获取web数

URL和URI

  • 构造URL对象:URL url=new URL(urlString);
  • 获取资源:InputStream inStream=url.openStream(); Scanner in=new Scanner(inStream);
  • URI是个纯粹的语法结构,包含用来指定Web资源的字符串的各种组成部分。URL是URI的一个特例,它包含了用于定位web资源的足够信息。而无法定位任何资源的URI成为URN。
  • 在Java类库中,URI类不包含任何用于访问资源的方法,它的唯一作用就是解析。相反的,URL类可以打开一个到达资源的流。因此,URL只能作用于那些Java类库知道该怎么处理的模式,例如http:、https、ftp:、本地文件系统(file:)和jar文件(jar:)。
  • URI的语法:[scheme:]schemeSpecificPart[#fragment] […]表示可选部分,并且:和#可以被包含在标识符内。包含scheme:部分的URI称为绝对URI,否则为相对URI。如果绝对URI的schemeSpecificPart不是以/开头,就称为不透明的。所有绝对的透明URI和相对URI都是分层的:http://horstmann.com/index.html 一个分层的URI的schemeSpecificPart具有以下结构:[//authority][path][?query] 对于那些基于服务器的URI,authority部分具有以下形式:[user-info@]host[:port]
  • URI类的作用之一就是解析标识符并将它分解成各种不同的组成部分。可以用getScheme、getSchemeSpecificPart、getAuthority等方法读取。URI的另一个作用就是处理绝对标识符和相对标识符,包括解析相对URI和相对化。relative=base.relativize(combined); combined=base.resolve(relative);

使用URLConnection获取信息

URLConnection能够获取更多信息,能够得到比基本的URL类更多的控制功能。
使用URLConnection必须小心地安排操作步骤:

  1. 获得URLConnection对象:URLConnection connection=url.openConnection();
  2. 使用setDoInput、setDoOutput、setIfModifiedSince等设置任意的请求属性。
  3. 连接远程资源:connection.connect();
  4. 与服务器连接后,可以查询头信息。getHeaderFieldKey和getHeaderField两个方法枚举了消息头的所有字段。也可以使用getContentType、getContentLength等方法查询标准字段。
  5. 访问资源数据。getInputStream方法返回一个输入流以读取信息(这个输入流与URL类的openStream返回的流相同)。

其他方法:

  • setDoInput、setDoOutput:在默认情况下,建立的连接只产生从服务器读取信息的输入流,并不产生任何执行写操作的输出流。如果想获得输出流,需要调用:connection.setDoOutput(true);
  • setIfModifiedSince:告诉连接你自某个特定日期以来被修改的数据感兴趣
  • setUserCaches:首先检查缓存
  • setAllowUserInteraction:在访问有密码保护的资源时弹出对话框,以便查询用户名和口令
  • setRequestProperty:设置对特定协议的任何名/值对。如果想访问一个有密码保护的网页:1.将用户名、冒号和密码以字符串形式连接在一起 String input=username+":"+password; 2.计算字符串的Base64编码。(Base64编码用于将字节序列编码成可打印的ASCⅡ字符序列)String encoding=base64Encode(input) 3.调用setRequestProperty,设置name的参数为Authorization,value的值为”basic”+encoding:connection.setRequestProperty("Authorization","Basic"+encoding);
  • 一旦调用了connect方法,就可以查询响应头信息。String key=connection.getHeaderFieldKey(n); 可以获得响应头的第n个键。其中n从1开始。同样地,调用String value=connection.getHeaderField(n); 可以获得第n个值。getHeaderFields方法可以返回一个封装了响应头字段的Map对象。Map<String,List<String>> headerFields=connection.getHeaderFields();

提交表单数据

  • 为了将信息从浏览器发送到服务器,需要填写表单。
  • 在向Web服务器发送信息时,通常会有两个命令被用到:GET和POST。
  • 使用GET命令时,只需将参数附在URL的结尾即可。这种URL的格式如下:http://host/script?parameters 其中,每个参数都具有”名字=值”的形式,而这些参数之间用&号分隔开。参数值将遵循下面的原则编码:保留字符A-Z、a-z、0-9以及.-*_ ;用+替换所有的空格;将其他所有字符编码为UTF-8,并将每个字节编码为%后面紧跟一个两位的十六进制数字。GET命令很简单,但有一个很大的局限性:大多数浏览器都对GET请求中可以包含的字符数作了限制。
  • 在使用POST命令时,不需要在URL中添加任何参数,而是从URLConnection中获取输出流,并将名/值对写入该流中。当然,仍然需要先编码。整个过程为:
URL url=new URL("");
URLConnection connection=url.openConnection();
connection.setDoOutput(true);
PrintWriter out=new PrintWriter(connection.getOutputStream());
out.print(name1+"="+URLEncoder.encode(value1,"UTF-8")+"&");
out.print(name2+"="+URLEncoder.encode(value2,"UTF-8"));
out.close();
  • URLConnection还向服务器发送了一个请求头。当提交表单时,该请求头包含下面这行内容:Content-Type:application/x-www-form-rlencoded 而POST的请求头还必须包括内容的长度Content-Length:124 请求头必须以空白行结尾,紧跟其后的才是数据部分。Web服务器滤去请求头后将数据部分发送给服务端脚本程序。注意,URLConnection对象会把发送到输出流的所有数据都先缓存起来,这是因为在真正发送之前,必须首先确定内容的总长度。

发送Email
编写程序通过创建到SMTP专用的端口25来发送邮件是一个很简单的事。简单邮件传输协议用于描述Email消息的格式。一旦连接到服务器,就可以发送一个邮件报头(采用SMTP格式,该格式很容易生成)紧随其后的是邮件消息。以下是操作的详细过程:

  1. 打开一个到达主机的套接字:Socket s=new Socket("mail.yourserver.com",25); PrintWriter out=new PrintWriter(s.getOutputStream());
  2. 发送以下信息到打印流:
HELO sending host
MAIL FROM:sender e-mail address
RCPT TO:recipient e-mail address
DATA
Subject: subject
(blank line)
mail message(any number of lines)
.
QUIT
  • SMTP规范规定,每一行都要以\r再紧跟\n来结尾。
  • SMTP曾经总是例行公事般地路由任何人的E-mail。但是,在蠕虫泛滥的今天,许多服务器都内置了检查功能,并且只接受来自授信用户或IP地址范围内的请求。其中,认证通常是通过安全套接字连接来实现的。实现人工认证模式的代码非常冗长乏味,所以使用JavaMail API在Java程序中发送e-mail.
  • 如果要使用JavaMail,则需要设置一些和邮件服务器相关的属性。例如,在使用GMail时,需要设置mail.transport.protocol=smtps mail.smtps.auth=true mail.smtps.host=smtp.gmail.com mail.smtps.user=...@gmail.com 可以存放在属性文件中读入。密码最好不要放在属性文件中,而是由用户输入。
  • 操作过程:
//读入属性文件,获取邮件会话
Session mailSession=Session.getDefaultInstance(props);
//用恰当的发送者、接受者、主题和消息文本创建信息:
MimeMessage message=new MimeMessage(mailSession);
message.setFrom(new InetAddress(from));
message.addRecipient(RecipientType.TO,new InetAddress(to));
message.setSubject(subject);
message.setText(builder.toString());
//然后发送消息
Transport tr=mailSession.getTransport();
tr.connect(null,password);
tr.sendMessage(message,message.getAllRecipients());
tr.close();
  • 如果邮件连接无法正常工作,可以调用mailSession.setDebug(true); 检查消息。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值