黑马程序员——JAVA拾遗之网络编程

来源:互联网 发布:英伟达10系列显卡知乎 编辑:程序博客网 时间:2024/05/11 21:37
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
java的Socket编程首先要对网络模型有所了解,平时用来分析网络模型的时候都是用7层结构,但是实际应用的时候使用的都是4层的模型。关于网络模型的知识,分哪几层,每一层有什么作用不是这里要讲的重点。大家可以搜索网络模型的相关知识。下面来介绍与java网络编程相关的知识。

网络编程有几个要素:IP地址,端口号,协议TCP/IP。

IP地址
在java的网络编程中,用InetAddress来封装IP地址。它有两个子类Inet4Address 和 Inet6Address,分别表示IPv4地址和IPv6地址。调用这个类的getHostAddress() 方法可以获取IP地址的字符串形式。

端口号
用于标识进程的逻辑地址,是不同进程的标识,有效的端口号范围是0-65535,其中0-1024是徐彤使用或保留端口。

传输协议
简单理解就是网络中的通讯规则,Socket编程讲的协议一般都是TCP/IP协议,TCP/IP中常用的协议有TCP 和 UDP两种,这里只总结java网络编程对这两种协议的支持。

java的网络编程中 Socket 的概念
所谓 Socket 通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

简单理解Socket需要抓住几个关键点:

1.Socket就是网络服务提供的一种机制
2.通信的两端都有Socket
3.网络通信其实就是Socket间的通信
4.数据在两个Socket间通过IO传输

UDP协议
特点:
1.将数据及源和目的封装成数据包中,不需要建立连接
2.每个数据报的大小限制在64K中
3.因无连接,是不可靠协议
4.不需要建立连接,速度快

UDP传输建立步骤:
1.建立DatagramSocket和DatagramPacket的对象,前者表示UDP协议的套接字,后者表示数据包
2.建立发送端与接收端
3.建立数据包
4.调用Socket的发送和接收方法
最后,根据需要确定是否关闭Socket


发送端示例
<span style="font-size:14px;">public class UdpSend{    public static void main(String[] args) throws Exception{        DatagramSocket ds = new DatagramSocket(60010);        String str = null;        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));        while(null != (str = bufr.readLine())){            byte[] bt = new String(str).getBytes();            DatagramPacket dp = new DatagramPacket(bt, bt.length,                InetAddress.getByName("192.168.0.106"), 60000);            ds.send(dp);            if ("88".equals(str)) {                break;            }        }    }}</span>

接收端示例
<span style="font-size:14px;">public class UdpReceive{    public static void main(String[] args) throws Exception{        DatagramSocket ds = new DatagramSocket(60000);        byte[] bt = new byte[1024];        DatagramPacket dp = new DatagramPacket(bt, bt.length);        while(true){            ds.receive(dp);            System.out.println(dp.getAddress().getHostAddress()+"..."+dp.getPort());            String str = new String(dp.getData(),0,dp.getLength());            System.out.println("收到" + str);            if("88".equals(new String(dp.getData(),0,dp.getLength()))){               break;             }        }        ds.close();    }}</span>

先运行UdpReceive,之后运行UdpSend,在UdpSend的窗口中输入字符串,会如下显示
192.168.0.106...60010
收到123
192.168.0.106...60010
收到asdf
192.168.0.106...60010
收到zxcv
……


可以看到数据包dp中封装了InetAddress的对象,用getAddress()可以获取到,之后用getHostAddress()方法获取到了InetAddress对象中的IP地址,数据包中还封装了端口。最后用getData()获取到了封装在dp中的数据。


TCP协议
特点:
1.建立连接,形成传输数据的通道
2.在连接中进行大数据传输
3.通过三次握手完成连接,是可靠协议
4.必须建立连接,效率会稍低


TCP协议建立通信的基本步骤


