(一)网络编程和网站编程区分开
网站编程是建立在网络编程之上,对网页和动态网页的编写。网络编程更底层一些,server服务器端和客户端都需要自己编写,网络的协议需要自己来确定。
其中:qq,微信,魔兽这是网络编程。
(二)网络基础概念
1、计算机网络
把分布在不同地理区域的计算机和专门的外部设备用通信线路互连成一个网络系统,使众多计算机可以方便的相互专递信息,共享资源。
2、网络通信协议
计算机网络中实现通信必须有一些约定即通信协议,对速率,传输代码,出错控制等制定标准。
3、网络通信接口
为了使两个结点之间能进行对话,必须在它们之间建立通信工具(即接口),使彼此之间能进行信息交换。
接口包括两部分:硬件装置:实现结点之间的信息传送 软件装置:规定双方进行通信的约定协议
(三)通信协议分层的思想
1、为什么要分层
底层的通信比较复杂,需要进行抽离,采用分层的模式,更加利于开发和扩展。
由于结点之间联系很复杂,在制定协议时,把复杂成份分解成一些简单的成份,再将他们复合起来。最常用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与再一层不发生关系。各层互不影响,利于系统的开发和扩展。
2、通信协议的分层规定
把用户应用程序作为最高层,把物理通信线路作为最底层,将其间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。
(四)两个重要的参考模型
OSI(open system interconnection ):开放系统互联
TCP/IP参考模型
应用层 ------------ Telnet(Internet远程登录服务的标准协议和主要方式)、FTP(文本传输协议)、HTTP(超文本传送协议)
传输层 ------------ TCP(传输控制协议)、UDP(用户数据报协议)
网络层 ------------ IP(网络之间的互联协议)
数据的传输过程:应用层将数据编码包装,发给传输层,同样在发给网络层,网络层再发费数据链路层,最后包装发给物理层进行传输,传输到目标地址,将数据一层一层解码。
(五)IP协议(推荐书:TCP/IP协议)
IP协议是网络层的主要协议,支持网间互连的数据包通信,是一个32位的地址,地址有两部分组成,一部分为网络地址,另一部分为主机地址。
作用:为每台机器提供了独一无二的IP地址。IP提供不可靠的,无连接的数据传送服务。不可靠指它不能保证IP数据报能成功到达目的地。没有找到目标地址,数据报将会丢失。
注意:IP地址是用4个字节表示的,每个字节最大的数就到255即(2的8次方-1)
弄明白:网关和子网掩码
1、子网掩码
子网掩码也是一个32位地址,只有一个作用:就是将某个IP地址划分为网络位和主机位两部分。主机位就是多少台设备。转换成二进制位数,二者相加等于32.
一般我们知道需要N个主机,把N换算成二进制,看其所占位数,用32减去该位数,结果就是网络位。转换成十进制就是子网掩码。
注意:子网的划分是通过改变子网掩码的位数来实现的。
例子:
100大于2的6次方,小于2的7次方,所以主机位数取7位。那么网络位数就是32-7=25位。25位的子网掩码11111111.11111111.11111111.10000000 换算成10进制的就是255. 255. 255. 128,这就是第一个子网的子网掩码,网络号为192.168.0.0/25,网络地址192.168.0.0,主机地址192.168.0.1~192.168.0.126,广播地址192.168.0.127
主机地址192.168.0.1~192.168.0.126,广播地址192.168.0.127
ip地址是否在同一网段,IP地址和子网掩码做与运算,如果结果相同就是在同一网段,同一网段就可以自由通信。
网关就相当于小区看门的大爷,同一网段就是指小区内的住户大家都认识,可以自由交流,但是小区内的要和小区外的人交流就只能通过大爷来传话。
2、网关
网关(Gateway)就是一个网络连接到另一个网络的“关口”。本质就是:一个网络通向其他网络的IP地址。
不同体系结构或者协议的网络之间在进行通信时,网关对收到的信息要重新打包,以适应目的系统的需求。
网关:就是外网和内网的一个关口,控制着外网和内网的信息交互。
(六)TCP协议和UDP协议(重点)
通过IP为每台机器建立地址,将每台机器连接成网络,就可以建立连接进行信息传输。
TCP(transmission control protocol):是一种面向连接的协议,提供可靠的字节流通信的协议。
注意:数据不能丢失,每个数据必须发送到,第一个数据没有发送到,第二个数据不会发送,必须等到第一个数据发送完成。安全可靠但是速度比较慢。
UDP(user data protocol):向相应程序提供了一种发送封装的原始IP数据报 的方法,发送时不需要建立连接,是一种不可靠的连接。
注意:数据可以丢失,速度快。例如:英雄联盟在游戏中就是采用的UDP传输协议,游戏中出现卡顿现象就是数据包丢失导致的,保证了游戏的流畅性。
(七)端口号(重点)
端口号在系统中占2个字节,因此每台机器最多2的16次方:65536个端口号。
作用:用来区分同一台电脑上的不同应用程序。类似IP地址来区分每台电脑。
端口号的理论值范围是从0到65535,公认的是0-1023 ,注册端口是1024-49152,还有随机动态端口是1024-65535,共是65536个端口。
注意:公认端口号0-1023这些端口已明确的绑定一些固定服务,如:HTTP:80:www服务。 FTP:FTP使用的端口有20和21。20端口用于数据传输,21端口用于控制信令的传输,控制信息和数据能够同时传输,这是FTP的特殊这处。FTP采用的是TCP连接。
要给自己编写的应用配置端口号,要分配1024以上的端口号,避免和固定服务的端口号冲突。
同时区分开:TCP端口和UDP端口,两种类型都有65536个端口。
(八)Socket编程(重点:应用广泛,各个平台都支持:windows,linux,unix)
两个java应用程序可通过一个双向的网络通信连接实现数据交换,这个双向链路的一端就称为一个Socket。
注意:socket:有插座的意思,socket编程就是实现两个应用能够通信,就相当于一条线,一端用插头连接客户端机器,另一端用插头连接服务器机器,实现二者的通信。
1、Socket通常用来实现client-server连接
2、java.net包中定义了两个类Socket和ServerSocket,分别用来实现双向连接的client和server端
3、建立连接时所需的寻址信息为远程计算机的IP地址和端口号(Port number)
注意:socket编程,要先启动服务器端,在启动客户端。
(九)针对网络通信的不同层次,Java提供了不同的API,其提供的网络功能有四大类:
1、InetAddress类:用于标识网络上的硬件资源,主要是IP地址
InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址。该类没有构造方法。
1 //获取本机的InetAddress实例
2 InetAddress address =InetAddress.getLocalHost();
3 address.getHostName();//获取计算机名
4 address.getHostAddress();//获取IP地址
5 byte[] bytes = address.getAddress();//获取字节数组形式的IP地址,以点分隔的四部分
6
7 //获取其他主机的InetAddress实例
8 InetAddress address2 =InetAddress.getByName("其他主机名");
9 InetAddress address3 =InetAddress.getByName("IP地址");
2、URL类:统一资源定位符,通过URL可以直接读取或写入网络上的数据
(1)URL(Uniform Resource Locator)统一资源定位符,表示Internet上某一资源的地址,协议名:资源名称
1 //创建一个URL的实例
2 URL baidu =new URL("http://www.baidu.com");
3 URL url =new URL(baidu,"/index.html?username=tom#test");//?表示参数,#表示锚点
4 url.getProtocol();//获取协议
5 url.getHost();//获取主机
6 url.getPort();//如果没有指定端口号,根据协议不同使用默认端口。此时getPort()方法的返回值为 -1
7 url.getPath();//获取文件路径
8 url.getFile();//文件名,包括文件路径+参数
9 url.getRef();//相对路径,就是锚点,即#号后面的内容
10 url.getQuery();//查询字符串,即参数
(2)使用URL类读取网页的内容
通过URL对象的openStream()方法可以得到指定资源的输入流,通过流能够读取或访问网页上的资源
1 //使用URL读取网页内容
2 //创建一个URL实例
3 URL url =new URL("http://www.baidu.com");
4 InputStream is = url.openStream();//通过openStream方法获取资源的字节输入流
5 InputStreamReader isr =newInputStreamReader(is,"UTF-8");//将字节输入流转换为字符输入流,如果不指定编码,中文可能会出现乱码
6 BufferedReader br =newBufferedReader(isr);//为字符输入流添加缓冲,提高读取效率
7 String data = br.readLine();//读取数据
8 while(data!=null){
9 System.out.println(data);//输出数据
10 data = br.readerLine();
11 }
12 br.close();
13 isr.colose();
14 is.close();
(十)TCP编程
Java中基于TCP协议实现网络通信的类:客户端的Socket类和服务器端的ServerSocket类
服务器端:
① 创建ServerSocket对象,绑定监听端口
② 通过accept()方法监听客户端请求
③ 连接建立后,通过输入流读取客户端发送的请求信息
④ 通过输出流向客户端发送乡音信息
⑤ 关闭相关资源
注意:1、accept()方法是一个阻塞方法:就是一直等待,直到有客户端连接到服务器端上的指定端口。这个过程中会一直占用资源,其他的客户端想要连接,只能等到该客户端结束,释放端口,其他客户端才可以使用。效率很低并且浪费资源。
(重点)
2、readLine()是读取到没有数据时就返回null(因为其它read方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null。
使用socket之类的数据流时,要避免使用readLine(),以免为了等待一个换行/回车符而一直阻塞。
readLine()在数据流发生异常或者另一端被close()掉时,也会返回null值。
String valueString = null;
while ((valueString=bf.readLine())!=null){
System.out.println(valueString);
}
1 /**
2 * 基于TCP协议的Socket通信,实现用户登录,服务端
3 */
4 //1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
5 ServerSocket serverSocket =newServerSocket(10086);//1024-65535的某个端口
6 //2、调用accept()方法开始监听,等待客户端的连接
7 Socket socket = serverSocket.accept();
8 //3、获取输入流,并读取客户端信息
9 InputStream is = socket.getInputStream();
10 InputStreamReader isr =newInputStreamReader(is);
11 BufferedReader br =newBufferedReader(isr);
12 String info =null;
13 while((info=br.readLine())!=null){
14 System.out.println("我是服务器,客户端说:"+info);
15 }
16 socket.shutdownInput();//关闭输入流
17 //4、获取输出流,响应客户端的请求
18 OutputStream os = socket.getOutputStream();
19 PrintWriter pw = new PrintWriter(os);
20 pw.write("欢迎您!");
21 pw.flush();
22
23
24 //5、关闭资源
25 pw.close();
26 os.close();
27 br.close();
28 isr.close();
29 is.close();
30 socket.close();
31 serverSocket.close()
客户端:
① 创建Socket对象,指明需要连接的服务器的地址和端口号
② 连接建立后,通过输出流想服务器端发送请求信息
③ 通过输入流获取服务器响应的信息
④ 关闭响应资源 1 //客户端
2 //1、创建客户端Socket,指定服务器地址和端口
3 Socket socket =newSocket("localhost",10086);
4 //2、获取输出流,向服务器端发送信息
5 OutputStream os = socket.getOutputStream();//字节输出流
6 PrintWriter pw =newPrintWriter(os);//将输出流包装成打印流
7 pw.write("用户名:admin;密码:123");
8 pw.flush();
9 socket.shutdownOutput();
10 //3、获取输入流,并读取服务器端的响应信息
11 InputStream is = socket.getInputStream();
12 BufferedReader br = new BufferedReader(new InputStreamReader(is));
13 String info = null;
14 while((info=br.readLine())!null){
15 System.out.println("我是客户端,服务器说:"+info);
16 }
17
18 //4、关闭资源
19 br.close();
20 is.close();
21 pw.close();
22 os.close();
23 socket.close();
应用多线程实现服务器与多客户端之间的通信
① 服务器端创建ServerSocket,循环调用accept()等待客户端连接
② 客户端创建一个socket并请求和服务器端
③ 服务器端接受苦读段请求,创建socket与该客户建立专线连接
④ 建立连接的两个socket在一个单独的线程上对话
⑤ 服务器端继续等待新的连接 1 //服务器线程处理
2 //和本线程相关的socket
3 Socket socket =null;
4 //
5 public serverThread(Socket socket){
6 this.socket = socket;
7 }
8
9 publicvoid run(){
10 //服务器处理代码
11 }
12
13 //============================================
14 //服务器代码
15 ServerSocket serverSocket =newServerSocket(10086);
16 Socket socket =null;
17 int count =0;//记录客户端的数量
18 while(true){
19 socket = serverScoket.accept();
20 ServerThread serverThread =newServerThread(socket);
21 serverThread.start();
22 count++;
23 System.out.println("客户端连接的数量:"+count);
24 }
(十一)UDP编程(不区分客户端和服务器端)
UDP协议(用户数据报协议)是无连接的、不可靠的、无序的,速度快
进行数据传输时,首先将要传输的数据定义成数据报(Datagram),大小限制在64k,在数据报中指明数据索要达到的Socket(主机地址和端口号),然后再将数据报发送出去
DatagramPacket类:表示数据报包(用于接收数据的包裹)
DatagramSocket类:进行端到端通信的类。
receive(DatagramPacket packet )方法:用于接收发出的数据。
send(DatagramPacket packet )方法:从此套接字中发送数据报包
1、服务器端实现步骤
① 创建DatagramSocket,指定端口号
② 创建DatagramPacket
③ 接受客户端发送的数据信息
④ 读取数据 1 //服务器端,实现基于UDP的用户登录
2 //1、创建服务器端DatagramSocket,指定端口
3 DatagramSocket socket =new datagramSocket(10010);
4 //2、创建数据报,用于接受客户端发送的数据
5 byte[] data =newbyte[1024];//
6 DatagramPacket packet =newDatagramPacket(data,data.length);
7 //3、接受客户端发送的数据
8 socket.receive(packet);//此方法在接受数据报之前会一致阻塞
9 //4、读取数据
10 String info =newString(data,o,data.length);
11 System.out.println("我是服务器,客户端告诉我"+info);
12
13
14 //=========================================================
15 //向客户端响应数据
16 //1、定义客户端的地址、端口号、数据
17 InetAddress address = packet.getAddress();
18 int port = packet.getPort();
19 byte[] data2 = "欢迎您!".geyBytes();
20 //2、创建数据报,包含响应的数据信息
21 DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);
22 //3、响应客户端
23 socket.send(packet2);
24 //4、关闭资源
25 socket.close();
-
2、客户端实现步骤
① 定义发送信息
② 创建DatagramPacket,包含将要发送的信息
③ 创建DatagramSocket
④ 发送数据
-
1 //客户端 2 //1、定义服务器的地址、端口号、数据 3 InetAddress address =InetAddress.getByName("localhost"); 4 int port =10010; 5 byte[] data ="用户名:admin;密码:123".getBytes(); 6 //2、创建数据报,包含发送的数据信息 7 DatagramPacket packet = newDatagramPacket(data,data,length,address,port); 8 //3、创建DatagramSocket对象 9 DatagramSocket socket =newDatagramSocket(); 10 //4、向服务器发送数据 11 socket.send(packet); 12 13 14 //接受服务器端响应数据 15 //====================================== 16 //1、创建数据报,用于接受服务器端响应数据 17 byte[] data2 = new byte[1024]; 18 DatagramPacket packet2 = new DatagramPacket(data2,data2.length); 19 //2、接受服务器响应的数据 20 socket.receive(packet2); 21 String raply = new String(data2,0,packet2.getLenth()); 22 System.out.println("我是客户端,服务器说:"+reply); 23 //4、关闭资源 24 socket.close();
结论:实际应用中,为避免同步阻塞方法带来的资源浪费,采用的是异步网络编程可以极大的提高效率,服务器没有工作可做的时候,会等在select调用上,不会占用系统资源,而当不同的条件满足时,又可以第一时间被唤醒,执行相应的操作。
(十二)java异步编程
jdk1.8之前的Future有点鸡肋,并不能实现真正的异步,需要阻塞的获取结果,或者不断的轮询,通常我们希望当线程执行完一些耗时的任务后,能够自动的通知我们结果,很遗憾这在原生jdk1.8之前是不支持的,但是我们可以通过第三方的库实现真正的异步回调
直到jdk1.8才算真正支持了异步操作,其中借鉴了某些框架的实现思想,但又有新的功能,同时在jdk1.8中提供了lambda表达式,使得java向函数式语言又靠近了一步。借助jdk原生的CompletableFuture可以实现异步的操作,同时结合lambada表达式大大简化了代码量。
代码例子如下:
[java] view plain copy
- package netty_promise;
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.function.Supplier;
- /**
- * 基于jdk1.8实现任务异步处理
- *
- * @author Administrator
- *
- */
- public class JavaPromise {
- public static void main(String[] args) throws Throwable, ExecutionException {
- // 两个线程的线程池
- ExecutorService executor = Executors.newFixedThreadPool(2);
- //jdk1.8之前的实现方式
- CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
- @Override
- public String get() {
- System.out.println("task started!");
- try {
- //模拟耗时操作
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return "task finished!";
- }
- }, executor);
- //采用lambada的实现方式
- future.thenAccept(e -> System.out.println(e + " ok"));
- System.out.println("main thread is running");
- }
- }
以上的三种实现方式类似下面的过程:
java异步编程参考程序:http://blog.csdn.net/tangyongzhe/article/details/49851769
http://blog.csdn.net/yjp19871013/article/details/53635116
http://blog.csdn.net/yjp19871013/article/details/53607388