网络编程
一、网络编程基础
java的网络通信可以使用TCP、IP和UDP等协议。在真正进行java网络编程之前,对这些协议进行简单的介绍。
1、TCP协议
TCP(Transmission Control Protocol),指的是传输控制协议,它是网络传输层的协议,主要负责数据的分组和重组。TCP协议提供了一种可靠的数据传输服务,它是面向连接的,大多数的网络应用程序都是用TCP协议来实现传输层。使用TCP协议创建一个网络应用程序非常容易,它可以保证数据传输的时间、顺序和内容正确无误。但是使用TCP 需要大量的网络开销,所以如果希望实现更高效的传输,使用TCP协议就不合适了。TCP所提供服务的主要特点如下:
(1)面向连接的传输(可靠性的保证)
(2)端到端的通信
(3)高可靠性,确保传输数据的正确性,不出现丢失或乱序。
(4)全双工方式传输
(5)采用字节流方式,即以字节为单位传输字节序列(面向字节流)
(6)紧急数据传送功能
2、IP协议
IP(Internet Protocol)(网络之间互联的协议)的缩写,中文简写成为“网协”,也就是为计算机网络互相连接进行通信而设计的协议。在因特网中,它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。
IP地址是一个32为(IPv4)或128(IPv6)的无符号数字,使用4组数字表示固定的编号,数字之间用一个点号隔开,例如“172.168.1.52”就代表网络中一个计算机唯一的地址编号。
3、TCP/IP
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WAN)设计的。它是由ARPANET网的研究机构发展起来的。
4、UDP协议
UDP(User Datagram Protocol),指的是用户数据报协议。它和TCP协议一样,都是网络传输层上的协议,但是它与TCP有着本质的区别。使用UDP协议传输时,不保证数据一定能到达目的地,也不保证到达的顺序性。但是UDP协议占用的资源比较少,所以一般用在一些可靠性要求比较低的网络应用上,如网络视频会议、在线影视和聊天室等音频、视频数据传送。
5、端口
端口(Port)可以被理解为计算机与外界通信交流的窗户。当一个信息到达时,根据其请求的端口号不同,就可以知道应该提供哪个服务了
常见服务端口表
服务 | 端口 |
HTTP | 80 |
FTP | 21 |
Telnet | 23 |
SMTP | 25 |
6、套接字
套接字(Socket)是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端面点,简单说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。某个程序将一段信息写入套接字中,该套接字就会将这段信息发送给另一个套接字,就像电话线的两端一样,这样另一端的程序就通过另一个套接字收到这段信息。所以使用套接字编程有时也称为Socket编程
7、java.net包
在java的API中,java.net包是用来提供网络服务的,java.net包中含有各种专门用来开发网络应用程序的类,程序开发人员可以使用该包中的类很容易的建立基于TCP可靠连接的网络程序,以及基于UDP不可靠连接的网络程序。java.net包可以大致分为以下两个部分:
(1)低级API,用于处理网络地址(也就是网络标识符,如IP地址)、套接字(也就是基本双向数据通信机制)和接口(用于描述网络接口)。
(2)搞基API,用于处理URL(表示统一资源标识符)、URL(表示统一资源标识符)、URLConnection连接(表示到URL所指向资源的连接)等。
二、InetAddress类
任何一台运行在Internet上的主机都有IP地址和当地的DNS能够解析的域名。而在java.net包中相应的提供了IP地址的封装类InetAddress。
InetAddress类用于描述和包装一个Internet IP地址,并提供了相关的常用方法,如解析IP地址的主机名称、获取本机IP地址的封闭、测试指定IP地址是否可达等。InetAddress实现java.io.Serializable接口,不允许继承。通过以下三种方法返回InetAddress实例。
getLocalhost():返回封装本地地址的实例
getAllByName(String host):返回封装Host地址的InetAddress实例数组
getByName(String host):返回一个封装Host地址的实例。其中,Host可以是域名或者是一个合法的IP地址。
InetAddress类的常用方法
boolean |
equals(Object obj) 将此对象与指定对象比较。 |
byte[] |
getAddress()
返回此 InetAddress 对象的原始 IP 地址。 |
static InetAddress[] |
getAllByName(String host)
在给定主机名的情况下,根据系统上配置的名称服务返回其 IP 地址所组成的数组。 |
static InetAddress |
getByAddress(byte[] addr)
在给定原始 IP 地址的情况下,返回 InetAddress 对象。 |
static InetAddress |
getByAddress(String host,
byte[] addr) 根据提供的主机名和 IP 地址创建 InetAddress。 |
static InetAddress |
getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。 |
String |
getCanonicalHostName()
获取此 IP 地址的完全限定域名。 |
String |
getHostAddress()
返回 IP 地址字符串(以文本表现形式)。 |
String |
getHostName()
获取此 IP 地址的主机名。 |
static InetAddress |
getLocalHost()
返回本地主机。 |
int |
hashCode()
返回此 IP 地址的哈希码。 |
boolean |
isAnyLocalAddress()
检查 InetAddress 是否是通配符地址的实用例行程序。 |
boolean |
isLinkLocalAddress()
检查 InetAddress 是否是链接本地地址的实用例行程序。 |
boolean |
isLoopbackAddress()
检查 InetAddress 是否是回送地址的实用例行程序。 |
boolean |
isMCGlobal()
检查多播地址是否具有全局域的实用例行程序。 |
boolean |
isMCLinkLocal()
检查多播地址是否具有链接范围的实用例行程序。 |
boolean |
isMCNodeLocal()
检查多播地址是否具有节点范围的实用例行程序。 |
boolean |
isMCOrgLocal()
检查多播地址是否具有组织范围的实用例行程序。 |
boolean |
isMCSiteLocal()
检查多播地址是否具有站点范围的实用例行程序。 |
boolean |
isMulticastAddress()
检查 InetAddress 是否是 IP 多播地址的实用例行程序。 |
boolean |
isReachable(int timeout)
测试是否可以达到该地址。 |
boolean |
isReachable(NetworkInterface netif, int ttl,
int timeout) 测试是否可以达到该地址。 |
boolean |
isSiteLocalAddress()
检查 InetAddress 是否是站点本地地址的实用例行程序。 |
String |
toString()
将此 IP 地址转换为 String 。 |
1 import java.io.IOException; 2 import java.net.InetAddress; 3 import java.net.UnknownHostException; 4 5 public class javaTest2 extends Thread { 6 7 public static void main(String[] args) throws IOException { 8 InetAddress ia=InetAddress.getLocalHost(); 9 System.out.println(ia); 10 InetAddress ia1=InetAddress.getByName("www.baidu.com"); 11 System.out.println(ia1); 12 System.out.println(ia.isReachable(1000)); 13 } 14 }
运行结果: 1 qinghan/10.160.10.202 2 www.baidu.com/220.181.111.188 3 false
分析结果:当使用isReachable()函数时出现false的原因,查资料是因为防火墙阻止了外面进来的ICMP数据包。
注:查看api文档,发现InetAddress类没有构造函数~
三、URL网络编程
1、URL类
URL由两部分组成:协议标识符和资源名称。其中“http”为使用的协议,它指的是超文本传输协议(HTTP)。其他常用的协议还包括文件传输协议(FTP)、Gopher、File和News。
URL类提供了多个方法,可以获取URL对象协议、主机名、端口号、路径、查询字符串、文件名及资源引用。
构造函数表
URL(String spec) 根据 String 表示形式创建 URL 对象。 |
URL(String protocol, String host,
int port, String file) 根据指定 protocol 、host 、port 号和 file
创建 URL 对象。 |
URL(String protocol, String host,
int port, String file, URLStreamHandler handler)
根据指定的 protocol 、host 、port
号、file 和 handler 创建 URL 对象。 |
URL(String protocol, String host, String file)
根据指定的 protocol 名称、host 名称和
file 名称创建 URL。 |
URL(URL context, String spec)
通过在指定的上下文中对给定的 spec 进行解析创建 URL。 |
URL(URL context, String spec, URLStreamHandler handler)
通过在指定的上下文中用指定的处理程序对给定的 spec 进行解析来创建 URL。 |
URL的常用方法
方法名称 | 方法说明 |
getProtocol() | 获得该URL的协议名 |
getAuthority() | 获得该URL的主机名和端口号 |
getFile() | 获得该URL的文件名 |
getHost() | 获得该URL的主机名 |
getPath() | 获得该URL的路径 |
getPort() | 获得该URL的端口号 |
getQuery() | 获得该URL 的查询字符串 |
getRef() | 获得该URL的引用锚记名 |
代码:
1 import java.net.MalformedURLException; 2 import java.net.URL; 3 4 public class javaTest2 extends Thread { 5 6 public static void main(String[] args) throws MalformedURLException { 7 URL url=new URL("http://www.baidu.com:80");//?后面表示参数,#后面表示锚点 8 System.out.println("协议名:"+url.getProtocol()); 9 System.out.println("主机名和端口号:"+url.getAuthority()); 10 System.out.println("文件名:"+url.getFile()); 11 System.out.println("路径:"+url.getPath()); 12 System.out.println("查询字符串:"+url.getQuery()); 13 System.out.println("锚记名:"+url.getRef()); 14 } 15 }
运行结果:
1 协议名:http 2 主机名和端口号:www.baidu.com:80 3 文件名: 4 路径: 5 查询字符串:null 6 锚记名:null
"/index.html?username=tom#test"?后面表示参数,#后面表示锚点(相对路径)
2、URLConnection类
URLConnection类用来表示与URL建立的通信连接。位于java.net包中,调用URL类的openConnection()方法可以获得URLConnection对象。获得对象后,开发人员可以调用该对象的connect()方法来连接远程资源,代码如下。
1 URL url=new URL("http://www.baidu.com"); 2 URLConnection urlc=url.openConnection();
构造函数表
protected |
URLConnection(URL url) 构造一个到指定 URL 的 URL 连接。 |
URL类和URLConnection类实现相同功能代码:
1 import java.io.BufferedReader; 2 import java.io.IOException; 3 import java.io.InputStream; 4 import java.io.InputStreamReader; 5 import java.net.MalformedURLException; 6 import java.net.URL; 7 import java.net.URLConnection; 8 9 public class javaTest2 extends Thread { 10 11 public static void main(String[] args) throws IOException { 12 URL url=new URL("http://www.baidu.com"); 13 InputStream is=url.openStream(); 14 //将字节流转换为字符流 15 InputStreamReader isr=new InputStreamReader(is,"utf-8"); 16 //字符流输入添加缓冲 17 BufferedReader bfr=new BufferedReader(isr); 18 19 String data=new String(bfr.readLine().getBytes("utf-8")); 20 System.out.println(data); 21 22 23 URLConnection urlc=url.openConnection(); 24 InputStream is1=urlc.getInputStream(); 25 //将字节流转换为字符流 26 InputStreamReader isr1=new InputStreamReader(is1,"utf-8"); 27 //字符流输入添加缓冲 28 BufferedReader bfr1=new BufferedReader(isr1); 29 30 String data1=new String(bfr1.readLine().getBytes("utf-8")); 31 System.out.println(data1); 32 33 34 bfr.close(); bfr1.close(); 35 isr.close(); isr1.close(); 36 is.close(); is1.close(); 37 } 38 }
运行结果:
1 � 2 �
打印的结果是一样的,但是出现的乱码问题没有解决(好烦啊~想了好久,用了挺多方法都不好使~)
四、Scoket通信
TCP协议是面向连接的、可靠的、有序的、以字节流的方式发送数据,基于TCP协议实现网络通信需要以下两种类协助实现:
客户端:Socket类 服务器端:ServerSocket类
注:建立连接的时候涉及到三次握手过程,释放连接时涉及到四次握手过程(TCP方式。。计算机网络知识)
socket通信的实现步骤
(1)创建serversocket和socket
(2)打开连接到socket的输入输出流。
(3)按照协议对socket进行读/写操作。
(4)关闭输入输出流,关闭socket。
1、基于TCP的socket通信
服务器端
1)创建serversocket对象,绑定监听端口号。
2)通过accept()方法监听客户端请求
3)连接建立后,通过输入输出流读取客户端发送的请求信息。
4)通过输出流向客户端发送响应信息。
5)关闭相关资源。
客户端
1)创建socket对象,指明需要建立连接的服务器地址和端口号。
2)连接建立后,通过输出流向服务器端发送请求信息。
3)通过输入流获取服务器响应的信息。
4)关闭相应的资源。
代码示例:
1 /* 2 * 服务器端(基于TCP的socket通信) 3 */ 4 import java.io.BufferedReader; 5 import java.io.BufferedWriter; 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.io.InputStreamReader; 9 import java.io.OutputStream; 10 import java.io.OutputStreamWriter; 11 import java.net.ServerSocket; 12 import java.net.Socket; 13 14 public class Server { 15 16 public static void main(String[] args) { 17 try { 18 //创建一个服务器端的serversocket对象 19 ServerSocket serverSocket=new ServerSocket(8888); 20 //通过accept()方法监听客户端请求 21 Socket socket=serverSocket.accept(); 22 //建立连接后,通过输入流获取客户端的请求信息 23 InputStream is=socket.getInputStream(); 24 //将字节流信息转化为字符流 25 InputStreamReader isr=new InputStreamReader(is); 26 //创建输入流缓存 27 BufferedReader br=new BufferedReader(isr); 28 29 String data=br.readLine(); 30 System.out.println("客户端对服务器端说:我是"+data); 31 32 //获取输出流,对请求作出相应的响应 33 OutputStream os=socket.getOutputStream(); 34 //将字节流转化为字符流 35 OutputStreamWriter osw=new OutputStreamWriter(os); 36 //为输出流添加缓冲 37 BufferedWriter bw=new BufferedWriter(osw); 38 bw.write("hello "+data+"!"); 39 40 bw.flush(); 41 42 //关闭资源 43 bw.close(); 44 osw.close(); 45 os.close(); 46 47 br.close(); 48 isr.close(); 49 is.close(); 50 socket.close(); 51 serverSocket.close(); 52 53 } catch (IOException e) { 54 // TODO Auto-generated catch block 55 System.out.println("连接建立失败"); 56 e.printStackTrace(); 57 } 58 } 59 } 60 61 62 /* 63 *客户端(基于TCP的socket通信) 64 */ 65 66 import java.io.BufferedReader; 67 import java.io.BufferedWriter; 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.io.InputStreamReader; 71 import java.io.OutputStream; 72 import java.io.OutputStreamWriter; 73 import java.net.Socket; 74 import java.net.UnknownHostException; 75 import java.util.Scanner; 76 77 public class Client { 78 79 public static void main(String[] args) { 80 //提示用户输入自己的姓名 81 /* String name=null; 82 System.out.println("请输入你的姓名:"); 83 Scanner sc=new Scanner(System.in); 84 name=sc.next();*/ 85 try { 86 //创建客户端Socket,指定服务器地址和端口 87 Socket socket=new Socket("localhost", 8888); 88 //获取输出流,向服务器发送信息 89 OutputStream os=socket.getOutputStream(); 90 //将字字节流转化成字符流 91 OutputStreamWriter osw=new OutputStreamWriter(os); 92 //创建字符流的缓冲 93 BufferedWriter bw =new BufferedWriter(osw); 94 95 osw.write("xiao"); 96 osw.flush(); 97 socket.shutdownOutput();//这句话作为客户端请求的结束标志,没有这就话客户端以为请求还没完事。 98 99 //接收服务器端响应,并将响应结果打印 100 InputStream is=socket.getInputStream(); 101 //将字节流信息转化为字符流 102 InputStreamReader isr=new InputStreamReader(is); 103 //创建输入流缓存 104 BufferedReader br=new BufferedReader(isr); 105 106 String data=br.readLine(); 107 System.out.println("服务器端对客户端说:"+data); 108 109 //关闭资源 110 //关闭资源 111 bw.close(); 112 osw.close(); 113 os.close(); 114 115 br.close(); 116 isr.close(); 117 is.close(); 118 socket.close(); 119 120 121 122 } catch (UnknownHostException e) { 123 // TODO Auto-generated catch block 124 System.out.println("连接建立失败"); 125 e.printStackTrace(); 126 } catch (IOException e) { 127 // TODO Auto-generated catch block 128 System.out.println("连接建立失败"); 129 e.printStackTrace(); 130 } 131 } 132 }
运行结果:服务器端输出
1 客户端对服务器端说:我是xiao
客户端输出
1 服务器端对客户端说:hello xiao!
2、基于UDP的socket编程
进行数据传输时,首先需要要将要传输的数据定义成数据报(Datagram)在数据报中指明数据所要达到的socket(主机地址和端口号),然后再将数据报发送出去。
相关操作类:
DatagramPacket:表示数据报包
DatagramSocket:进行端到端的通信的类
服务器端的实现步骤
(1)创建DatagramSocket,指定端口号
(2)创建DatagramPacket
(3)接收客户端发送的数据信息
(4)读取数据
客户端实现步骤
(1)定义发送的信息
(2)创建DatagramPacket,包含将要发送的信息
(3)创建DatagramSocket
(4)发送数据
代码示例:
1 import java.io.IOException; 2 import java.net.DatagramPacket; 3 import java.net.DatagramSocket; 4 import java.net.InetAddress; 5 import java.net.SocketException; 6 7 /* 8 * 基于UDP的socket通信(服务器端) 9 */ 10 public class Servier { 11 12 public static void main(String[] args) throws IOException { 13 /* 14 * 接收客户端的请求信息 15 */ 16 //创建服务器端的DatagramSocket,指定接口 17 DatagramSocket datagramSocket=new DatagramSocket(8800); 18 //创建服务器端DatagramPacket,用于接收客户端发送的数据报 19 byte []data=new byte[1024];//创建字节数组,指定接收数据包的大小 20 DatagramPacket datagramPacket=new DatagramPacket(data, data.length); 21 //接收客户端发送的数据 22 datagramSocket.receive(datagramPacket); 23 //读取数据 24 String info=new String(data,0,datagramPacket.getLength()); 25 System.out.println("我是服务器,客户端说:我是"+info); 26 27 /* 28 * 服务器端作出响应(向客户端发送数据) 29 */ 30 //定义客户端的地址和端口号数据(可以从客户端发送过来的数据报对象获取) 31 InetAddress address=datagramPacket.getAddress(); 32 int port=datagramPacket.getPort(); 33 34 byte[] data2=("欢迎你!"+info).getBytes(); 35 //创建数据报,包含响应的数据信息 36 DatagramPacket datagramPacket2=new DatagramPacket(data2, 0, data2.length, address, port); 37 //响应客户端,将信息传送给客户端 38 datagramSocket.send(datagramPacket2); 39 40 41 //关闭资源 42 datagramSocket.close(); 43 } 44 45 } 46 47 import java.io.IOException; 48 import java.net.DatagramPacket; 49 import java.net.DatagramSocket; 50 import java.net.InetAddress; 51 import java.net.SocketException; 52 import java.net.UnknownHostException; 53 54 /* 55 * 基于UDP的socket通信(客户端) 56 */ 57 public class Client { 58 59 public static void main(String[] args) throws IOException { 60 61 /* 62 * 发送客户端请求信息 63 */ 64 //1、创建DatagramSocket对象 65 DatagramSocket datagramSocket =new DatagramSocket(); 66 //2定义服务器端的地址,端口号数据 67 InetAddress address=InetAddress.getByName("localhost"); 68 int port=8800;//无法通过函数获取,只能自己赋值 69 byte []data="小帅哥".getBytes(); 70 //3、创建DatagramPacket对象,用来发送客户端的请求信息的数据报 71 DatagramPacket datagramPacket=new DatagramPacket(data, 0, data.length, address, port); 72 //4.发送客户端的请求数据信息 73 datagramSocket.send(datagramPacket); 74 75 /* 76 * 接收服务器端的响应信息 77 */ 78 //创建接收数据包的字节数组 79 byte []data1=new byte[1024]; 80 //创建DatagramPacket对象,用来接收服务器端的响应信息 81 DatagramPacket datagramPacket1=new DatagramPacket(data1, data1.length); 82 //接收服务器端的响应数据报信息 83 datagramSocket.receive(datagramPacket1); 84 //读取数据 85 String info=new String(data1,0,datagramPacket1.getLength()); 86 System.out.println("我是客户端,服务器端说:"+info); 87 88 //关闭资源 89 datagramSocket.close(); 90 91 } 92 93 }
运行结果:服务器端输出:
1 我是服务器,客户端说:我是小帅哥
客户端输出:
1 我是客户端,服务器端说:欢迎你!小帅哥
3、Socket总结
(1)对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket流。(省事,但是每个资源都关是个好习惯~)
(2)客户端与服务器端的通信,使用多线程,代码中可适当的降低线程的优先级,这样有利于提高运行速度。(网络编程不可避免要和多线程相关联)
/* *实现多线程网络通信的思路 */ public class TcpRun extends Thread{ public void run(){ //to do something } }
public static void main(String[] args) throws IOException {
try{
ServerSocket serverSocket=new ServerSocket(8888);
//通过accept()方法监听客户端请求
while(true){
Socket socket=serverSocket.accept(); TcpRun tcpRun=new TcpRun();
tcpRun.setPriority(4); //设置线程的优先级范围为[1,10],默认是5
tcpRun.start(); }catch(){
//to do something
}
}
(3)使用TCP通信传输一般为对象(使用ObjectOutputStream对象序列化,传递对象,或ObjectInputStream)
下一篇就是重点用这个的一个小项目:http://www.cnblogs.com/xiaotiaosi/p/6389418.html
(4)个人觉得,tcp和udp编程差别的原因:
1)tcp是面向字节流的,udp面向数据报的
2)tcp是面向连接的,udp不是面向连接的。