TCP分客户端和服务端,客户端对应的对象是Socket,服务端是ServerSocket 
1.使用 Socket 和 ServerSocket 类分别建立客户端和服务端的套接字,指定主机和端口。
(注意:TCP是面向连接的,所以建立Socket对象时,必须已经存在ServerSocket服务,一旦建立Socket对象,连接就已经建立,否则会发生ConnectException,提示 Connection timed out: connect)
2.获取流套接字中的流,通过流进行数据传输。
(客户端通过输出流OutputStream向服务端写数据,通过InputStream从服务端读取数据;服务端通过accept()方法获取连接过来的Socket对象后,通过Socket对象的InputStream读取客户端传过来的数据,通过OutputStream给客户端返回数据。accept()方法是阻塞式的,没有链接就会等待)
3.服务端获取数据后,关闭客户端,断开连接。
最后,根据需要来决定是否关闭服务端。

  


最基本的客户端发送,服务端接收的示例:客户端发送一条数据,服务端接收,代码如下——
<span style="font-size:14px;">//客户端public class TcpClient{    public static void main(String[] args) throws Exception{        //创建客户端的Socket服务,指定主机和端口        Socket s = new Socket("192.168.0.106",60000);//本机的IP地址,发送端口为60000        OutputStream out = s.getOutputStream();        String str = "test";        out.write(str.getBytes());    }}</span>


<span style="font-size:14px;">//服务端public class TcpServer{    public static void main(String[] args) throws Exception{        //建立服务端Socket服务,并监听端口        ServerSocket ss = new ServerSocket(60000);        //获取连接过来的客户端对象        Socket s = ss.accept();                System.out.println("IP:" + s.getInetAddress().getHostAddress());        //获取发送过来的数据        InputStream in = s.getInputStream();        byte[] bt = new byte[1024];        int len = in.read(bt);        System.out.println(new String(bt, 0 , len));        s.close();        ss.close();    }}</span>


当执行TcpClient的代码后,TcpServer会打印
IP:172.16.177.165
test

可以看到,客户端的IP和传输的数据都被服务端获取到了

在实际的应用情况下,客户端只发送数据,服务端只接收数据的情况基本是不存在的,客户端和服务端都会同时接收对方的数据,并且会给对方发送数据,就想下面演示的这样:
<span style="font-size:14px;">/** * 客户端有发送有接收 */public class TcpClient2{    public static void main(String[] args) throws Exception{        Socket s = new Socket("192.168.0.106",60000);        OutputStream out = s.getOutputStream();        out.write("TcpClient say hello to TcpServer".getBytes());        InputStream in = s.getInputStream();        byte[] bt = new byte[1024];        int len = in.read(bt);        System.out.println(new String(bt, 0 ,len));        s.close();    }}</span>


<span style="font-size:14px;">/** * 服务端有接收有返回 */public class TcpServer2{    public static void main(String[] args) throws Exception{        ServerSocket ss = new ServerSocket(60000);        Socket s = ss.accept();        System.out.println("ip:" + s.getInetAddress().getHostAddress());        InputStream in = s.getInputStream();        byte[] bt = new byte[1024];        int len = in.read(bt);        System.out.println(new String(bt, 0, len));        OutputStream out = s.getOutputStream();        out.write("TcpServer : nice to meet you".getBytes());        s.close();        ss.close();    }}</span>

先执行 TcpServer2 ,启动服务,之后再执行 TcpClient2 ,可以看到结果

TcpServer2输出

ip:192.168.0.106
TcpClient say hello to TcpServer

TcpClient2输出
TcpServer : nice to meet you


TcpClient2发送给TcpServer2一条数据"TcpClient say hello to TcpServer",TcpServer2收到这条数据,并且返回一条数据"TcpServer : nice to meet you",而TcpClient2接收到了这条数据。最后TcpServer2断开了连接,停止了服务,整个过程终止。

用TCP实现客户端文件的上传

思路:
1.客户端建立与服务端的Socket连接
2.客户端读取本地文件,获取 Socket 的 OutputStream 后通过OutputStream把数据发送给服务端。
3.服务端通过InputStream读取到客户端送来的数据后,通过文件形式输出。
4.最后关闭使用到的流,并且断开与客户端的Socket连接。

