------- android培训、java培训、期待与您交流! ----------
网络编程
一、预备知识:
1、IP地址:
互联网上连接了无数的服务器和计算机,每个主机都有唯一的地址,作为该主机在互联网上的标识,这个地址就是IP地址。IP地址是一种在Internet上给主机编址的方式,也称为网际协议地址。IP地址有4个十进制数组成,每个数的取值范围是0~255,各数之间用改一个点号(.)分割。例如:202.102.2.34。
2、端口:常用端口(Web服务:80 Tomcat:8080 Mysql:3306)
端口是计算机I/O信息的接口。计算机炼乳通信网络或者Internet需要一个端口,这个端口不是物理端口,而是一个有16位数标识的逻辑端口,而且这个端口是TCP/IP协议的一部分。通过这个端口就可以进行信息的I/O。端口号是由一个16位2进制的数,其取值范围是0~65535。在实际中,计算机上的0~1024端口都被保留为系统服务,在程序中不应让自己设计的服务占用这些端口
3、通信协议(TCP/UDP)
协议是描述数据交换时必须遵循的规则和格式。网络协议规定了在网络上传输的数据类型,以及怎样解释这些数据类型和怎样请求传输这些数据。Internet的通信协议是一种四层协议模型,从下至上分别是链路层(包括OSI七层模型中的物理层与数据链路层)、网络层、传输层、应用层。运行与计算机中的网络应用利用传输层协议——传输控制协议(Transmission Control Protocol,TCP)或用户数据报协议(User Datagram Protocol,UDP)进行通信。
TCP是一种基于连接的传输协议,它为两个计算机之间提供了点到点的可靠数据流,保证从连接的一端发送的数据能够以正确的顺序到达另一端。应用层的常用协议,如HTTP、FTP等都是需要可靠通信通道的协议,数据在网络上的发送和接收顺序对于这些应用来说是至关重要的。
与TCP不同的是UDP不是基于连接的,而是为应用层提供了一种非常简单、高效的传输服务。UDP从一个应用程序向另一个应用程序发送独立的数据报,但并不能保证这些数据报一定能够到达对方,并且这些数据报的传输次序无保障,后发送的数据报可能先到目的地。因此,使用UDP时,任何必需的可靠性都必须由应用程序自身提供。UDP适用于对通信可靠性要求低且对通信性能要求高的应用,例如域名系统DNS、路由信息协议等都建立在UDP的基础上。
UDP的特点(重点):
1、将数据及源和目的封装在数据包中,不需要建立连接。
2、每个数据报的大小限制在64K内
3、因为是不需连接,所以不可靠
4、不需要建立连接,速度快。
TCP的特点(重点):
1、建立连接,形成传输数据的通道。
2、在连接中进行大量数据传输。
3、通过3次握手完成连接,是可靠协议
4、必须建立连接,效率较低。
4、网络模型
OSI模型:
①分层模型:
TCP模型:
①分层模型:
二、Java中对网路三要素的定义
InetAddress类:————IP
该类没有构造函数,只能通过该类的静态函数获取InetAddress对象。
1、获取本机的InetAddress
①static InetAddress getLocalHost() 通过此函数可以获取本机的InetAddress对象,该对象的字符串表现形式是主机名称和IP地址。
②通过获取的InetAddress对象可以调用getHostName()和getHostAddress()分别获取该对象的名称和IP地址。
2、获取其他计算机的InetAddress对象
①通过 InetAddress getByName(String host)函数可以通过主机名称获取该主机的InetAddress对象。
②通过InetAddress[] getAllByName(String host)在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组
Socket机制:
1、Socket就是为网络服务提供的一种机制。
2、通信的两端必须都要有Socket。
3、网络通信其实就是Socket间通信。
4、数据在两个Socket间通过IO传输。
三、1不同协议建立Socket的方法(UDP Socket的建立)
1、UDP的Socket的建立————DatagramSocket类
此类表示用来发送和接收数据报的套接字。数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。
通过void send(DatagramPacket p);方法发送数据,通过void receive(DatagramPacket p);方法接收数据。
DatagramPacket类:此类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
在创建对象时,接收的DatagramPacket和发送DatagramPacket的定义是不同的。
接收时的定义:DatagramPacket(byte[] buf, int length) ——
发送时的定义:DatagramPacket(byte[] buf, int length, InetAddress address, int port)——所有的数据发送时都是字节数据,所以必须把发送的东西转换成字节数据。还要将目的地址和端口传进要封装的对象。
2、建立发送和连接的步骤:
发送:
1、通过DatagramSocket类创建UDP服务。
2、确定要发送的数据,将数据转换成字节流,装在一个字节数组里。
3、将要发送的数据封装成DatagramPacket对象,在该对象里还要包括发送的大小、IP地址对象(InetAddress)、端口号
4、通过DatagramSocket对象的send方法发送。
5、关闭UDP服务。
public class UDPSendDemo { public static void main(String[] args) throws Exception { //1、建立UDP服务,通过DatagramSocket对象 DatagramSocket UDPSend_socket = new DatagramSocket(); //2、确定发送的数据,并封装成对象 byte[] buf = "你好".getBytes(); //(DataramePacket的发送应该在其中封装要被传输的数组、要发送的数据长度、IP、发送的端口) DatagramPacket UDPSend_packet = new DatagramPacket(buf,buf.length,InetAddress.getByName("117.139.158.68"),8000); //3、发送数据 UDPSend_socket.send(UDPSend_packet); //4、关闭资源 UDPSend_socket.close(); } }
接收:
1、定义一个UDPSocket服务,监听一个端口。
2、定义一个数据包,因为要存储接收到的字节数据。
因为数据包对象中有更多的功能可以提取字节数据中的不同数据信息。
3、通过Socket服务的receive方法将收到的数据存入已经定义好的数据包中。(该方法是阻塞式方法)
4、通过数据包对象的功能,将这些不同的数据取出。
5、关闭资源,并让先前定义的UDPSocket的引用指向null。
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UDPReceiveDemo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub while(true) { DatagramSocket UDPRec_socket = new DatagramSocket(8001); byte[] buf = new byte[1024]; DatagramPacket UDPRec_packet = new DatagramPacket(buf,buf.length); UDPRec_socket.receive(UDPRec_packet); String ip = UDPRec_packet.getAddress().getHostAddress(); String string = new String(UDPRec_packet.getData(),0,UDPRec_packet.getLength()); System.out.println("发送者的IP:"+ip+"\r\n发送的内容是:"+string +"\r\n发送的端口Port:"+UDPRec_packet.getPort()+"\r\n"); UDPRec_socket.close(); UDP_socket=null; } } }
利用UDP传输协议做一个聊天的小程序
原理:1、发送消息和接收消息没有先后,所以发送消息和接收消息的程序,都要封装在独自的线程中。
2、接收消息的程序要是循环接收的,这样才能接收到对方发来的消息。
程序:
import java.io.*; import java.net.*; class IS implements Runnable { private DatagramSocket dss; public IS(DatagramSocket dss) { this.dss = dss; } public void run() { try { BufferedReader ISd = new BufferedReader(new InputStreamReader(System.in)); for (String im=ISd.readLine(); im!=null; im=ISd.readLine()) { byte [] ISbuf = im.getBytes(); DatagramPacket ISpacket = new DatagramPacket(ISbuf,ISbuf.length,InetAddress.getByName("10.11.116.12"),9090); dss.send(ISpacket); } } catch (Exception e) { } } } class IR implements Runnable { private DatagramSocket dsr; public IR(DatagramSocket dsr) { this.dsr = dsr; } public void run() { try { while (true) { byte[] IRbuf = new byte[1024]; DatagramPacket IRpacket = new DatagramPacket(IRbuf,IRbuf.length); dsr.receive(IRpacket); String ip = IRpacket.getAddress().getHostAddress(); String data =new String( IRpacket.getData(),0,IRpacket.getLength()); System.out.println(ip+"发来:"+data); } } catch (Exception e1) { } } } class Chat { public static void main(String[] args) throws Exception { DatagramSocket ds = new DatagramSocket(); DatagramSocket dr = new DatagramSocket(9090); new Thread(new IS(ds)).start(); new Thread(new IR(dr)).start(); } }
2、 TCP传输(Socket)
1、TCP的传输依赖于连接,分为客户端(Socket)和服务端(ServerSocket)。
1、客服端:通过查阅Socket对象,发现在建立客户端的时候,就可以去连接指定的主机。因为TCP是面向连接的,所以在Socket服务时,就要有服务端的存在,并连接成功。形成通路后,在该通道进行数据的传输。
步骤:
1、创建 Socket服务,并指定要连接的主机和端口。在连接成功后会自动建立两个流(发送和读取)
可以通过Socket(InetAddress address,int port)
也可以通过Socket(String host, int port)
2、通过创建的Socket对象调用getOutputStream();获取输出流。
3、该输出流将要传输的数据写入。
4、通过Socket对象调用getInputStream()方法,获取输入流,来接收服务端反馈的信息。
5、关闭服务。
import java.io.*; import java.net.*; class TCPClient { public static void main(String[] args) throws Exception { //建立客户端的Socket Socket tcpc = new Socket("10.11.116.12",9090); while(true) { //获取该服务的输出流 OutputStream tcpout = tcpc.getOutputStream(); tcpout.write("TCP传输".getBytes()); //关闭连接 tcpc.close(); } } }
2、服务端:
步骤:
1、建立ServerSokect服务,并监听一个端口。
2、通过ServerSokect的accept方法获取客户端的Socket,如果没有连接就等(阻塞式方法)
3、客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客服端对象的读取流发过来的数据。
4、关闭
import java.io.*; import java.net.*; class TCPServer { public static void main(String[] args)throws Exception { ServerSocket tcps = new ServerSocket(9090); while(true) { Socket tcpc = tcps.accept(); String ip = tcpc.getInetAddress().getHostName(); System.out.println("TCP连接"+ip); InputStream in = tcpc.getInputStream(); byte[] buf =new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); tcpc.close(); } } }
注意:
1、如果传输的是字符数据,使用BufferedRead()方法和BufferedWriter()方法时,在传输时,必须在用newLine()方法去写入一个换行符,否则服务端和接收端,都会一直等下去。
2、在循环读写的时候,可以加结束标记,来是结束读取的循环操作。
3、还可以使用客户端的Socket对象,调用shutdownOutput()方法。结束客户端的输出流相当于,给流中加上一个结束标记。
2、利用TCP传输的原理编的小程序
1、进行网络复制的小程序
要求:
从客户端上传一个图片到服务端,服务接收后把图片保存到服务器定义的文件夹,并反馈给服务端“上传成功“的字样。
原理:和TCP传输原理一样,只是读取的文件是从硬盘上,并且服务端将读取的文件保存到硬盘上。其实就是多出两个流(1、读取硬盘上文件的读取流,2、往硬盘上保存文件的输出流)
程序:
import java.io.*; import java.net.*; //定义上传图片的客户端PicClient class PicClient { public static void main(String[] args)throws Exception { while(true) { //1、创建服务端的Socekt对象并绑定在指定的ip地址和指定的端口 Socket picc = new Socket("127.0.0.1",9090); //2、将要读取的图片封装在一个文件对象中 File mp3 = new File("F:\\合同.jpg"); //3、建立读取文件的流对象 BufferedInputStream picin = new BufferedInputStream(new FileInputStream(mp3)); //4、获取上传到服务端的流对象 OutputStream piccpw = picc.getOutputStream(); //5、读取文件,并上传到服务端 for (int i=picin.read();i!=-1 ;i=picin.read() ) { piccpw.write(i); } //6、给服务端发送上传完毕的指令 picc.shutdownOutput(); //7、获取读取服务器反馈信息的流对象 BufferedReader ini =new BufferedReader( new InputStreamReader(picc.getInputStream())); //8、读取反馈的内容,并打印在控制台上 for (String string=ini.readLine();string!=null ;string =ini.readLine() ) { System.out.println(string); } //9、关闭用于读取文件的流对象和客户端 picin.close(); picc.close(); } } } //服务端 class PicServer { public static void main(String[] args)throws Exception { //1、创建服务端的ServerSocket对象,并绑定监视的端口 ServerSocket s = new ServerSocket(9090); while(true) { //2、获取与该服务器连接的客户端 Socket picc = s.accept(); //3、打印出客户端的IP地址 System.out.println(picc.getInetAddress().getHostAddress()); //4、获取读取客户端上传的数据的流对象 BufferedInputStream bis = new BufferedInputStream( picc.getInputStream()); //5、建立输出流,通过此流将读取到的数据保存在硬盘上 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("server1.jpg")); //6、读取并写入到输出流保存到硬盘 for (int i=bis.read(); i!=-1;i=bis.read() ) { bos.write(i); bos.flush(); } //7、获取到客户端的输出流用于给客户端反馈消息。 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(picc.getOutputStream())); PrintWriter pr = new PrintWriter(bw,true); pr.println("上传成功"); //8、给客户端发送反馈信息完毕的指令 picc.shutdownOutput(); //9、关闭客户端的连接 picc.close(); } } }
2、TCP客户端并发登录
要求:
1、客户端输入用户名,如果用户名存在则显示欢迎,如果不存在,则显示不存在。
2、如果输入错误超过3次,则客户端自动关闭。
3、用户名信息存贮在了一个文本文件中
原理:同上。
程序:
import java.io.*; import java.net.*; class Login { public static void main(String[] args)throws Exception { Socket client = new Socket("127.0.0.1",9090); BufferedReader buf = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(client.getOutputStream(),true); BufferedReader bufin = new BufferedReader(new InputStreamReader(client.getInputStream())); for (int x=0;x<3 ;++x ) { String line = buf.readLine(); if ("".equals(line)) { System.out.println(line+"line"); break; } System.out.println(line+"line"); out.println(line); String info = bufin.readLine(); System.out.println("info:"+info); if (info.contains("欢迎")) { break; } } buf.close(); } } class SerThread implements Runnable { private Socket ser; SerThread(Socket ser) { this.ser = ser; } public void run() { String ip = ser.getInetAddress().getHostAddress(); System.out.println(ip+"is conneting"); try { for (int x=0;x<3 ;++x ) { BufferedReader bufin = new BufferedReader(new InputStreamReader(ser.getInputStream())); String name = bufin.readLine(); BufferedReader bufr = new BufferedReader(new FileReader("name.txt")); PrintWriter out = new PrintWriter(ser.getOutputStream(),true); boolean flag=false; for (String name1=bufr.readLine(); name1!=null;name1=bufr.readLine() ) { if(name1.equals(name)) { flag = true; break; } } if (flag) { System.out.println(name+":已登录"); out.println(name+":欢饮光临"); break; } else { System.out.println(name+":正在尝试登录"); out.println("用户名不存在"); } } ser.close(); } catch (Exception e ) { throw new RuntimeException("校验失败"); } } } class Ser { public static void main(String[] args) throws Exception { ServerSocket ser = new ServerSocket(9090); while (true) { Socket cli = ser.accept(); new Thread(new SerThread(cli)).start(); } } }
3、通过客户端访问服务器
1、浏览器与自定义服务器
1、自定义服务端:向连接上的客户端反馈一个消息(”欢迎“)。和之前服务端的定义的一样。
2、浏览器:在地址栏内输入ip地址:端口号。即可访问。
2、自定义客户端与Tomcat服务器
1、HTTP请求消息头——浏览器给服务器发送了什么信息,才会请求到服务器的信息呢?
自定义浏览器:自定义浏览器就需要将请求消息头发送到服务器。其他不变。
我们通过在服务端将浏览器发过来的信息打印在控制台上,来观察。通过以下程序来将浏览器发送的数据打印出来。
import java.io.*; import java.net.*; class TCPServer { public static void main(String[] args)throws Exception { ServerSocket tcps = new ServerSocket(9090); while(true) { Socket tcpc = tcps.accept(); String ip = tcpc.getInetAddress().getHostAddress(); System.out.println(ip+"连接"); InputStream in = tcpc.getInputStream(); byte[] buf =new byte[1024]; int len = in.read(buf); System.out.println(new String(buf,0,len)); PrintWriter pw = new PrintWriter(tcpc.getOutputStream(),true); pw.println("hello,客户端"); tcpc.shutdownOutput(); tcpc.close(); } } }
在服务器端打印的信息有
GET / HTTP/1.1 Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave -flash, application/xaml+xml, application/x-ms-xbap, application/x-ms-applicatio n, application/QVOD, application/QVOD, application/vnd.ms-xpsdocument, applicati on/msword, application/vnd.ms-powerpoint, application/vnd.ms-excel, */* Accept-Language: zh-cn User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET 4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.2) Accept-Encoding: gzip, deflate Host: 127.0.0.1:9090 Connection: Keep-Alive
Accept:支持的文件类型Accept-Language:zh-cn支持的语言
User-Agent:用户的信息
Accept-Encoding:支持的压缩方式。
Host:访问的主机名以及端口号。
Connection:
在消息头后要空两行。
2、HTTP应答消息头——服务器给浏览器发送的应答消息头,通过以下自定义浏览器访问Tomcat服务器可获取此消息头
import java.io.*; import java.net.*; class MyIE { public static void main(String[] args)throws Exception { Socket s = new Socket("192.168.1.254",8080); PrintWriter out = new PrintWriter(s.getOutputStream(),true); out.println("GET /myweb/demo.html HTTP/1.1"); out.println("Accept: */*"); out.println("Accept-Language: zh-cn"); out.println("Host: 192.168.1.254:11000"); out.println("Connection: closed"); out.println(); out.println(); BufferedReader bufr = new BufferedReader(new InputStreamReader(s.getInputStream())); String line = null; while((line=bufr.readLine())!=null) { System.out.println(line); } s.close(); } }
获取到的应答消息头如下:
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Accept-Ranges: bytes ETag: W/"3282-1321936820000" Last-Modified: Tue, 22 Nov 2011 04:40:20 GMT Content-Type: text/html Content-Length: 3282 Date: Fri, 31 May 2013 06:53:07 GMT Connection: close
HTTP/1.1:http协议的版本。200是响应状态码,表示成功。Last-Modified:最后修改的日期
Content-Type:发送的文本类型
Content-Length:文本的大小
Date:时间
Connection:连接状态。
四、URL类与URLConnection
URL:在之前我们自定义浏览器的时候,必须把URL地址手动的拆成各个段。而URL类中有这些方法,使我们不必在手动的去拆分网络地址。
1、URL解析所用到的方法:
String getProtocol() : 获取此 URL 的协议名称。
String getFile() :获取此 URL 的文件名。
String getHost() : 获取此 URL 的主机名(如果适用)。
String getPath() :获取此 URL 的路径部分。
int getPort() : 获取此 URL 的端口号。
String getProtocol() : 获取此 URL 的协议名称。
String getQuery() : 获取此 URL 的查询部
注意:当我们没有指定访问的端口号时,则getPort返回的值是-1。所以在程序中我们需要判断一下,如果没有指定端口getPort==-1,就将其默认为80端口。
方法示例:
import java.net.*; class URLDemo { public static void main(String[] args) throws MalformedURLException { URL url = new URL("http://127.0.0.1/myweb/index.html?name=haha&age=30"); System.out.println("getProtocol() :"+url.getProtocol()); System.out.println("getHost() :"+url.getHost()); System.out.println("getPort() :"+url.getPort()); System.out.println("getPath() :"+url.getPath()); System.out.println("getFile() :"+url.getFile()); System.out.println("getQuery() :"+url.getQuery()); int port = getPort(); if(port==-1) port = 80; } }
URLConnection对象:
1、URLConnection
是所有类的超类,它代表应用程序和 URL 之间的通信链接。此类的实例可用于读取和写入此 URL 引用的资源。通常,创建一个到 URL 的连接需要几个步骤:
2、URLConnection内容包括协议,目的地址。在其内部建立了连接。
- 通过在 URL 上调用
openConnection
方法创建连接对象。- 处理设置参数和一般请求属性。
- 使用
connect
方法建立到远程对象的实际连接。- 远程对象变为可用。远程对象的头字段和内容变为可访问。
我们可以通过URLConnection的对象来获取输入流获取信息。
——InputStream getInputStream() :返回从此打开的连接读取的输入流。
——OutputStream getOutputStream() :返回写入到此连接的输出流。
3、所以在建立应用层的网络通信的时候不用在建立Socket对象。直接通过Connection对象的方法获取。
例:
import java.net.*; import java.io.*; class URLConnectionDemo { public static void main(String[] args) throws Exception { //1、建立URL对象 URL url = new URL("http://127.0.0.1:8080/myweb/index.html"); //2、通过URL对象的openConnection()方法获取连接对象URLConnection URLConnection conn = url.openConnection(); System.out.println(conn); //3、通过连接对象获取输入流来获取服务器反馈的信息 InputStream in = conn.getInputStream(); //4、定义一个数组来装这些数据 byte[] buf = new byte[1024]; int len = in.read(buf); //5、打印获取到的信息 System.out.println(new String(buf,0,len)); } }