第九章 网络编程
第一节 理论基础
java网络编程模式(java.net包)马
克-to-win:一台机器还没玩转,(视频下载) (全部书籍)现在又想靠网络操作别的机器,可想有多难吧!但是Sun公司把那些访问网络的难题细节(打包和拆包,
块的来回运输,以及握手等等)都封在它的net包中了,通过调用net包中的类的方法,你可以轻松访问和操作网上的其它电脑。Java的这种网络编程模式(即调用net包中的类的方法的方式)其实就是一个基于上一章的文件流的模式;所以可像对其他数据流那样采用同样的方法调用。
java通信的方法:服务器和客户机
马克-to-win:我们先理解一下人类的通信,再谈机器,就好懂了。你
想跟张三说话,他如果事先在大声听音乐,你肯定白说。他如果事先知道你要跟他说话,于是支棱着耳朵等着你说,肯定错不过你说话。好了,电脑通信就是受到了
我刚才提到的那个大家熟视无睹的现象的启发而采取了服务器和客户机的模式。在以上的例子中,张三就是一台普通机器,我就是客户机(想和他通信)。他没注意
我时,我们无法通信。他一旦注意我时,他就成为了一台服务器。(提供某种服务的机器),我们就可以通信了。
连接的机制(套接字)(Socket)
马克-to-win:同学,(视频下载) (全部书籍)请看看你自己的电视和什么连在一
起。是木桌子吗?还是窗帘?对,是接线板。网络的发明者们就是受到了我刚才提到的那个大家熟视无睹的现象的启发而采取了Socket(接线板)机制。但不
幸的是,
我们的最早的翻译家们不知什么原因,翻译成了套接字?谁能告诉我中文里套接字是什么意思?接线板(Socket)机制很形象。服务器和客户端都生成一个
Socket的类对象,俩对象就可以发送数据了。当然,机器之间的物理硬件以及电缆连接等细节都封装在类中了。我们就没必要关心了。下一步,从
Socket接线板中得到一个InputStream或OutputStream,这样,我们就可以将网络连接作为一对IO流对象来处理了。
机器的标识
Java的机器标识机制可以对全因特网的机器进行标识。为达到这个目的,Sun采用了IP(internet协议而不是workgroup或什么其他协议)的概念。IP以两种形式存在着:
(1) DNS(域名服务)形式。比如我自己的域名是abc.com。假定我在自己的域内有一台名为kkk的计算机,它的域名就可以是kkk.abc.com。需要声明的是:www.baidu.com也是一台机器名称。
(2) 此外,也可采用“四点”格式,比如101.96.33.23。
协议(网络中计算机之间通信的规则)
马克-to-win:(视频下载) (全部书籍)协议就是生活中的合同,生活中我们比如做一个大的软件项目,我们得和人签合同,说你们第一步给一部分钱,我们第二步干完活,你们第三
步给剩余的钱。网络协议也一样,说明一步一步我们怎么通信。我们上网经常用到超文本传输协议 (HTTP)或文件传输协议
(FTP)或简单邮件传输协议 (SMTP),这章我们重点讲述传输控制协议(TCP)和用户数据报协议(UDP)。
“传输控制协议”(TCP),具有高度的可靠性。即收到的
数据肯定正确,错误的宁可删掉当做没传。当然,这种可靠性需要我们付出代价:TCP具有非常高的开销。生活中,我上次买了个手机,人家质保一年,我非让人
家质保两年,人家说加钱也行。道理一样。还有另一种协议,即“用户数据报协议”(UDP),这是一种“不可靠协议”。优点就是快,我在国外时最爱用的一种
网络电话卡就是UDP的,卡很便宜,通话质量很差,比我国内的朋友给我打电话便宜多了,所以一打电话没完没了,还听不清楚,他们都怕了,还以为我有多有
钱,其实都应该感谢UDP卡这种技术这么便宜。所以好好学吧!另外,有些应用也许能向服务器传回一条UDP消息,以便以后能够恢复。this
is a smart way by your self to achieve eliability.
无网络测试连网程序(localhost或127.0.0.1)
由于多种原因,(视频下载) (全部书籍)我们可能没有网络环境,但得测试自己做好的程序。网络的设计者建立了一个特殊的地址——localhost——来满足要求。或者使用:127.0.0.1
端口(port)
马 克-to-win:(视频下载) (全部书籍)Port端口是个伟大的发明。否则当我又想访问www.baidu.com获得他的网页,又想同时访问这台机器的sql server数据库获取数据库时怎么办呢?这时就用端口来区分。一个在80口,一个在1433端口。一个端口相对于一个程序(先不谈一个程序用多个端口的 情况)。一台机器共有65536个端口。Port must be between 0 and 65535 inclusive.
第二节 实践练习
1.InetAddress的用法
下面这个程序利用InetAddress.getByName()来得到你的和百度IP地址。
import java.net.*;
public class TestMark_to_win {
public static void main(String[] args) throws Exception {
/* static InetAddress getByName(String host) Determines the IP address
of a host, given the host's name.
*/
InetAddress a = InetAddress.getByName("localhost");
System.out.println(a.getHostAddress());
System.out.println(a.getHostName());
InetAddress b = InetAddress.getByName("www.baidu.com");
System.out.println(b.getHostAddress());
System.out.println(b.getHostName());
}
}
输出结果:
127.0.0.1
localhost
61.135.169.125
www.baidu.com
2.TCP协议的通信实例
马克-to-win:(视频下载) (全部书籍)我们首先给出一个最最简单的helloworld通信程序。让大家体会一把两台机器的通信,大家之后就可以慢慢把它发展成为聊天程 序。马克-to-win:TCP通信有两个类:1)ServerSocket:服务器用它监听进入的连接;2)Socket:双方都用它初始一次连接。一旦客户端申请建立一个连接,ServerSocket就会返回(通过accept()方法)一个对应的服务器端的Socket,以便进行直接通信。从此时起,我们就得到了一对真正的“Socket-Socket”连接,此时可以利用getInputStream()以及getOutputStream()从每个Socket产生对应的 InputStream和OutputStream对象。之后,可按上章介绍的方法对类进行处理,就象原来对待其他任何流对象那样。创建一个 ServerSocket时,只需为其赋予一个端口编号。但在创建一个客户端 Socket时,必须同时赋予IP以及要连接的端口。下面这对程序先运行服务器程序,再运行客户端程序。
import java.io.*;
import java.net.*;
public class TestMark_to_win {
public static final int PORT = 4002;
public static void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(PORT);
// Blocks until a connection occurs:
System.out.println("我作为服务器,正等着你");
Socket socket = s.accept();
System.out.println("这句开始打印不出来");
InputStream in = socket.getInputStream();
int i = in.read();
System.out.println("Echoing: " + i);
socket.close();
s.close();
}
}
以下是客户端程序:
import java.net.*;
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 4002);
OutputStream out = socket.getOutputStream();
out.write(97);
socket.close();
}
}
输出结果:
我作为服务器,正等着你
这句开始打印不出来
Echoing: 97
例:2.2.2(客户端读写,服务器端也读写)(蓝笔为比上一个程序多的部分)
import java.io.*;
import java.net.*;
public class TestMark_to_win {
public static final int PORT = 4002;
public static void main(String[] args) throws IOException {
ServerSocket s = new ServerSocket(PORT);
// Blocks until a connection occurs:
System.out.println("我作为服务器,正等着你");
Socket socket = s.accept();
System.out.println("这句开始打印不出来");
InputStream in = socket.getInputStream();
// Blocks until a connection occurs:
int i = in.read();
System.out.println("服务器端反馈" + i);
OutputStream out = socket.getOutputStream();
out.write(98);
socket.close();
s.close();
}
}
以下是客户端程序:
3.UDP(数据报)协议的通信实例
马克-to-win:(视频下载) (全部书籍)在UDP编程当中,技术上没有一个服务器和客户端的概念,即没有类似于TCP中的ServerSocket类,只有主动和被动之说,
客户端和服务器都用DatagramSocket(MyPORT)来绑定到一个端口,发送和接收dataPacket,它们是对等的双方。不过通常来讲,
先发送数据的被认为是客户端。in UDP, there is no concept of server or client, only
active and passive, client and server both use new
DatagramSocket(MyPORT) to bind to a port to use the port to send and
receive the dataPacket, the counterpart which initially send the
dataPacket is deemed as the client. unlike the TCP protocol, there,
there is really ServerSocket.
例:2.3.1(客户端写,服务器端读)
import java.net.*;
import java.io.*;
import java.util.*;
public class TestMark_to_win {
static final int MyPORT = 1711;
public static void main(String[] args) throws IOException {
byte[] bufreceive = new byte[1000];
byte[] bufsend = new byte[1000];
/* public DatagramPacket(byte[] buf,int length) Constructs a
DatagramPacket for receiving packets of length length. The length
argument must be less than or equal to buf.length.
*/
DatagramPacket packetreceive = new DatagramPacket(bufreceive,
bufreceive.length);
// Can listen & send on the same socket:
/*
* Datagram packets are used to implement a connectionless packet
* delivery service.
*/
DatagramSocket socket;
/*
* This class represents a socket for sending and receiving datagram
* packets. A datagram socket is the sending or receiving point for a
* packet delivery service. DatagramSocket(MyPORT): Constructs a
* datagram socket and binds it to the specified port on the local host
* machine.
*/
socket = new DatagramSocket(MyPORT);
while (true) {
/*
* Receives a datagram packet from this socket. When this method
* returns, the DatagramPacket's buffer is filled with the data
* received. Block until a datagram appears:
*/
socket.receive(packetreceive);
/* public byte[] getData()Returns the data buffer. public int
* getLength() Returns the length of the data to be sent or the
* length of the data received.
*
* public String(byte[] bytes,int offset,int length) Constructs a
* new String by decoding the specified subarray of bytes using the
* platform's default charset.
*/
String stringreceive = new String(packetreceive.getData(), 0,
packetreceive.getLength());
System.out.println(stringreceive);
stringreceive = stringreceive + "coming back from server";
/*
* public byte[] getBytes() Encodes this String into a sequence of
* bytes using the platform's default charset, storing the result
* into a new byte array.
*/
bufsend = stringreceive.getBytes();
/*
* public DatagramPacket(byte[] buf,int length,InetAddress
* address,int port) Constructs a datagram packet for sending
* packets of length length to the specified port number on the
* specified host. The length argument must be less than or equal to
* buf.length.
*
* public InetAddress getAddress() Returns the IP address of the
* machine to which this datagram is being sent or from which the
* datagram was received. public int getPort() Returns the port
* number on the remote host to which this datagram is being sent or
* from which the datagram was received.
*/
DatagramPacket echo = new DatagramPacket(bufsend, bufsend.length,
packetreceive.getAddress(), packetreceive.getPort());
socket.send(echo);
}
}
}
以下是客户端程序:
import java.net.*;
import java.io.*;
import java.util.*;
public class Test {
static final int MyPORT = 1710;
static final int SERVERPORT = 1711;
public static void main(String[] args) throws IOException {
byte[] bufreceive = new byte[1000];
byte[] bufsend = new byte[1000];
DatagramPacket p = new DatagramPacket(bufreceive, bufreceive.length);
DatagramSocket client;
BufferedReader dis = new BufferedReader(
new InputStreamReader(System.in));
/* public static InetAddress getByName(String host) throws
* UnknownHostException: Determines the IP address of a host, given the
* host's name. The host name can either be a machine name, such as
* "java.sun.com", or a textual representation of its IP address. If a
* literal IP address is supplied, only the validity of the address
* format is checked.
*/
InetAddress destination = InetAddress.getByName("localhost");
client = new DatagramSocket(MyPORT);
while (true) {
String str = dis.readLine();
if (str.equals("end"))
break;
bufsend = str.getBytes();// string encode to a byte array
DatagramPacket sendpacket = new DatagramPacket(bufsend,
bufsend.length, destination, SERVERPORT);
client.send(sendpacket);
// Block until a datagram appears:
client.receive(p);
/* p.getData means return the data buffer of the packet,
* String(byte[] bytes, int offset, int length)------ Constructs a
* new String by decoding the specified subarray of bytes using the
* platform's default charset.
*/
String psx = new String(p.getData(), 0, p.getLength());
System.out.println(psx);
}
}
}
客户端结果输出:(eclipse本身有bug,所以我只用英文实验)
java
javacoming back from server
study
studycoming back from server
end
服务器端结果输出:
java
study
马克-to-win:(视频下载) (全部书籍)URL(Uniform Resource Locator-----一致资源查找器)它用来指向Internet上的资源文件,比如
http://java.sun.com:8080/docs/introdiction.htm
net包中的URL类提供API来访问Internet上的信息。
比如以上的URL中:
1)协议:http
2)IP 地址或主机名:java.sun.com
3)端口号:8080
4)实际文件路径:docs/introdiction.htm
输出结果:
protocol = http
host = java.sun.com
filename = /docs/books/tutorial/index.html
default port = 80
port = 8080
例:2.4.2
/* This program needs an open connection to run.
*/
import java.net.*;
import java.io.*;
import java.util.*;
public class TestMark_to_win {
public static void main(String[] args) throws Exception {
URL yahoo = new URL("https://www.oracle.com/index.html");
/* public URLConnection openConnection() throws IOException: Returns a
* URLConnection object that represents a connection to the remote
* object referred to by the URL.
*/
URLConnection yahooConnection = yahoo.openConnection();
/* public long getLastModified() Returns the value of the last-modified
* header field. The result is the number of milliseconds since January
* 1, 1970 GMT.
*/
System.out.println("content LastModified"
+ new Date(yahooConnection.getLastModified()));
/*public InputStream getInputStream()throws IOException: Returns an
* input stream that reads from this open connection.
*/
// DataInputStream in = new
// DataInputStream(yahooConnection.getInputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(
yahooConnection.getInputStream()));
String inputLine;
for (int i = 0; i < 25; i++) {
inputLine = in.readLine();
System.out.println(inputLine);
}
in.close();
}
}
输出结果:
我们先给大家普及一下同步和异步的概念。 引自百度百科:同步指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系。
同步(英语:Synchronization),指对在一个系统中所发生的事件(event)之间进行协调,在时间上出现一致性与统一化的现象。 例如,你想将32轨的音频信号录制在两台16轨磁带机上,则这两个磁带机的磁带传送轴就需要锁定在一起,这个过程就称为同步。如果这两个设备没有进行同步,无论它们开始的时间多么一致,也会由于两台设备机械结构的差异而产生时间漂移 。
异步是指用户程序执行IO操作后便开始做自己的事情,当IO操作已经完成的时候,会得到IO完成的通知。用户程序再接着干。一点儿事儿都不耽误。
心跳,长连接,短连接的关系: 连接建立后,如果不断开连接 ,缺省就是“长连接”,不需要发送数据来保持连接。但网上总说要发“心跳”来维持长连接?为什么?因为有些监听所有端口的防火墙或者电脑管理软件会把超过一定时间没有数据传输的连接当作死连接,这些软件会自动将死连接断开。当有心跳时,不会被这类软件当做死连接。短连接表示当需要与目标通信时创建连接,通讯一结束立刻断开。
TCP设计内部包含了心跳信号。 但是这个心跳信号和平台相关,且默认关闭。我们尽量不要依赖这个功能。
udp用于传输视频流,音频流或传感器流等,少传数据没事。大不了花屏,沙沙声等。如需确认对方还在线,则也需心跳。有时在你的手机上可以设置一个指示灯,如对方在线,可让等亮着,如不在线了,可让灯灭着。比如有时心情不好,即使在线,也不说话。所以没数据,但有心跳。
马克-to-win:我们这章讲的io,实际上是bio,即blocked io(阻塞式io)。sun公司推出bio以后,用户马上就反应,这种bio在多用户时效率比较低。sun公司在1.4版本马上推出了nio,所谓的new io和后来的AIO(异步IO)。
AIO异步非阻塞IO:
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成 以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,因为真正的IO读取或者写入操作已经由 内核完成了。
这么看貌似异步应该比同步的效率高,但是实际性能测下来,并不一定是这样。因为异步要想完成,需要一些额外的线程去工作,这一定会花费额外的资源。
马克-to-win:后来在实际的工作当中,人们发现nio好是好,但使用起来非常困难。有的工程师花了半年以上的时间,终于把自己的nio系统磨合的非常精湛了。突然发觉java界又出了一个新的东西,叫做netty。就是有人觉得nio不好使,把他封装成了一个新技术叫netty。那些使用nio的工程师大呼太坑人了。后来很多人就转向了netty。使了一段时间,发觉业内又出了一个新的东西叫vert.x。这个技术是又把netty封装了一下。里面还有一些aio的东西。这就是java行业状况。更新换代非常快。
我们的策略是把bio学好,基础打牢,之后什么在当时找工作时流行学什么。这就是我的前沿课程有几十门的原因。
作业:
1) 查找某个www.microsoft.com的IP地址。
2)检查http://www.microsoft.com:8080/docs/books/属性。
3)把www.263.com的内容前三行存在一个文件中。
4)用Socket和datagram, 同时实现密码验证。
课外作业:
5)医院系统:肚子疼可能是什麽病?感冒?结石?(在服务器端有一文件存着这些信息)
6)请做一个Web服务器,客户端能打印出一个html文件。
大作业:
编写仿安卓的网络异步回调底层实现代码(AsynNetUtils和handler等),思路:网上有很多异步回调代码,但都是在同一台机器上,找不到在不同机器之间如何异步回调?这里提供一个思路,客户端在主线程发送完消息以后,(启动一额外线程去监听端口动向,当有数据进来时,就回调自己的某一方法即可)同时主线继续该干什么干什么。编写时可参考我参考目录下的网页。
课外阅读:
以下的例子,是一个服务器对多个客户端。我们的客户端程序可以运行很多遍,代表多个客户。 (视频下载) (全部书籍)
/*in this frame work, many clients can access the server with many thread and many socket using only one port,
bbb client use bbb socket with bbb thread, by default, one port can accept 50 socket. */
import java.net.ServerSocket;
import java.io.*;
import java.net.Socket;
public class ThreadServers {
public static void main(String[] args) {
try {
/* public ServerSocket(int port)
throws IOExceptionCreates a server socket, bound to the specified port.
The maximum queue length for incoming connection indications (a request
to connect) is set to 50. If a connection indication
arrives when the queue is full, the connection is refused.
public ServerSocket(int port,int backlog)
throws
IOException Creates a server socket and binds it to the specified local
port number, with the specified backlog.
The maximum queue length for incoming connection indications (a request
to connect) is set to the backlog parameter. If a connection
indication arrives when the queue is full, the connection is refused.
*/
ServerSocket ss = new ServerSocket(8089);
for(;;){
/* here this ServerSocket can accept unlimited client socket.every time after
it accept one, it just come back from the loop, and block here on accept statement.
a new client corresponds to a new socket */
Socket s = ss.accept();
/* this reader get data from socket, then print in the console.
a new client corresponds to a new thread */
ReadThread reader = new ReadThread(s);
// WriteThread writer = new WriteThread(s);
/* WriteThread get the input from console, then write it to the network. */
reader.start();
/*in this case, we have to comment out the following statement because if there
are two clients, if we type in characters in the console, which clients do we
type in to send out to? so the example is made easier not to server to send
out, to make it work, you can make the server to pop up two windows, then one window
corresponds to one client, in window, if you type in some characters, you send them to this client. */
// writer.start();
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
以下是客户端程序:
import java.net.Socket;
import java.net.*;
import java.io.*;
public class ThreadClients {
public static void main(String[] args) {
Socket s = null;
try {
/*a new client corresponds to a new socket*/
s = new Socket("localhost", 8089);
/* this reader get data from socket, then print out in the console. */
/* WriteThread get the input from console, then write it to the network. */
// ReadThread reader = new ReadThread(s);
/*a new client corresponds to a new thread*/
WriteThread writer = new WriteThread(s);
// reader.start();
writer.start();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
/*this reader get data from socket, then print in the console. */
import java.net.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.*;
public class ReadThread extends Thread {
private Socket socket;
public ReadThread(Socket socket) {
this.socket = socket;
}
public void run(){
try {
BufferedReader in = new BufferedReader
(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
while(true){
String line = in.readLine();
out.println(line+" coming back from server");
System.out.println(line);
}
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
/*WriteThread get the input from console, then write it to the network.*/
import java.net.Socket;
import java.io.PrintWriter;
import java.io.*;
public class WriteThread extends Thread {
private Socket socket;
public WriteThread(Socket socket) {
this.socket = socket;
}
public void run(){
try {
/*PrintWriter(OutputStream out, boolean autoFlush)
Create a new PrintWriter from an existing OutputStream.
public PrintWriter(Writer out,boolean autoFlush) Create a new PrintWriter.
autoFlush - A boolean; if true, the println() methods will flush the output buffer */
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader inn = new BufferedReader
(new InputStreamReader(socket.getInputStream()));
BufferedReader in =new BufferedReader(new InputStreamReader(System.in));
for(;;){
String line = in.readLine();
out.println(line);
String linen = inn.readLine();
System.out.println(linen);
}
}
catch (IOException ex) {
}
}
}
when run,start server, then start several clients.