示例代码
<span style="font-size:14px;">//客户端public class UploadPicClient{    public static void main(String[] args) throws Exception{        Socket s = new Socket("192.168.0.106",60000);        FileInputStream fin = new FileInputStream("d:\\1.jpg");        OutputStream fout = s.getOutputStream();        byte[] bt = new byte[1024];        int len = 0;        while((len = fin.read(bt))!=-1){            fout.write(bt, 0, len);        }        s.shutdownOutput();        InputStream in = s.getInputStream();        byte[] btin = new byte[1024];        int num = in.read(btin);        System.out.println(new String(btin, 0, num));        fin.close();        s.close();    }}</span>


<span style="font-size:14px;">//服务端public class UploadPicServer{    public static void main(String[] args) throws Exception{        ServerSocket ss = new ServerSocket(60000);        Socket s = ss.accept();        InputStream in = s.getInputStream();        FileOutputStream fout = new FileOutputStream("f:\\pic.jpg");        byte[] bt = new byte[1024];        int len = 0;        while((len = in.read(bt))!= -1){            fout.write(bt, 0, len);        }        OutputStream out = s.getOutputStream();        out.write("上传成功".getBytes());        fout.close();        s.close();        ss.close();    }}</span>


上面的代码实现了将客户端本地d盘下的1.jpg文件通过TCP协议上传到服务器,服务器收到后以pic.jpg的名称存储到服务器的f盘下。需要注意:
客户端发送完数据以后需要执行s.shutdownOutput();目的是让服务端知道数据已经传输结束,否则(len = in.read(bt))!= -1这个判断条件会一直持续,服务端程序无法继续。

在实际应用的过程中,服务端对客户端提供服务不会像上例中那样是1对1的,往往都是服务端同时对多个客户端提供服务,因此需要引入多线程的方式来实现服务端。上面的例子用多线程的方式改造,来看看改造以后的代码:

<span style="font-size:14px;">//定义处理客户端请求的线程类class ClientSocket implements Runnable{    private Socket s;    ClientSocket(Socket s){        this.s = s;    }    public void run(){        int count = 1;        try {            String ip = s.getInetAddress().getHostAddress();            File f = new File("f:\\"+ip+"("+count+")"+".jpg");            while(f.exists()){                f = new File("f:\\"+ip+"("+ (count++) +")"+".jpg");            }            InputStream in = s.getInputStream();            FileOutputStream fout = new FileOutputStream(f);            byte[] bt = new byte[1024];            int len = 0;            while((len = in.read(bt))!= -1){                fout.write(bt, 0, len);            }            OutputStream out = s.getOutputStream();            out.write("上传成功".getBytes());            fout.close();            s.close();        } catch (Exception e) {            System.err.println("上传失败");        }    }}</span>


<span style="font-size:14px;">//持续监听客户端的请求,每个请求分配一个线程public class UploadPicThreadServer{    public static void main(String[] args) throws Exception{        ServerSocket ss = new ServerSocket(60000);        while(true){            Socket s = ss.accept();            new Thread(new ClientSocket(s)).start();        }    }}</span>


在上面的代码中,服务端把每个连接到的客户端封装到单独的线程中,同时处理多个客户端请求。并对每个客户端上传的文件根据IP进行命名,对同一个IP上传的文件进行了编号。防止不同客户端上传的文件互相覆盖,也防止同一个文件上传的不同的文件互相覆盖。

基本的Socket原理知晓以后就可以来推测常用的网络传输工具的工作过程和原理了,下面拿浏览器和tomcat举例,先写下如下的代码:
<span style="font-size:14px;">public class IEServer{    public static void main(String[] args) throws Exception{        ServerSocket ss = new ServerSocket(60000);        while(true){            Socket s = ss.accept();            InputStream in = s.getInputStream();            byte[] bt = new byte[1024];            int len = in.read(bt);            System.out.println(new String(bt, 0, len));            System.out.println("end");            OutputStream out = s.getOutputStream();            out.write("hahaha".getBytes());            s.close();        }    }}</span>


先执行这段代码,之后尝试用浏览器访问本机的如下地址:http://127.0.0.1:60000/
可以看到IEServer打印了如下的数据:
<span style="font-size:14px;">GET /favicon.ico HTTP/1.1Host: 127.0.0.1:60000User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3Accept-Encoding: gzip, deflateConnection: keep-aliveend</span>

