网络编程
1.网络编程
1.1计算机网络概述
网络编程的实质就是两个(或多个)设备(例如计算机)之间的数据传输。
按照计算机网络的定义,通过一定的物理设备将处于不同位置的计算机连接起来组成的网络,这个网络中包含的设备有:计算机、路由器、交换机等等。
其实从软件编程的角度来说,对于物理设备的理解不需要很深刻,就像你打电话时不需要很熟悉通信网络的底层实现是一样的,但是当深入到网络编程的底层时,这些基础知识是必须要补的。
路由器和交换机组成了核心的计算机网络,计算机只是这个网络上的节点以及控制等,通过光纤、网线等连接将设备连接起来,从而形成了一张巨大的计算机网络。
网络最主要的优势在于共享:共享设备和数据,现在共享设备最常见的是打印机,一个公司一般一个打印机即可,共享数据就是将大量的数据存储在一组机器中,其它的计算机通过网络访问这些数据,例如网站、银行服务器等等。
如果需要了解更多的网络硬件基础知识,可以阅读《计算机网络》教材,对于基础进行强化,这个在基础学习阶段不是必须的,但是如果想在网络编程领域有所造诣,则是一个必须的基本功。
对于网络编程来说,最主要的是计算机和计算机之间的通信,这样首要的问题就是如何找到网络上的计算机呢?这就需要了解IP地址的概念。
为了能够方便的识别网络上的每个设备,网络中的每个设备都会有一个唯一的数字标识,这个就是IP地址。在计算机网络中,现在命名IP地址的规定是IPv4协议,该协议规定每个IP地址由4个0-255之间的数字组成,例如10.0.120.34。每个接入网络的计算机都拥有唯一的IP地址,这个IP地址可能是固定的,例如网络上各种各样的服务器,也可以是动态的,例如使用ADSL拨号上网的宽带用户,无论以何种方式获得或是否是固定的,每个计算机在联网以后都拥有一个唯一的合法IP地址,就像每个手机号码一样。
但是由于IP地址不容易记忆,所以为了方便记忆,有创造了另外一个概念——域名(Domain Name),例如sohu.com等。一个IP地址可以对应多个域名,一个域名只能对应一个IP地址。域名的概念可以类比手机中的通讯簿,由于手机号码不方便记忆,所以添加一个姓名标识号码,在实际拨打电话时可以选择该姓名,然后拨打即可。
在网络中传输的数据,全部是以IP地址作为地址标识,所以在实际传输数据以前需要将域名转换为IP地址,实现这种功能的服务器称之为DNS服务器,也就是通俗的说法叫做域名解析。例如当用户在浏览器输入域名时,浏览器首先请求DNS服务器,将域名转换为IP地址,然后将转换后的IP地址反馈给浏览器,然后再进行实际的数据传输。
当DNS服务器正常工作时,使用IP地址或域名都可以很方便的找到计算机网络中的某个设备,例如服务器计算机。当DNS不正常工作时,只能通过IP地址访问该设备。所以IP地址的使用要比域名通用一些。
IP地址和域名很好的解决了在网络中找到一个计算机的问题,但是为了让一个计算机可以同时运行多个网络程序,就引入了另外一个概念——端口(port)。
在介绍端口的概念以前,首先来看一个例子,一般一个公司前台会有一个电话,每个员工会有一个分机,这样如果需要找到这个员工的话,需要首先拨打前台总机,然后转该分机号即可。这样减少了公司的开销,也方便了每个员工。在该示例中前台总机的电话号码就相当于IP地址,而每个员工的分机号就相当于端口。
有了端口的概念以后,在同一个计算机中每个程序对应唯一的端口,这样一个计算机上就可以通过端口区分发送给每个端口的数据了,换句话说,也就是一个计算机上可以并发运行多个网络程序,而不会在互相之间产生干扰。
在硬件上规定,端口的号码必须位于0-65535之间,每个端口唯一的对应一个网络程序,一个网络程序可以使用多个端口。这样一个网络程序运行在一台计算上时,不管是客户端还是服务器,都是至少占用一个端口进行网络通讯。在接收数据时,首先发送给对应的计算机,然后计算机根据端口把数据转发给对应的程序。
有了IP地址和端口的概念以后,在进行网络通讯交换时,就可以通过IP地址查找到该台计算机,然后通过端口标识这台计算机上的一个唯一的程序。这样就可以进行网络数据的交换了。
但是,进行网络编程时,只有IP地址和端口的概念还是不够的,下面就介绍一下基础的网络编程相关的软件基础知识。
1.2网络编程概述
网络编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。
按照前面的介绍,网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据好换的复杂。虽然通过IP地址和端口号可以找到网络上运行的一个程序,但是如果需要进行网络编程,则需要了解网络通讯的过程。
网络通讯基于“请求—响应”模型。在网络通讯中,第一次主动发起通讯的程序被称为客户端(client)程序,简称客户端,而第一次通讯中等待链接的程序被称为服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质区别。
由此,网络编程中的两种程序就分别是客户端和服务器端,例如QQ程序,每个QQ用户安装的都是QQ客户端程序,而QQ服务器端程序则在腾讯公司的机房中,为大量的QQ用户提供服务。这种网络编程的结构被称为客户端/服务器结构,也叫Client/Serverj结构,简称C/S结构。
使用C/S结构的程序,在开发时需要分别开发客户端和服务器端,这种结构的优势在于客户端是专门开发的,所以根据需要实现各种效果,专业点的说就是表现力丰富,而服务器端也需要专门进行开发。但是这种结构也存在着很多不足,例如通用性差,几乎不能通用,也就是说一种程序的客户端只能和对应的服务器端通讯,而不能和其他服务器端通讯,在实际维护中,也需要维护专门的客户端和服务器端,维护的压力比较大。
其实在运行很多程序时,没有必要使用专门的客户端,而需要使用通用的客户端,例如浏览器,使用浏览器作为客户端的结构称为浏览器/服务器结构,也叫做Browser/Server结构,简称B/S结构。
使用B/S结构的程序,在开发时只需要开发服务器端即可,这种优势在于开发压力比较小,不需要维护客户端,但是这种结构也存在这很多不足,例如浏览器的限制比较大,表现了不强,不能进行系统级别的操作等。
总之C/S结构和B/S结构是现在网络编程中常见的两种结构,B/S结构其实也就是一种特殊的C/S结构。
另外简单的介绍一下P2P(Point to Point)程序,常见的如BT、电驴等。P2P程序是一种特殊的程序,应该一个P2P程序中既包含客户端程序,也包含服务器端程序,例如BT,使用客户端程序部分连接其它的种子(服务器端),而使用服务器端向其它的BT客户端传输数据。如果这个还不是很清楚,其实P2P程序和手机是一样的,当手机拨打电话时就是使用客户端的作用,而手机处于待机状态时,可以接收到其它用户拨打的电话则起的就是服务器端的功能,只是一般的手机不能同时使用拨打电话和接听电话的功能,而P2P程序实现了该功能。
最后介绍一下网络编程中最重要的,也是最复杂的概念——协议(protocol)。按照前面的介绍,网络编程就是运行在不同计算机中两个程序之间的数据交换。在实际进行数据交换时,为了让接收端理解该数据,计算机比较笨,什么都不懂的,那么久需要规定该数据的格式,这个数据的格式就是协议。
如果没有理解协议的概念,那么再举一个例子,记得有个电影叫《永不消逝的电波》,讲述的是地下党通过电台发送情报的故事,这里我们不探讨电影的剧情,而只关 心电台发送的数据。在实际发报时,需要首先将需要发送的内容转换为电报编码,然后将电报编码发送出去,而接收端接收的是电报编码,如果需要理解电报的内容 则需要根据密码本翻译出该电报的内容。这里的密码本就规定了一种数据格式,这种对于网络中传输的数据格式在网络编程中就被称作协议。
那么如何编写协议格式呢?答案是随意。只要按照这种协议格式能够生成唯一的编码,按照该编码可以唯一的解析出发送数据的内容即可。也正因为各个网络程序之间协议格式的不同,所以才导致了客户端程序都是专用的结构。
在实际的网络编程中,最麻烦的内容不是数据的发送和接受,因为这个功能在几乎所有编程语言中都提供了封装好的API进行调用,最麻烦的内容就是协议的设计及协议的生产和解析,这个才是网络编程最核心的内容。
1.3网络通讯方式
在现有的网络中,网络通讯的方式主要有两种:
1.TCP(传输控制协议)方式。
2.UDP(用户数据协议)方式。
为了方便理解这两种方式,还是先来看个例子。大家使用手机时,向别人传递信息时有两种方式:拨打电话和发送短信。使用拨打电话的方式可以保证该信息传递给别人,因为别人接电话时本身就确认收到了该信息。而发送短信的方式价格低廉,使用方便,但是接受人可能收不到。
在网络通讯中,TCP方式就类似于拨打电话,使用该种方式进行网络通讯时,需要建立专门的虚拟连接,然后进行可靠的数据传输,如果数据发送失败,则客户端会自动重发该数据,而UDP方式就类似于发送短信,使用这种方式进行网络通讯时,不需要建立专门的虚拟连接,传输也不是很可靠,如果发送失败则客户端无法获得。
这两种传输方式都是实际的网络编程中进行使用,重要的数据一般使用TCP方式进行数据传输,而大量的非核心数据则都通过UDP方式进行传递,在一些程序中甚至结合使用这两种方式进行数据的传递。
由于TCP需要建立专用的虚拟连接以及确认传输是否正确,所以使用TCP方式的速度稍微慢一些,而且传输时产生的数据量要比UDP稍微大一些。
关于网络编程的基础知识就介绍这么多,如果需要深入了解相关知识请阅读专门的计算机网络书籍,下面开始介绍Java语言中网络编程的相关技术。
1.3网络编程步骤
按照前面的基础知识介绍,无论使用TCP方式还是UDP方式进行网络通讯,网络编程都是由客户端和服务器端组成,所以,下面介绍网络编程的步骤时,均以C/S结构为基础进行介绍。
1.3.1客户端网络编程步骤
客户端是指网络编程中首先发起连接的程序,客户端一般实现程序界面和基本逻辑实现,在进行实际的客户端编程时,无论客户端复杂还是简单,以及客户端实现的方式,客户端的编程主要由三个步骤实现:
1.建立网络连接
客户端网络编程的第一步都是建立网络连接。在建立网络连接时需要指定连接的服务器的IP地址和端口号,建立完成以后,会形成一条虚拟的连接,后续的操作就可以通过该连接实现数据交换了。
2.交换数据
连接建立以后,就可以通过这个连接交换数据了,交换数据严格要求按照请求响应模型进行,由客户端发送一个请求数据到服务器,服务器反馈一个响应数据后给客户端,如果客户端不发送请求则服务器就不响应。
根据逻辑需要,可以多次交换数据,但是还是必须遵循请求响应模型。
3.关闭网络连接
在数据交换完成后,关闭网络连接,释放程序占用的端口、内存等系统资源,结束网络编程。
最基本的步骤一般都是这三个步骤,在实际实现时,步骤2会出现重复,在进行代码组织时,由于网络编程是比较耗时的操作,所以一般开启专门的现场进行网络通讯。
1.4服务器端网络编程步骤
服务器是指网络编程中被等待连接的程序,服务器端一般实现程序的核心逻辑以及数据存储等核心功能。服务器端的编程步骤和客户端不同,是由四个步骤实现,依次是:
1.监听端口
服务器端属于被动等待连接,所以服务器端启动以后,不需要发起连接,而只需要监听本地计算机的某个固定端口即可。这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。
2.获得连接
当客户端连接到服务器端时,服务器端就可以获得一个连接,这个连接包含客户端信息,例如客户端IP地址等,服务器端和客户端通过该连接进行数据交换。
一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现。
3.交换数据
服务器端通过获得的连接进行数据交换。服务器端的数据交换步骤是首先接收客户端发送过来的数据,然后进行逻辑处理,再把处理以后的结果数据发送给客户端。简单来说,就是先接收再发送,这个和客户端的数据交换顺序不同。
其实,服务器端获得的连接和客户端的连接是一样的,只是数据交换的步骤不同。当然,服务器端的数据交换也是可以多次进行的。在数据交换完成以后,关闭和客户端的连接。
4.关闭连接
当服务器程序关闭时,需要关闭服务器端,通过关闭服务器端使得服务器监听的端口以及占用的内存可以释放出来,实现了连接的关闭。
其实服务器端编程的模型和呼叫中心的实现是类似的,例如移动的客服电话10086就是典型的呼叫中心,当一个用户拨打10086时,转接给一个专门的客服人员,由该客服实现和该用户的问题解决,当另外一个用户拨打10086时,则转接给另一个客服,实现问题解决,依次类推。
在服务器端编程时,10086这个电话号码就类似于服务器端的端口号码,每个用户就相当于一个客户端程序,每个客服人员就相当于服务器端启动的专门和客户端连接的线程,每个线程都是独立进行交互的。
这就是服务器端编程的模型,只是TCP方式是需要建立连接的,对于服务器端的压力比较大,而UDP是不需要建立连接的,对于服务器端的压力比较小罢了。
总之,无论使用任何语言,任何方式进行基础的网络编程,都必须遵循固定的步骤进行操作,在熟悉了这些步骤以后,可以根据需要进行逻辑上的处理,但是还是必须遵循固定的步骤进行。
其实,基础的网络编程本身不难,也不需要很多的基础网络知识,只是由于编程的基础功能都已经由API实现,而且需要按照固定的步骤进行,所以在入门时有一定的门槛,希望下面的内容能够将你快速的带入网络编程技术的大门。
2.Java网络编程技术
和网络编程有关的基本API位于Java.NET包中,该包中包含了基本的网络编程实现,该包是网络编程的基础。该包既包含基本的网络编程类,也包含封装后的专门处理WEB相关的处理类。
首先来介绍一下基础的网络类-InetAddress类。该类的功能是代表一个IP地址,并且将IP地址和域名相关的操作方法包含在该类的内部。关于该类的使用,下面通过一个基础的代码演示该类的使用。
import java.net.InetAddress; import java.net.UnknownHostException; public class InetAddressDemo { public static void main(String[] args) { try { InetAddress inet1 = InetAddress.getByName("www.163.com"); System.out.println(inet1); InetAddress inet2=InetAddress.getByName("127.0.0.1"); System.out.println(inet2); InetAddress inet3=InetAddress.getLocalHost(); System.out.println(inet3); String host =inet3.getHostName(); System.out.println("域名:"+host); String ip=inet3.getHostAddress(); System.out.println("IP:"+ip); } catch (UnknownHostException e) { e.printStackTrace(); } } }
在该示例代码中,演示了InetAddress类的基本使用,并使用了该类的几个常用方法,该代码的执行结果是:
www.163.com/202.201.14.182
/127.0.0.1
DESKTOP-HRHF03J/192.168.1.196
域名:DESKTOP-HRHF03J
IP:192.168.1.196
说明:由于该代码中包含一个互联网的网址,所以运行该程序时需要联网,否则将产生异常。
在后续的使用中,经常包含需要使用InetAddress对象代表IP地址的构造方法,当然,该类的使用补水必须的,也可以使用字符串来代表IP地址。
3.TCP编程
在Java语言中,对于TCP方式的网络编程提供了良好的支持,在实际实现时,以java.net.socket类代表客户端连接,以java.net.ServerSocket类作为服务器端连接。在进行网络编程时,底层网络通讯的细节已经实现了比较高的封装,所以在程序员实际编程时,只需要指定IP地址和端口号就可以建立连接了。正是由于这种高度的封装,一方面,简化了Java语言网络编程的难度,另外也使得使用Java语言进行网络编程无法深入到网络的底层,所以使用Java语言进行网络底层系统编程很困难,具体点说,Java语言无法事先底层的网络嗅探以及获得IP包结构等消息。但是由于Java语言的网络编程比较简答,所以还是获得了广泛的使用。
在使用TCP方式进行网络编程时,需要按照前面介绍的网络编程的步骤进行,下面分别介绍一下在Java语言中客户端和服务器端的实现步骤。在客户端网络编程中,首先需要建立连接,在Java API中以及java.net.socket类的对象代表网络连接,所以建立客户端网络连接,也就是创建Socket类型的对象,该对象代表网络连接,示例如下:
Socket socket1=new Socket(“192.168.1.103”,10000);
Socket socket2=new Socket(“www.sohu.com”,80);
上面的代码中,socket1实现的是连接到IP地址是192.168.1.103的计算机的10000号端口,而socket2实现的是连接到域名是www.sohu.com的计算机的80号端口,至于底层网络如何实现建立连接,对于程序员来说是完全透明的。如果建立连接时,本机网络不通,或服务器端程序未开启,则会抛出异常。
连接一旦建立,则完成了客户端编程的第一步,紧接着的步骤就是按照“请求-响应”模型进行网络数据交换,在Java语言中,数据传输功能由Java IO实现,也就是说只需要从连接中获得输入流和输出流即可,然后将需要发送的数据写入连接对象的输出流中,在发送完成后从输入流中读取数据即可。示例代码如下:
OutputStream os=socket1.getOutputStream();
InputStream is=socket1,getInputStream();
上面的代码中,分别从socket1这个连接对象获得了输出流和输入流对象,在整个网络编程中,后续的数据交换就变成了IO操作,也就是遵循“请求-响应”模式的规定,先向输出流中写入数据,这些数据会被系统发送出去,然后再从输入流中读取服务器端的反馈信息,这样就完成了一次数据交换工作,当然这个数据交换可以多次进行。
这里获得的只是最基本的输出流和输入流对象,还可以根据前面学习到的IO知识,使用流的嵌套将这些获得的基本流对象转换成需要的装饰流对象,从而方便数据的操作。
最后当数据交换完成以后,关闭网络连接,释放网络连接占用的系统端口和内存等资源,完成网络操作,示例代码如下:
socket1.close();
这就是最基本的网络编程功能介绍,下面是一个简单的网络客户端程序示例,该程序的作用是向服务器发送一个字符串“Hello”,并将服务器端的反馈显示到控制台,数据交换只进行一次,当数据交换完成以后关闭网络连接,程序结束,实现的代码如下:
UDP协议发送和接收数据
发送: 创建UDP发送端的Socket对象 创建数据并把数据打包 发送数据 释放资源 接收: 创建UDP接收端的Socket对象 创建数据包用于接收数据 接收数据 解析数据包 释放资源
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /* * UDP协议发送数据: * A:创建发送端Scoket对象 * B:创建数据,并把数据打包 * C:调用Socket对象的发送方法发送数据包 * D:释放资源 * */ public class SendDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建发送端Scoket对象 //DatagramSocket DatagramSocket ds = new DatagramSocket(); //创建数据,并把数据打包 //DatagramPacket(byte[]buf,int length,InetAddress address,int port) //创建数据 byte[]bys="hello world!".getBytes(); int length=bys.length; //IP地址对象 InetAddress address = InetAddress.getByName("192.168.1.104"); //端口 int port=10086; DatagramPacket p = new DatagramPacket(bys, length, address, port); //调用Socket对象的发送方法发送数据包 //public void send(DatagramSocket p) ds.send(p); //释放资源 ds.close(); } } package day26_UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /* * UDP协议接收数据: * A:创建发送端Scoket对象 * B:创建一个数据包(接收容器) * C:调用Socket对象的接收方法接收数据 * D:解析数据,并显示在控制台 * E:释放资源 * * */ public class ReceiveDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 创建发送端Scoket对象 // DatagramSocket(int port) DatagramSocket ds = new DatagramSocket(10085); // 创建一个数据包(接收容器) // DatagramPacket(byte[] buf,int length) byte[] bys = new byte[1024]; int length = bys.length; DatagramPacket dp = new DatagramPacket(bys, length); // 调用Socket对象的接收方法接收数据 ds.receive(dp); // 解析数据,并显示在控制台 // 获取对方的ip // public InetAddress getAddress() InetAddress address = dp.getAddress(); String ip = address.getHostAddress(); // public byte[] getData():获取数据缓冲区 // public int getLength():获取数据的实际长度 byte[] bys2 = dp.getData(); int len = dp.getLength(); String s = new String(bys2, 0, len); System.out.println(ip + ":" + s); // 释放资源 ds.close(); } }
TCP协议发送和接收数据
发送: 创建TCP客户端的Socket对象 获取输出流,写数据 释放资源 接收: 创建TCP服务器端的Socket对象 监听客户端对象 获取输入流,读取数据 释放资源
import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; /* * TCP发送数据: * A:创建发送端的Socket对象、 * 这一步如果成功,就说明连接已经建立成功了 * B:获取输出流,写数据 * C:释放资源 * * 连接被拒绝,TCP协议一定要先开服务器 * java.net.ConnectException: Connection refused: connect * * */ public class ClientDemo { public static void main(String[] args) throws IOException, IOException { // TODO Auto-generated method stub //创建Socket对象 //Socket(InetAddress address, int port) //Socket(String host,int port) //Socket socket = new Socket(InetAddress.getByName("192.168.1.104"),8888); Socket s=new Socket("192.168.1.104", 8888); //获取输出流 //public OutputStream getOutputStream() OutputStream os = s.getOutputStream(); os.write("hello,tcp".getBytes()); //释放资源 s.close(); } } import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /* * TCP协议接收数据: * A:创建接收端的Socket对象 * B:监听客户端 * C:获取输入流,读取数据显示在控制台 * D:释放资源 * */ public class ServerDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建接收端的Socket的对象 //ServerSocket(int port) ServerSocket ss = new ServerSocket(8888); //监听客户端,返回一个对应的Socket对象 //public Socket accept() Socket s = ss.accept();//阻塞式方法 //获取输入流,读取数据显示在控制台 InputStream is = s.getInputStream(); byte[]bys=new byte[1024]; int len=is.read(bys); String str=new String(bys, 0, len); String ip=s.getInetAddress().getHostAddress(); System.out.println(ip+":"+str); //释放资源 //关闭socket对象 //不关闭服务器 s.close(); // ss.close();//这个不关闭 } }
案例
最基本的UPD协议发送和接收数据
package day26_UDP优化; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; public class SendDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建发送端Socket对象 DatagramSocket ds = new DatagramSocket(); //创建数据并打包 byte[]bys="hellowrold".getBytes(); DatagramPacket dp = new DatagramPacket(bys, bys.length,InetAddress.getByName("192.168.1.104"),12344); //发送数据 ds.send(dp); //释放资源 ds.close(); } } package day26_UDP优化; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /* * 多次启动接收端: * java.net.BingException:Address already in use:Cannot bind * */ public class ReceiveDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 创建接收端的Socket对象 DatagramSocket ds = new DatagramSocket(12344); // 创建一个包 byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); // 接收数据 ds.receive(dp); // 解析数据 String ip = dp.getAddress().getHostAddress(); String s = new String(dp.getData(), 0, dp.getLength()); System.out.println("from" + ip + "data is:" + s); //释放资源 ds.close(); } }
把发送数据改进为键盘录入
package day26_UDPDemo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; /* * 数据来自与键盘录入 * 键盘录入数据要自己控制录入结束(886) * */ public class SendDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建发送端Socket对象 DatagramSocket ds = new DatagramSocket(); //封装键盘录入数据 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line=null; while((line=br.readLine())!=null){ if("886".equals(line)){ break; } //创建数据并打包 byte[]bys=line.getBytes(); DatagramPacket dp = new DatagramPacket(bys, bys.length,InetAddress.getByName("192.168.1.104"),12341); //发送数据 ds.send(dp); } //释放资源 ds.close(); } } package day26_UDPDemo; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; /* * 多次启动接收端: * java.net.BingException:Address already in use:Cannot bind * 端口被占用 * */ public class ReceiveDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 创建接收端的Socket对象 DatagramSocket ds = new DatagramSocket(12341); while(true){ // 创建一个包 byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); // 接收数据 ds.receive(dp); // 解析数据 String ip = dp.getAddress().getHostAddress(); String s = new String(dp.getData(), 0, dp.getLength()); System.out.println("from" + ip + "data is:" + s); } //释放资源 //接收端应该一直开着等待接收数据,是不需要关闭的 // ds.close(); } }
一个简易聊天小程序并用多线程改进
package day26_UDPDemo2; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class SendThread implements Runnable { private DatagramSocket ds; public SendThread(DatagramSocket ds) { // TODO Auto-generated constructor stub this.ds=ds; } @Override public void run() { // TODO Auto-generated method stub try{ //从键盘录入数据 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line=null; while((line=br.readLine())!=null){ //如果键盘录入时886,则结束 if(line.equals("886")){ break; } //创建数据打包 byte[]bys=line.getBytes(); InetAddress address = InetAddress.getByName("192.168.1.104"); DatagramPacket dp = new DatagramPacket(bys, bys.length, address, 12306); //发送数据 ds.send(dp); } }catch(IOException e){ e.printStackTrace(); } } } package day26_UDPDemo2; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class ReceiveThread implements Runnable { private DatagramSocket ds; public ReceiveThread(DatagramSocket ds) { // TODO Auto-generated constructor stub this.ds=ds; } @Override public void run() { // TODO Auto-generated method stub try{ while(true){ //创建数据包 byte[]bys=new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); //接收数据 ds.receive(dp); //解析数据 String hostIp = dp.getAddress().getHostAddress(); byte[] data = dp.getData(); String s = new String(data,0,data.length); System.out.println("From"+hostIp+"data is :"+s); } }catch(IOException e){ e.printStackTrace(); } } } package day26_UDPDemo2; import java.io.IOException; import java.net.DatagramSocket; import java.net.SocketException; /* * 通过多线程改进刚才的聊天程序,这样我就可以实现在一个窗口发送和接收数据了 * */ public class ChatRoom { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建Socket套接字 DatagramSocket sendDs = new DatagramSocket(); DatagramSocket receiveDs = new DatagramSocket(12306); //创建两个线程实现,创建对象 SendThread st = new SendThread(sendDs); ReceiveThread rt = new ReceiveThread(receiveDs); //实现线程实例,使用Runnable实现 Thread t1 = new Thread(st); Thread t2 = new Thread(rt); //启动线程 t1.start(); t2.start(); } }
最基本的TCP协议发送和接收数据
package day26_TCP优化; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class ClientDemo { public static void main(String[] args) throws IOException, IOException { // TODO Auto-generated method stub //创建客户端Socket对象 Socket s = new Socket("192.168.1.104",9999); //获取输出流 OutputStream os = s.getOutputStream(); os.write("hellowrold".getBytes()); //获取输入流 InputStream is = s.getInputStream(); byte[]bys=new byte[1024]; int len = is.read(bys); String client=new String(bys, 0, len); System.out.println("client:"+client); //释放资源 s.close(); } } package day26_TCP优化; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建服务器Socket对象 ServerSocket ss = new ServerSocket(9999); //监听客户端连接 Socket s = ss.accept(); //获取输入流 InputStream is=s.getInputStream(); byte[]bys=new byte[1024]; int len=is.read(bys); String server = new String(bys,0,len); System.out.println("server:"+server); //获取输出流 OutputStream os = s.getOutputStream(); os.write("数据已经收到".getBytes()); //释放资源 s.close(); // ss.close();//这个可以不关 } }
客户端键盘录入服务器控制台输出
package day26_TCPDemo; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class ClientDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建Socket对象 //ip地址可能会变 Socket s = new Socket("192.168.1.100",22222); //使用高效数据流键盘录入数据 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //给通道写入数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line=null; while((line=br.readLine())!=null){ //键盘输入886结束 if(line.equals("886")){ break; } bw.write(line); bw.newLine(); bw.flush(); } //释放资源 //br.close();//键盘录入886后就结束了 //bw.close();//s关闭后,bw就关闭了 s.close(); } } package day26_TCPDemo; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //创建ServerSocket对象 ServerSocket ss = new ServerSocket(22222); //客户端监听 Socket s = ss.accept(); //包装通道内容的流 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String line=null; while((line=br.readLine())!=null){ System.out.println(line); } //释放资源 s.close(); } }
客户端键盘录入服务器写到文本文件
package day26_TCPDemo2; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; public class ClientDemo { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建Socket对象 Socket s = new Socket("192.168.1.100",23456); //获取键盘录入数据流 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //封装键盘录入数据到通道(发送) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line=null; while((line=br.readLine())!=null){ //结束标记 if(line.equals("over")){ break; } //写入通道 bw.write(line); bw.newLine(); bw.flush(); } //释放资源 // br.close(); // bw.close(); s.close(); } } package day26_TCPDemo2; /* * 客户键盘录入,服务器输出文本文件 * */ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub // 创建ServerSocket对象 ServerSocket ss = new ServerSocket(23456); //监听用户对象 Socket s = ss.accept(); //读取通道数据 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //封装文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter("Tcp.txt")); //写入文本 String line=null; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } //释放资源 bw.close();//文件操作流需要关闭 s.close(); } }
客户端读取文本文件服务器控制台输出
package day26_TCPDemo3; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; /* * 客户端文本文件,服务器输出到控制台 * */ public class ClientDemo { public static void main(String[] args)throws IOException { //创建Socket对象 Socket s = new Socket("192.168.1.100",34567); //封装文本文件 BufferedReader br = new BufferedReader(new FileReader("Tcp.txt")); //封装通道内的流(客户端写) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); String line=null; while((line=br.readLine())!=null){ bw.write(line); bw.newLine(); bw.flush(); } //释放资源 br.close(); s.close(); } } package day26_TCPDemo3; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class ServerDemo { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建ServerSocket对象 ServerSocket ss = new ServerSocket(34567); //监听客户端 Socket s = ss.accept(); //封装通道流数据(服务器读) BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); String line=null; while((line=br.readLine())!=null){ System.out.println(line); } //释放资源 s.close(); } }
客户端读取文本文件服务器写到文本文件
package day26_TCPDemo4; /* * 需求:客户端上传文件,服务器端输出文本文件 * * 按照我们正常的思路加入反馈信息,结果却没有反应,为什么呢? * 读取文本文件是可以null作为结束信息的,但是呢?通道内是没有这种方式结束信息de * 所以服务器根本就不知道你结束了,而你还想服务器给你反馈,所以相互等待了。 * * */ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; public class UploadClient { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建Socket对象 Socket s = new Socket("192.168.1.100",11111); //封装文本文件 BufferedReader br = new BufferedReader(new FileReader("Tcp.txt")); //封装通道内流(写) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //写数据 String line=null; while((line=br.readLine())!=null){//阻塞 bw.write(line); bw.newLine(); bw.flush(); } //Socket提供了一个终止功能,会通知服务器你别等了, s.shutdownOutput(); //接收反馈 BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream())); String client = brClient.readLine();//阻塞 System.out.println(client); //释放资源 br.close(); s.close(); } } package day26_TCPDemo4; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class UploadServer { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建ServerSocket对象 ServerSocket ss = new ServerSocket(11111); //监听客户端 Socket s = ss.accept();//阻塞 //封装通道内流 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //封装文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter("Tcp_Copy.txt")); //写入数据 String line=null; while((line=br.readLine())!=null){//阻塞 bw.write(line); bw.newLine(); bw.flush(); } //给出反馈 BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bwServer.write("文件上传成功");//阻塞 bwServer.newLine(); bwServer.flush(); //释放资源 bw.close(); s.close(); } }
上传图片
package day26_TCPDemo5; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class UploadClient { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建客户Socket对象 Socket s = new Socket("192.168.1.100",19191); //封装图片文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("Test.jpg")); //封装通道内流 BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream()); //将图片写入通道 byte[]bys=new byte[1024]; int len=0; while((len=bis.read(bys))!=-1){ bos.write(bys, 0, len); //刷新 bos.flush(); } //通知服务器结束 s.shutdownOutput(); //接收反馈 InputStream is = s.getInputStream(); byte[]bys2=new byte[1024]; int len2=is.read(bys2); String client=new String(bys2, 0, len2); System.out.println(client); //释放资源 bis.close(); s.close(); } } package day26_TCPDemo5; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /* * 上传图片 * */ public class UploadServer { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建ServerSocket对象 ServerSocket ss = new ServerSocket(19191); //监听客户连接 Socket s = ss.accept(); //封装通道内流 //图片用字节流(字节高效流) BufferedInputStream bis = new BufferedInputStream(s.getInputStream()); //封装图片文件 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("Test_Copy.jpg")); //写入通道 byte[]bys=new byte[1024]; int len=0; while((len=bis.read(bys))!=-1){ bos.write(bys, 0, len); //刷新 bos.flush(); } //给个反馈 OutputStream os = s.getOutputStream(); os.write("图片上传成功".getBytes()); //释放资源 bos.close(); s.close(); } }
多线程改进
package day26_Multi_Tcp; /* * 通过while循环可以改进一个服务器接收多个客户端 * 但是这个是有问题的 * 如果是这样的情况,假设我有张三、李四等多个人分别执行客户端 * 张三:tcp.txt 1M * 李四:b.txt 100K * 等等 * */ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; public class UploadClient { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建Socket对象 Socket s = new Socket("192.168.1.100",11111); //封装文本文件 //张三 //BufferedReader br = new BufferedReader(new FileReader("Tcp.txt")); //李四 BufferedReader br = new BufferedReader(new FileReader("b.txt")); //封装通道内流(写) BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //写数据 String line=null; while((line=br.readLine())!=null){//阻塞 bw.write(line); bw.newLine(); bw.flush(); } //Socket提供了一个终止功能,会通知服务器你别等了, s.shutdownOutput(); //接收反馈 BufferedReader brClient = new BufferedReader(new InputStreamReader(s.getInputStream())); String client = brClient.readLine();//阻塞 System.out.println(client); //释放资源 br.close(); s.close(); } } package day26_Multi_Tcp; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; public class UserThread implements Runnable { private Socket s; //构造方法 public UserThread(Socket s) { // TODO Auto-generated constructor stub this.s=s; } @Override public void run() { // TODO Auto-generated method stub try{ //封装通道内流 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //为了防止名字冲突,使用时间戳 String newName=System.currentTimeMillis()+".java"; //封装文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter(newName)); //写入数据 String line=null; while((line=br.readLine())!=null){//阻塞 bw.write(line); bw.newLine(); bw.flush(); } //给出反馈 BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); bwServer.write("文件上传成功");//阻塞 bwServer.newLine(); bwServer.flush(); //释放资源 bw.close(); s.close(); }catch(IOException e){ e.printStackTrace(); } } } package day26_Multi_Tcp; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; public class UploadServer { public static void main(String[] args)throws IOException { // TODO Auto-generated method stub //创建ServerSocket对象 ServerSocket ss = new ServerSocket(11111); while(true){ //监听客户端 Socket s = ss.accept();//阻塞 new Thread(new UserThread(s)).start(); } } }
在java中网络程序有两种协议:TCP和UDP,TCP通过握手协议进行可靠的连接,UDP则是不可靠连接。
IP地址:用于标记一台计算机的身份证。
IP地址由网络地址(确定网络)和主机地址(网络中的主机)组成。
子网掩码:为了区分网络地址和主机地址。
IP地址分为A类地址、B类地址、C类地址(常用)、D类地址、E类地址。
127.0.0.1(localhost)是本机地址。
IPV4和IPV6
IPV4使用4个十进制数表示,即32位二进制。
SMTP是简单邮件传输协议,端口号是25.
telnet用于连接远程计算机或者因特网计算机提供的服务。每个服务都会设定一个端口。
给出类似 telnet ip port 即可和特定的服务进行通信
如果要连接因特网的服务,不仅要给出端口,还要给出计算机的名称,只有给出IP地址和端口号时,才能够请求服务,并接收到应答。
URL和URI
URI:统一资源标识符,用于标识一个web资源,包含了两个部分。
(1)URL:统一资源定位符。能够精确的定位数据的URI
(2)URN:统一资源名称。除了URL的URI
在java中URI和URL是分开的两个类,URI类专门用于解析,URL用于通信。
URL
1.URI分类
绝对和相对:
(1)绝对URI是指有确定的协议。比如http,ftp。后面以/进行分隔
(2)相对URI是没有scheme的。
透明和不透明:
(1)不透明URI是不能够被解析的URI。不透明URI是绝对URI。scheme后面的部分不是以/进行分割。
分层和不分层:
(1)分层是绝对透明URI或相对URI。
所有的网页端口都是80.
2.URI的作用:
(1)解析
URI的格式:
[scheme**:**
]scheme-specific-part[**#**
fragment]
scheme表示用的协议,可以是http\https\ftp\file等。
scheme-specific-part是其余部分。
进一步细分:
scheme**:**
path[**#**
fragment]
常用方法:
-
getScheme()获得scheme;
-
getSchemeSpecificPart()
-
getPath()
-
getAuthority()
(2)相对标识符和绝对标识符的转换
resolve和relative函数。
示例代码:
任务1:取得特定网址的html代码。
任务2:分析地址信息。
任务3:绝对地址和相对地址转换
import java.net.*;
class IPDemo
{
public static void main(String[] args) throws Exception
{
/*
* //下边这句话会报未知主机异常
* //获取本地主机IP地址
InetAddress i= InetAddress.getLocalHost();
System.out.println(i.toString());
String adr = i.getHostAddress();
String name = i.getHostName();
sop("adr="+adr+" name="+name);
*/
//获取其他主机IP地址
//通过主机名获取IP地址,需要解析,所以较慢,而且对于百度、新浪这样的主机名,获得的IP地址可能不唯一。
InetAddress i= InetAddress.getByName("www.sina.com");
System.out.println(i.toString());
String adr = i.getHostAddress();//获得IP地址的字符串表现形式
String name = i.getHostName();//获取主机名
sop("adr="+adr+" name="+name);
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
TCP和UDP的区别【重要】
UDP:是面向无连接的,它将数据集源和目的封装成数据包中,不需要建立连接;它的每个数据报(Datagram)的大小限制在64k以内;因为无连接,是不可靠的协议;同样因为不需要建立里连接,所以速度快。
应用特点:只求速度快,数据丢失对程序影响不大。
应用程序实例:聊天室工具、视频、网络会议等。
TCP:需要建立连接,形成传输数据的通道;在连接中进行大量数据传输;通过三次握手协议完成连接,是可靠的协议;因为必须建立连接,所以效率会稍低。(TCP不像UDP需要封包,而且每个包不能超过64k,它可以大量的传输数据)。
应用特点:适合对数据完整性要求高的程序。
应用程序实例:下载程序。
Socket
网络编程其实就Socket编程,Socket是为网络服务的一种机制。(Socket,中文意思为插座)
每个应用程序都有一个socket信端点,可以把Socket想象为每个应用程序上的码头。
通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket间通过IO传输。
UDP传输
UDP两端是发送端和接受端。
DatagramSocket:UDP传输专用的程序通信端点,既能发送也能接受。
DatagramPacket:数据报包,封装了UDP传输的数据报。里边可以设置要发送的目的地IP地址、端口及要发送的数据。
广播地址:每个网段尾数为255的IP地址,例如192.168.1.255
需求:通过Udp传输方式,将一段文字数据发送出去。
定义一个udp发送端。
思路:
1。建立UDPsocket服务。
2.提供数据,并将数据封装到数据包中。
3.通过socket服务的发送功能,将数据包发出去。
4.关闭资源。
*/
/*发送方*/
import java.net.*;
class UdpSend
{
public static void main(String[] args) throws Exception
{
//1.创建UDP服务,通过DatagramSocket对象。
DatagramSocket ds = new DatagramSocket(8888);
//2.确定数据,并封装成数据包。DatagramPacket(byte[] buf, int length, InetAddress address, int port)
byte[] buf = "这是通过UDP发送的数据!".getBytes();
DatagramPacket dp = new DatagramPacket(buf,buf.length,InetAddress.getByName("127.0.0.1"),10000);
//3.通过socket服务,将已有的数据包发送出去,通过send方法。
ds.send(dp);
//4.关闭资源。它使用了底层的资源,所以要关闭资源。
ds.close();
}
}
/*
需求;
定义一个应用程序,用于接收并处理数据。
定义udp接收端
思路:
1.定义socket服务,通常会监听一个端口,其实就是给这个接受网络应用程序定义一个数字标示。
方便与明确哪些数据过来该应用程序可以处理。
2.定义一个数据包,因为要存储要接受到的字节数据,
因为数据包对象中有更多功能可以提取字节数据中的不同数据信息。
3.通过socket服务的receive方法将收到的数据存入已定义的数据包中。
4.通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上。
5.关闭资源。
*/
import java.net.*;
class UdpRece
{
public static void main(String[] args) throws Exception
{
//1.创建udpsocket,建立端点。这里必须定义监听的端口,其实就是给网络应用程序定义数字标识。
DatagramSocket ds = new DatagramSocket(10000);
//2.定义数据包,用于存储数据。
byte [] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//3.通过服务的receive方法,将收到的数据存入到数据包中。
ds.receive(dp);//receive是一个阻塞式方法没有接受到数据,会一直等待
//4.获取数据包中的方法获取其中的数据。
InetAddress i= dp.getAddress();
String ip = i.getHostAddress();
String data = new String(dp.getData(),0,dp.getLength());
int port = dp.getPort();
System.out.println(ip+"::"+data+"::"+port);
//5、关闭资源
ds.close();
}
}
//改进:让接收端始终处于接收状态,发送端可以发送多条数据并以“over”结束
package tan;
import java.io.*;
import java.net.*;
public class UDPSend2 {
public static void main(String[] args) throws Exception{
DatagramSocket socket=new DatagramSocket();//可以不填写端口保持默认
//读取键盘录入···
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
DatagramPacket dp=null;
String line=null;
while((line=br.readLine())!=null){
//聊天中输入over结束聊天
if("over".equals(line)){
break;
}
byte[]buf=("sender:"+line).getBytes();
//192.168.1.255是这个网段的广播地址,用它就能实现局域网群聊。
InetAddress ip=InetAddress.getByName("127.0.0.1");
//打包数据
dp=new DatagramPacket(buf, buf.length,ip,10001);
//发送数据
socket.send(dp);
}
socket.close();
}
}
package tan;
import java.io.*;
import java.net.*;
public class UDPRec2 {
public static void main(String[] args) throws Exception {
DatagramSocket ds=new DatagramSocket(10001);
byte []buf=new byte[64];
DatagramPacket dp=new DatagramPacket(buf, buf.length);
//无限循环,始终处于接收状态
while(true){
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
int port=dp.getPort();
String data=new String(dp.getData(),0,dp.getLength());
System.out.println("IP"+ip+":"+port+":"+data);
}
}
}
UDP练习:聊天程序
package tan;
import java.io.*;
import java.net.*;
class Send implements Runnable{
private DatagramSocket ds;
public Send(DatagramSocket ds) {
this.ds=ds;
}
public void run() {
try {
ds=new DatagramSocket();
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
String line=null;
while((line=bufr.readLine())!=null){
if("over".equals(line)){
break;
}
byte []buf=line.getBytes();
DatagramPacket dp=new DatagramPacket(buf, 0,buf.length,InetAddress.getByName("127.0.0.1"),10002);
ds.send(dp);
}
ds.close();
} catch (Exception e) {
throw new RuntimeException("发送端发送失败!");
}
}
}
class Receive implements Runnable{
private DatagramSocket ds;
public Receive(DatagramSocket ds) {
this.ds=ds;
}
public void run() {
try {
while(true){
byte []buf=new byte[1024];
DatagramPacket dp=new DatagramPacket(buf, buf.length);
ds.receive(dp);
String ip=dp.getAddress().getHostAddress();
String data=new String(dp.getData(),0,dp.getLength());
System.out.println("ip:"+ip+" data:"+data);
}
} catch (Exception e) {
throw new RuntimeException("接收端失败!");
}
}
}
public class chatDemo {
public static void main(String[] args) throws Exception {
DatagramSocket sendSocket=new DatagramSocket();
new Thread(new Send(sendSocket)).start();
DatagramSocket receSocket=new DatagramSocket(10002);
new Thread(new Receive(receSocket)).start();
}
}
TCP传输
TCP的两端对应的是客户端和服务端。
Socket:TCP传输客户端通信端点。
SeverSocket:TCP传输服务端通信端点。
因为TCP需要建立连接,所有Socket客户端一建立就要指定服务端的IP和端口。而在服务端建立时,要设置监听的端口。
TCP连接成功后,在客户端和服务端就会产生网络流。
客户端Socket提供对网络流进行读写的输入流和输出流对象。
服务端操作网络流时,先获取客户端Socket对象,然后利用该Socket的字节输入输出流进行读写操作。
客户端与服务端进行多次交互时,注意阻塞式方法对程序的影响。
ServerSocket(int port, int** backlog**),这个构造函数中backlog用于指定客户端的最大连接数。
TCP传输中的两个问题
一、 客户端与服务端交互时同时阻塞
在示例代码3中,当客户端和服务端中都都有多个阻塞式方法时,如果使用缓冲区读取每行,没有刷新并加入换行符时,会导致客户端与服务端同时处于阻塞等待状态。
二、 定义文件结束标记
在示例代码4中,当上传文件时,客户端把本地文件变成了IO流,并写入网络输出流时,因为去掉了去掉了window加入的结束标记,使得该IO流没有结束标记,导致服务端读取流中数据时,无法结束。这就需要自定义标记。自定义标记有三种方法:
1.定义”over”这样的字符结束标记;
2.盖时间戳。
3.使用Socket的shutdownOutput( )和shutdownInput()方法。
/*
客户端:
通过查阅Socket对象,发现在该对象建立时,就可以连接指定的主机。
因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,
并连接成功,形成通路后,在该通道进行数据的传输。
需求:给服务段发送一个文本数据。
步骤:
1.创建Socket服务,并指定要连接的主机和端口。
*/
package TCP;
import java.io.*;
import java.net.*;
public class TCPClient {
public static void main(String[] args) throws Exception, IOException {
//1.创建客户端的Socket服务,指定目的主机和端口
Socket s=new Socket("127.0.0.1", 10003);
//2.为了发送数据,应该获取Socket流中的输出流。
OutputStream out=s.getOutputStream();
//3.获取输出流后写入数据
out.write("我是客户端~~".getBytes());
//socket关闭客户端Socket服务,也就关闭了网络流资源。
s.close();
}
}
/*
服务端:
需求:定义端点,接受数据,并打印在控制台上。
服务端:
1.建立服务端的Socket服务,通过SeverSocket();并监听一个端口。
2. 获取连接过了的客户端对象。
通过SeverSocket的accept方法。没有就会等,所以这个方法是阻塞式的。
3.客户端如果发过了数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流读取发过了的数据。并打印在控制台上。
4.关闭服务。(可选操作,服务端一般是一直开着的)。
*/
package TCP;
import java.io.*;
import java.net.*;
public class TCPServer {
public static void main(String[] args) throws Exception{
//1.建立服务端的Socket服务,通过SeverSocket();并监听一个端口。
ServerSocket ss=new ServerSocket(10003);
//2. 获取连接过了的客户端对象。 通过SeverSocket的accept方法。没有就会等,所以这个方法是阻塞式的。
Socket s=ss.accept();
//3.获取客户端发过来的数据,那么要使用客户端对象的读取流方法读取对象。这个流是网络流
InputStream is=s.getInputStream();
byte[]buf=new byte[1024];
int len=is.read(buf);
System.out.println(new String(buf, 0, buf.length));
//4.关闭客户端,服务端(可选)
s.close();
ss.close();//关闭服务端(可选)
}
}
//服务端接收到客户端的信息后并没有反馈信息
/*
示例代码2-------完成客户端与服务端的一次交互
演示tcp的传输的的客户端和服务端的互访。
需求:客户端给服务端发送数据,服务端收到数据后,给客户端反馈信息。
客户端:
1.建立socket服务,指定要连接的主机和端口
1.获取Socket流中的输出流,将数据写入到该流中,通过网络发送给服务端。
3.获取socket流中的输入流,将服务端反馈的数据获取到,并打印,
4.关闭客户端资源。
*/
package TCP;
import java.io.*;
import java.net.*;
public class TCPClient2 {
public static void main(String[] args) throws Exception {
Socket s=new Socket("127.0.0.1", 10004);
//利用输出流向服务器发出数据
OutputStream out=s.getOutputStream();
out.write("服务端你好!我是客户端".getBytes());
//利用输入流接收服务器返回的数据
InputStream in=s.getInputStream();
byte[]buf=new byte[1024];
int len=in.read(buf);//阻塞式方法,等待服务端返回信息
System.out.println(new String(buf,0,len));
s.close();
}
}
package TCP;
import java.io.*;
import java.net.*;
public class TCPServer2 {
public static void main(String[] args) throws Exception {
ServerSocket ss=new ServerSocket(10004);
Socket s=ss.accept();
//获取客户端ip
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"......is connected!");
//利用输入流接收客户端发送的信息
InputStream in=s.getInputStream();
byte []buf=new byte[1024];
int len=in.read(buf);
System.out.println(new String(buf, 0, len));
//利用输出流向客户端发送反馈信息
OutputStream out=s.getOutputStream();
out.write("你好客户端!我是服务端,已经接收到了你发送的数据".getBytes());
s.close();//关闭socket连接
}
}
示例代码3------键盘录入数据通过服务端转成大写形式。
需求:建立一个文本转换服务器。
客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。
而且,客户端可以不断的进行文本转换。当客户端输入over,转换就结束。
分析:
客户端:
既然是操作设备上的数据,那么就可以使用io技术,并按照IO的操作规律来思考。
源:键盘录入
目的:网络设备,也就是网络输出流,
而且操作的是文本数据,可以选择字符流。
步骤:
1,建立服务
2.获取键盘录入
3,将数据发给服务端。
4.获取服务端返回的大写数据。
5.结束,关闭资源。
都是文本数据,可以使用字符流进行操作。同时提高效率,要加入缓冲。
客户端代码:
package TCP;
import java.io.*;
import java.net.*;
public class TransClient {
public static void main(String[] args) throws Exception {
//1.建立socket服务
Socket s=new Socket("127.0.0.1", 10005);
//2.读取键盘数据(获取键盘录入)
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
//3.定义目的,将数据写入socket输出流,发给服务器。
//BufferedWriter bufout=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//代码简化:如下···
PrintWriter out=new PrintWriter(s.getOutputStream(),true);
//4.定义一个socket读取流,读取服务端返回的信息
BufferedReader bufin=new BufferedReader(new InputStreamReader(s.getInputStream()));
String line=null;
while((line=bufr.readLine())!=null){//阻塞位置1,等待键盘录入,没有数据则等待
if("over".equals(line)){
break;
}
// bufout.write(line);//写到缓冲区中去了
// bufout.newLine();//换行,没有这一句,则阻塞2的readline不会返回字符,
// bufout.flush();//没有这一句,则数据留在缓冲区中,阻塞2同样阻塞。
out.println(line);//打印流的println()方法可以实现自动刷新
String str=bufin.readLine();//阻塞位置3
System.out.println("server:"+str);
}
//5.关闭资源
bufr.close();
s.close();//给socket在加入结束标记(-1)
}
}
/*
服务端:
源.socket读取流。
目的:socket输出流
都是文本,装饰
*/
package TCP;
import java.io.*;
import java.net.*;
public class TransServer {
public static void main(String[] args) throws Exception {
ServerSocket ss=new ServerSocket(10005);
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"....已经连接!");
BufferedReader bufin=new BufferedReader(new InputStreamReader(s.getInputStream()));
// BufferedWriter bufout=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//简化书写:
PrintWriter out=new PrintWriter(s.getOutputStream(), true);
String line=null;//定义字符串来接收输出的数据
// 阻塞位置2,没有数据+换行符(win中是“\r\n”)则等待,尤其注意换行符
while((line=bufin.readLine())!=null){
// bufout.write(line.toUpperCase());
// bufout.newLine();
// bufout.flush();
System.out.println(line);
out.println(line.toUpperCase());//小写转大写
}
s.close();
ss.close();
}
}
/*
该例子出现的问题:
现象:客户端和服务端都在莫名的等待。
为什么呢?
因为客户端和服务端都有阻塞式的方法,这些方法没有读到结束标记,那么就一直等。
而导致两端,都在等待。
*/
示例代码4------TCP上传文件(使用IO字符流)
把文件从客户端传到服务器端上。
容易出现的问题:没有客户端上传完后,文件结束标记,服务器无法停止连接Socket连接。
客户端代码:
package TCP;
import java.io.*;
import java.net.*;
public class TextClient {
public static void main(String[] args) throws Exception {
Socket s=new Socket("127.0.0.1", 10006);
//源:封装成文件对象
BufferedReader bufr=new BufferedReader(new FileReader("TCPClient.java"));
//目的:通过打印流将文件写入到socket输出流中以便于服务端接收
//Attention:在上传文件前,最好先把文件名发给服务端
PrintWriter out=new PrintWriter(s.getOutputStream(), true);
String line=null;
while((line=bufr.readLine())!=null){
out.println(line);
}
s.shutdownOutput();//关闭客户端的输出流,相当于给流中加入一个结束标记 -1;
//读取来自服务端的socket输入流
BufferedReader bufin=new BufferedReader(new InputStreamReader(s.getInputStream()));
String str=bufin.readLine();
System.out.println("server:"+str);
//勿忘记关闭资源
bufr.close();
s.close();
}
}
package TCP;
import java.io.*;
import java.net.*;
public class TextServer {
public static void main(String[] args) throws Exception {
ServerSocket ss=new ServerSocket(10006);
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+".....is connected !");
//源:来自客户端发送的socket输入流
BufferedReader bufin=new BufferedReader(new InputStreamReader(s.getInputStream()));
//目的:将文字写入到server.txt中
PrintWriter out=new PrintWriter(new FileWriter("server.txt"),true);
String line=null;
while((line=bufin.readLine())!=null){
out.println(line);
}
//向客户端发送消息
PrintWriter pw=new PrintWriter(s.getOutputStream(),true);
pw.println("文件上传成功!");
//别忘了关闭资源
out.close();
s.close();
ss.close();
}
}
/*客户端:
1.建立服务端点
2.读取客户端已有的图片数据
3.通过socket的输出流将数据发给服务端。
4.读取服务端反馈信息。
5.关闭。*/
package TCP;
import java.io.*;
import java.net.*;
public class PicClient {
public static void main(String[] args) throws Exception{
//建立socket服务
Socket s=new Socket("127.0.0.1", 10007);
//读取数据-->字节流
FileInputStream fis=new FileInputStream("1.jpg");
//输出数据-->获取socket输出流向服务端写数据
OutputStream out=s.getOutputStream();
byte []buf=new byte[1024];//定义缓冲区
int len=0; //字节长度
while((len=fis.read(buf))!=-1){
out.write(buf, 0, len);
}
s.shutdownOutput(); //告诉服务端已写完数据
//接受服务器发送的反馈信息
InputStream in=s.getInputStream();
byte[]bufin=new byte[1024];
int inLen=in.read(buf);
System.out.println("From Server:"+new String(buf, 0, inLen));
//关闭资源
fis.close();
s.close();
}
}
//服务端
package TCP;
import java.io.*;
import java.net.*;
public class PicServer {
public static void main(String[] args) throws Exception{
ServerSocket ss=new ServerSocket(10007);
Socket s=ss.accept();
String ip=s.getInetAddress().getHostAddress();
System.out.println(ip+"...... is connected!");
InputStream in=s.getInputStream();
FileOutputStream fos=new FileOutputStream("server.jpg");
byte[]buf=new byte[1024];
int len=0;
while((len=in.read())!=-1){
fos.write(buf, 0, len);
}
//向客户端发送消息(socket输出流)
OutputStream out=s.getOutputStream();
out.write("图片上传成功!".getBytes());
//关闭资源
fos.close();
s.close();
ss.close();
}
}
TCP客户端并发访问
客户端并发访问服务器时,把服务端的处理代码封装在Runnable实现子类的run方法中,并把服务器获的的Socket对象传给该实现子类的构造函数。服务端通过while循环启动多个线程,对多个客户端请求进行并发处理。这也是一般服务器的基本原理。
/*客户端:
1.建立服务端点
2.读取客户端已有的图片数据
3.通过socket的输出流将数据发给服务端。
4.读取服务端反馈信息。
5.关闭。*/
package TCP;
import java.io.*;
import java.net.*;
public class PicClient {
public static void main(String[] args) throws Exception{
//过滤连接(演示要用DOS窗口)
//限定java命令传入的参数只有一个(主函数录入)
if(args.length!=1){
System.out.println("请选择一个jpg格式的图片");
return;
}
//判断是否是文件而不是目录
File file=new File(args[0]);
if(!(file.exists()&& file.isFile())){
System.out.println("该文件有问题,不存在或者不是文件");
return;
}
//判断这个文件的 后缀名是不是JPG
if(!file.getName().endsWith(".jpg")){
System.out.println("图片格式错误");
return;
}
//限定图片大小为5MB以内
if(file.length()>1024*1024*5){
System.out.println("图片过大,应在5M以内");
return ;
}
//建立socket服务
Socket s=new Socket("127.0.0.1", 10007);
//读取数据-->字节流
FileInputStream fis=new FileInputStream("1.jpg");
//输出数据-->获取socket输出流向服务端写数据
OutputStream out=s.getOutputStream();
byte []buf=new byte[1024];//定义缓冲区
int len=0; //字节长度
//记得要将buf数组传进去,你咋老是记不住呢???
while((len=fis.read(buf))!=-1){
out.write(buf, 0, len);
}
s.shutdownOutput(); //告诉服务端已写完数据
//接受服务器发送的反馈信息
InputStream in=s.getInputStream();
byte[]bufin=new byte[1024];
int inLen=in.read(buf);
System.out.println("From Server:"+new String(buf, 0, inLen));
//关闭资源
fis.close();
s.close();
}
}
服务端代码演示:
示例5服务端有个局限性,当A客户端连接上以后,被服务端获取到,服务端执行具体流程。
这时B客户端连接,只有等待。因为服务端还没有处理完A客户端的请求。还没有循环回来
执行下次accept方法。所以,暂时获取不到B客户端对象。那么为了可以让多个客户端
同时并发访问服务端。那么服务端最好就是将每个客户端封装到一个单独的线程中去。这
样就可以同时处理多个客户端请求。如何定义线程?只要明确了每一个客户端要在服务端
执行的代码即可。将该代码存入run方法中。
package TCP;
import java.io.*;
import java.net.*;
public class PicServer {
public static void main(String[] args) throws Exception{
ServerSocket ss=new ServerSocket(10007);
while(true){
Socket s=ss.accept();
new Thread(new PicThread(s)).start();
}
}
}
class PicThread implements Runnable{
private Socket s;
public PicThread(Socket s) {
this.s=s;
}
public void run() {
String ip=s.getInetAddress().getHostAddress();
int count=0;
try {
System.out.println(ip+"......is connnected!");
InputStream in = s.getInputStream();
File file=new File(ip+"("+(count)+")"+".jpg");
while(file.exists()){
file=new File(ip+"("+(count++)+")"+".jpg");
}
FileOutputStream fos=new FileOutputStream(file);
byte[]buf=new byte[1024];
int len=0;
//又在这犯错误了!要记得将buf传进去,不然输出的肯定是空
while((len=in.read(buf))!=-1){
fos.write(buf, 0, len);
}
//向客户端发送消息(socket输出流)
OutputStream out=s.getOutputStream();
out.write("图片上传成功!".getBytes());
//关闭资源
fos.close();
s.close();
} catch (Exception e) {
throw new RuntimeException(ip+"上传失败!");
}
}
}
示例代码7:------客户端并发登陆
客户端通过键盘录入用户,服务端对这个用户名进行校验。如果该用户存在,在服务端显示xxx,已登陆。并在客户端显示 xxx,欢迎光临。 如果该用户不存在,在服务端显示xxx,尝试登陆。并在客户端显示 xxx,该用户不存在。 最多登录三次。登陆成功、登录三次都直接断开Socket
package tan;
import java.io.*;
import java.net.*;
class LoginClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("127.0.0.1", 10008);
//读取键盘录入
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//客户端发送所读取的数据
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
//读服务端发送的socket流(反馈信息)
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//只能发送三次
for (int x = 0; x < 3; x++) {
//先读取键盘录入的数据
String line = bufr.readLine();
//读取的有可能为空,要判断发送的信息是否为空
if (line == null)
break;
//发送这一行:line
out.println(line);
//读服务端发送的反馈信息(一行)
String info = bufIn.readLine();
System.out.println("info:" + info);
if (info.contains("欢迎")){
break;
}
}
//关闭资源
bufr.close();
s.close();
}
}
package tan;
import java.io.*;
import java.net.*;
public class LoginServer {
public static void main(String[] args) throws Exception {
ServerSocket ss=new ServerSocket(10008);
while(true){
Socket s=ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
//定义用户线程类
class UserThread implements Runnable {
private Socket s;
UserThread(Socket s) {
this.s = s;
}
public void run() {
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "....connected");
try {
//校验3次
for (int x = 0; x < 3; x++) {
//获取客户端发送的socket流-->利用到转换流
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//用户名 一个
String name = bufIn.readLine();
if (name == null){
break;
}
//服务端读取user.txt文件
BufferedReader bufr = new BufferedReader(new FileReader("user.txt"));
//想客户端发送信息
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
boolean flag = false;//定义标记 记录判断的结果 ,默认false
//一行行的读 可以读到结尾(操作的是文件)
while ((line = bufr.readLine()) != null) {
//怎么判断呢?判断有了怎样?没有又怎样?要根据循环判断的结果来判断
if (line.equals(name)) {
flag = true;//如果找到了数据则直接跳出
break;
}
}
//根据循环后的结果来判断
if (flag) {
//结果为真,则服务端输出name
System.out.println(name + ",已登录");
//服务端写出去告诉客户端
out.println(name + ",欢迎光临");
//已经校验成功,则直接break
break;
} else {
System.out.println(name + ",尝试登录");
out.println(name + ",用户名不存在");
}
}
s.close();
} catch (Exception e) {
throw new RuntimeException(ip + "校验失败");
}
}
}
URL
URL-:代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
URI 是统一资源标识符,而 URL是统一资源定位符。因此,笼统地说,每个 URL都是 URI,但不一定每个 URI都是 URL。这是因为 URI还包括一个子类,即统一资源名称 (URN),它命名资源但不指定如何定位资源。
常用方法示例:
intgetDefaultPort() :获取与此 URL关联协议的默认端口号。
String getFile() :获取此 URL的文件名。
String getHost() :获取此 URL的主机名(如果适用)。
String getPath() :获取此 URL的路径部分。
int getPort() :获取此 URL的端口号。
String getProtocol():获取此 URL的协议名称。
String getQuery() :获取此 URL的查询部分。
import java.net.*;
class URLDemo
{
public static void main(String[] args) throws Exception
{
//URL url=new URL("http://192.168.1.13:11000/myweb/demo.html");
URL url=new URL("http://192.168.1.13:11000/myweb/demo.html?name=haha&age=30");
System.out.println("getProtocol():"+url.getProtocol()); //http
System.out.println("getHost():"+url.getHost());//192.168.1.13
System.out.println("getDefaultPort():"+url.getDefaultPort());//80,如果关联的协议没有默认的端口,则值为-1;
System.out.println("getPort():"+url.getPort()); // 11000,如果没有设置则为-1;
System.out.println("getPath():"+url.getPath());// /myweb/demo.html
System.out.println("getFile():"+url.getFile());///myweb/demo.html?name=haha&age=30
System.out.println("getQuery():"+url.getQuery());//name=haha&age=30
/* int port = url.getPort();
if(port==-1)
port =80;
getPort() = -1
*/
}
}
URLConnection
它的作用于Socket类似,其实它内部封装了Socket,所以可以获取网络输入输出流。通过URL和URLConnection可以方便地对应用层网络数据进行读取和操作。
URLConnection———应用层
Socket-————传输层
import java.net.*;
import java.io.*;
class URLConnectionDemo
{
public static void main(String[] args) throws Exception
{
URL url=new URL("http://192.168.1.13:8080/myweb/demo.html");
URLConnection conn = url.openConnection();
System.out.println(conn);
InputStream in = conn.getInputStream();
byte [] buf =new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
}
}
Apache HttpClient
OKHttpClient