而浏览器显示了字符串hahaha。
可以看到,在浏览器访问服务端的时候,给服务端发送了一长串HTTP协议的消息头。这个消息头里是一些浏览器和服务器间的访问规则,比如浏览器使用的协议是HTTP的1.1版本,能接受的数据类型在Accept后,浏览器的语言Language等等。这些信息可以让服务端知晓浏览器对数据的需求。


下面再拿tomcat服务器来举例,在本地的tomcat服务器路径webapps下建一个myweb文件夹,里面写一个HTML文件。
代码任意,比如我写:
<span style="font-size:14px;"><html><body><font color="red">hahaha</font><h1>oooooooooo</h1></body></html></span>

保存名称为1.html

启动tomcat后,用如下代码来模拟浏览器访问tomcat的过程,打印tomcat返回的数据:
<span style="font-size:14px;">public class TomcatClient{    public static void main(String[] args) throws Exception{        Socket s = new Socket("127.0.0.1", 8080);        //模拟浏览器进行消息发送        PrintWriter p = new PrintWriter(s.getOutputStream(), true);        p.println("GET /myweb/1.html HTTP/1.1");        p.println("Host: 127.0.0.1:60000");        p.println("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0");        p.println("Accept: */*");        p.println("Accept-Language: zh-cn");        p.println("Accept-Encoding: gzip, deflate");        p.println("Connection: closed");        p.println();                InputStream in = s.getInputStream();        byte[] bt = new byte[1024];        int len = 0;        while((len = in.read(bt)) != -1){            System.out.println(new String(bt, 0, len));        }    }}</span>

注意,tomcat是以空行来识别请求消息是否结束,因此在消息头后面一定要加个空行。

执行代码后,可以看到tomcat返回了如下数据:
<span style="font-size:14px;">HTTP/1.1 200 OKServer: Apache-Coyote/1.1Accept-Ranges: bytesETag: W/"105-1410503043928"Last-Modified: Fri, 12 Sep 2014 06:24:03 GMTContent-Type: text/htmlContent-Length: 105Date: Fri, 12 Sep 2014 08:22:36 GMTConnection: close<html><body><font color="red">hahaha</font><h1>oooooooooo</h1></body></html></span>

如果接受数据的是浏览器,因为浏览器中有各种解析引擎(html,css,js等等),就可以解析返回的数据,把后面的那一段html代码解析成对应的显示方式。

java 中对 Socket 编程中涉及到的URL地址用一个类URL来描述,看下面的代码:
<span style="font-size:14px;">URL url = new URL("http://127.0.0.1:8080/myweb/demo.html?name=zhangsan&age=18");System.out.println("" + url.getProtocol());System.out.println("" + url.getPort());System.out.println("" + url.getHost());System.out.println("" + url.getPath());System.out.println("" + url.getFile());System.out.println("" + url.getQuery());</span>

这段代码的执行结果是:
http
8080
127.0.0.1
/myweb/demo.html
/myweb/demo.html?name=zhangsan&age=18
name=zhangsan&age=18

可以看到getPath和getFile的区别,getPath是不带查询部的,而getFile是带着查询部的。getQuery会返回查询部的参数。

URL类有方便的操作URL地址外,还有什么作用呢?让我们回到刚才看看那个打印tomcat返回数据的例子:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
…………


tomcat返回的数据是以这样的信息头来展示的,但是对浏览器来说,最重要的其实是下面html代码部分,用URL类来改造之前的那个例子中的代码:
<span style="font-size:14px;">public class URLTest{    public static void main(String[] args) throws Exception{        URL url = new URL("http://127.0.0.1:8080/myweb/1.html");        URLConnection conn = url.openConnection();        InputStream in = conn.getInputStream();        byte[] bt = new byte[1024];        int len = 0;        while((len = in.read(bt))!=-1){            System.out.println(new String(bt, 0, len));        }    }}</span>

执行代码之后,看到以下输出:
<html>
<body>
<font color="red">hahaha</font>
<h1>oooooooooo</h1>
</body>

</html>


可以看到,消息头已经被隐藏了。URLConnection对象还可以获取服务器返回数据的各种信息,比如Content-Type等,都有对应的方法,这里就不一一例举了。





0 0