码迷,mamicode.com
首页 > 其他好文 > 详细

网络编程基础

时间:2016-06-26 14:11:26      阅读:277      评论:0      收藏:0      [点我收藏+]

标签:

网络编程基础



学习网络编程的原因


网络编程主要是实现计算机与计算机之间的数据传输问题,网络编程要与网页编程概念区分开来,Java一般不用来做桌面应用软件,PC机基本不用网络编程,但是如果以后要学习的是安卓方向,那么就会用到网络编程知识。那么为什么又要学习网络编程呢?因为在Java SE的小项目中,设计到网络编程、GUI编程的等都集合在一起的项目才是练手的最好项目,所以我觉得还是要好好学习网络编程。在网络编程中应该学习的内容是:分布在不同地域的计算机通过外部设备实现消息、数据、资源共享。

网络通讯的三要素


  • IP
    地址
    • 找到通讯双方计算机的方式
  • 端口
    • 找到双方通信软件的方式,不同的软件通过不同的端口对相应的软件进行监控
  • 协议
    • 计算机与计算机之间通讯需要外界设备,比如路由器,协议规定了路由双方必须采用相同的协议进行解析,否则根本不能正确的发送消息。协议用来规范通信双方发送的数据帧以及编码解码的规定。
IP地址

IP地址的本质就是一个由32位的二进制数组成的数据,但是为了便于人们读取,将其中的8位作为一个单元,切分成了4份,就形成了现在的方式,比如:192.168.101.44等,每一个切分后的块可表示的范围是0-255。

IP地址等于网络号+主机号构成,其中子网掩码中未255的部分全部都为网络号,IP地址可以分为三类:

  • A类地址: 一个网络号+三个主机号 2^24次方台机器 政府机关在用
  • B类地址: 两个网络号+两个主机号 2^16次方台机器 学校、银行等在用
  • C类地址 三个网络号+一个主机号 2^8次方台几区 私人使用
    • 我们平常使用的IP是从路由器过滤出来的。

在Java中使用使用了一个类来描述IP地址。在java.net.InetAddress类中,可以用来描述一个IP地址,下面的代码说明了该类的基本用法。

package com.jpzhutech.inetaddress;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class TestInetAddress {
    public static void main(String[] args) {
        InetAddress inetAddress = null;
        try {
            // 获取本机的信息
            inetAddress = InetAddress.getLocalHost();  //得到本机的IP地址,如果没装网卡就没有IP地址
            String hostName = inetAddress.getHostName();
            //System.out.println(hostName); //显示本机的主机名
            String hostAddress = inetAddress.getHostAddress();
            System.out.println(hostAddress);   //显示本机的ip地址

            /*获取别人的ip地址
            InetAddress byName = InetAddress.getByName("DESKTOP-G56LBO2"); //根据ip或者主机名就能得到相应的主机信息
            String hostName = byName.getHostName();
            System.out.println(hostName);
            */
            /* 根据域名得到ip地址的数组,这种情况也常会发生
            InetAddress[] allByName = InetAddress.getAllByName("www.baidu.com"); //为什么该方法会返回一个数组呢?String host可以写域名也可以写ip还可以写主机名,baidu的一个域名就注册了两个ip地址
            for(int i = 0 ; i < allByName.length;i++){
                InetAddress inetAddress2 = allByName[i];
                String hostAddress = inetAddress2.getHostAddress();
                System.out.println(hostAddress);
                String hostName = inetAddress2.getHostName();
                System.out.println(hostName);
            }
            */
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
}
端口

端口在Java中并没有使用类来描述,因为端口号仅仅是一个标识符,端口号从0-65535的范围,其它的无效,但是我们都能用0-65535吗?回答是不能,0-1023系统绑定了一些服务,不能用,如果用会报错。1024-49151系统绑定了一些松散的服务,松散的含义是有一些端口可能没被占用,有些被占用了,也可以使用这里的端口,如果发生冲突换一个端口就好了。1024-65535我们可以使用,如果发生冲突就换一个其他的端口号。49152-65535动态或者私有的端口,我们可以随便用,在使用之前使用cports查看你想使用的端口是否有端口占用。

协议

  • UDP通讯协议
    • 将数据封装成为数据包,不需要建立连接。
    • 每个包的大小限制在64K中。
    • 因为无连接所以不可靠,不可靠指的是可能发生数据包的丢失
    • 因为不需要建立连接,所有速度快
    • User Datagram Protocol的简写
    • UDP通信是不分客户端和服务端的,只分接收端和发送端
    • 例如:对讲机、游戏…
  • TCP通讯协议
    • 面向连接,有特有的通道
    • 在连接中传输大数据量
    • 通过三次握手机制建立连接,可靠协议
    • 通信前必须建立连接,效率稍低
    • 如:打电话、文件的传输

Java中Socket编程


在Java中网络通信业被称为Socket通信,Socket翻译为:套接字。我们可以理解为“插座”,在通信之前需要安装插座,之后建立管道进行通信。

UDP下Socket通信

  • DatagramSocket(UDP插座服务)
    • 接收端与发送端都要启动DatagramSocket服务
    • 既有接收数据的服务,也有发送数据的服务
  • DatagramPacket(数据包类)
    • 双方在传输数据时要初始化该包的实例

TestUdpSender.java

package com.jpzhutech.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;


public class TestUdpSender {

    public static void main(String[] args) {
        //建立UDP服务,很像码头运送货物的场景,在建立UDP时不需要指定端口等信息,这些信息应该被封装在数据包中
        DatagramSocket datagramSocket = null;
         DatagramPacket datagramPacket = null;
        try {
             datagramSocket = new DatagramSocket();
             //准备数据,将数据封装到数据包中
             String data = "这是我第一个UDP服务的例子";
             try {
                 datagramPacket = new DatagramPacket(data.getBytes(), data.getBytes().length, InetAddress.getLocalHost(), 9090);//第三个参数指的是发送的IP地址,最后一个参数是端口号
                 //调用UDP的服务发送数据包
                 try {
                    datagramSocket.send(datagramPacket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } 
            } catch (UnknownHostException e) {
                throw new RuntimeException(e);
            }
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }finally{
            //关闭资源实际上就是释放端口等资源
            if(datagramSocket != null){
                datagramSocket.close();
            }
        }
    }

    public void sender(){

    }

}

TestUdpReceive.java

package com.jpzhutech.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class TestUdpReceive {

    public static void main(String[] args) {

        DatagramSocket datagramSocket = null;
        DatagramPacket datagramPacket = null;
        try {
            //建立UDP服务,接收端必须套时时刻刻监听着发送端的端口,因为发送端根本不管接收端是否准备好,直接发送数据
             datagramSocket = new DatagramSocket(9090);

            //建立一个数据包的对象,发送端发来的数据会被放在该buf数组中
             byte[] buf = new byte[1024];
             datagramPacket = new DatagramPacket(buf, buf.length);

             //调用UDP服务接收数据
             try {
                datagramSocket.receive(datagramPacket); //阻塞式操作,如果没有接收到数据,就一直等待
                System.out.println("接收到的数据为:"+new String(buf,0,datagramPacket.getLength())); //第三个参数获取的是实际存储的字节数据的大小
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }finally{
            if (datagramSocket != null) {
                datagramSocket.close();
            }
        }

    }

}

如果将上述接收端的缓冲数组大小换成5而不是1024,那么很显然不能完全的读取到数据,就会出错,怎么避免这个问题?无解:这是由UDP协议本身决定的。

UDP协议中Socket编程步骤总结

  • 发送端
    • 建立UDP服务
    • 准备数据,把数据封装到数据包中进行发送,发送端的数据包要带上ip地址与端口号。
    • 调用UDP的send发送数据
    • 关闭UDP服务
  • 接收端
    • 建立UDP服务
    • 准备空的数据包接收数据,只需要知道端口号就行
    • 调用UDP的服务接收数据,使用receive()方法
    • 关闭UDP服务
UDP协议注意事项

在使用UDP协议进行网络通讯时,必须要注意:

  • 对方的IP地址
    • 单个人发送:指定其具体的ip地址
    • 广播地址:广播地址就是主机号为255的地址,给广播IP地址发送消息的时候,在同一个网络段的机器都能接收到信息,只有在UDP协议中才有广播地址发送这个说法
  • 服务的端口号
  • 数据包的格式

上述三个条件缺一不可,比如如果你的数据包的格式不对,接收端会拒绝你的数据,接收方会判定为你现在的数据为非法的数据,所以接收不到。每一个网络程序都有自己所处理的特定格式的数据,如果接收到数据不符合指定的格式,就会被当做垃圾数据丢弃(相当于加密),这么做是为了保证安全。

通常定义的数据格式:
version: time: sender: ip: flag(发送的标识符): content(真正要发送的内容)。

出现数据包丢失的情况

什么情况下会出现数据丢失的情况呢?

  • 带宽不足的情况
    • 局域网基本不存在带宽不足的情况
  • CPU处理能力不足
TCP下Socket通信

TCP通信特点:

  • TCP协议是基于IO流进行数据传输的,是面向连接的。
  • TCP进行数据传输时数据没有大小限制。
  • TCP面向连接,通过三次握手保证数据的完整性,是一个可靠的协议。
  • TCP是面向连接的,所以速度慢。
  • TCP是严格的区分客户端与服务器端。
  • 比如:打电话、文件传输使用TCP协议、下载等。

  • Socket(客户端)

  • ServerSocket(服务器端)

TcpClient.java

package com.jpzhutech.tcp;

/**
 * 端口被占用的异常可能会经常出现,这都是由于已经有程序在运行
 * @author 朱君鹏
 *
 */

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {
    public static void main(String[] args) {

        Socket socket = null;

        try {
            // 建立TCP服务
            socket = new Socket(InetAddress.getLocalHost(), 9090);
            // 获取socket对象的输出流对象
            OutputStream outputStream = socket.getOutputStream();
            // 利用输出流对象把数据写出即可
            outputStream.write("服务端你好".getBytes());
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

TcpServer.java

package com.jpzhutech.tcp;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;

public class TcpServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            //建立服务器端TCP连接
            serverSocket = new ServerSocket(9090);
            //建立输入流对象,其中accept方法也是阻塞型的方法,用于接收客户端的连接,如果没有客户端与其进行连接时,会一直等
            InputStream inputStream = serverSocket.accept().getInputStream();//想要使用InputStream,但是只有Socket才有,只能使用accept方法获取
            byte[] buf = new byte[1024];
            int length = 0 ;
            while((length = inputStream.read(buf)) != -1 ){
                System.out.println("接收到的数据为:"+new String(buf,0,length));
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}
TCP协议中Socket编程步骤总结

  • 建立TCP服务
  • 接收客户端的连接产生Socket
  • 获取对应的流对象,读取后写出数据
  • 关闭资源
TCP协议编程示例

TcpClient.java

package com.jpzhutech.tcp;

/**
 * 端口被占用的异常可能会经常出现,这都是由于已经有程序在运行
 * @author 朱君鹏
 *
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class TcpClient {
    public static void main(String[] args) {

        Socket socket = null;

        try {
            // 建立TCP服务
            socket = new Socket(InetAddress.getLocalHost(),9090);
            // 获取socket对象的输出流对象
            OutputStream outputStream = socket.getOutputStream();
            // 利用输出流对象把数据写出即可
            outputStream.write("服务端你好".getBytes());
            outputStream.flush();
            socket.shutdownOutput(); //关闭输出流,如果想知道为什么,注释该语句会看到其中的原因,参见文章:http://wiki.jikexueyuan.com/project/java-socket/socket-read-deadlock.html


            //获取输入流对象,读取服务端回送的数据
            InputStream inputStream = socket.getInputStream();
            byte[] buf = new byte[1024];
            int length = 0 ;

            while((length = inputStream.read(buf)) != -1 ){   //该行指令实际上没有执行
                System.out.println("客户端接收到的数据:"+ new String(buf, 0, length) );
            }

        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

TcpServer.java

package com.jpzhutech.tcp;


import java.io.IOException;
import java.io.InputStream;

import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            //建立服务器端TCP连接
            serverSocket = new ServerSocket(9090);
            //建立输入流对象,其中accept方法也是阻塞型的方法,用于接收客户端的连接,如果没有客户端与其进行连接时,会一直等
            Socket socket = serverSocket.accept();  //拿到与服务器端建立连接的客户端
            InputStream inputStream = socket.getInputStream();//想要使用InputStream,但是只有Socket才有,只能使用accept方法获取



            byte[] buf = new byte[1024];
            int length = 0 ;
            int count = 0 ;
            while((length = inputStream.read(buf)) != -1 ){  
                System.out.println("服务器端接收到的数据为:"+new String(buf,0,length));
            }


            //拿到输出流对象,输出数据
            OutputStream outputStream = socket.getOutputStream();
            String string = "客户端你也好啊!";
            outputStream.write(string.getBytes());
            outputStream.flush();
            String string2 = "你找我干嘛?";
            System.out.println(string2);
            socket.shutdownOutput(); //关闭输出流,如果想知道为什么,注释该语句会看到其中的原因,只有有了该语句,在客户端的read(buf)方法中才会在最后返回-1,程序才能结束,否则会造成死锁




        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

将上述的代码改造,得到可以产生类似QQ中问答效果的代码。

ChatClient.java

package com.jpzhutech.tcpqq;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;

public class ChatClient {

    public static void main(String[] args) {
        Socket socket = null;
        try {
             socket = new Socket(InetAddress.getLocalHost(), 9090);
             OutputStream outputStream = socket.getOutputStream(); //获取字节流
             OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); //将字节流转化为字符流

             //获取键盘的输入流对象
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));  //将键盘的字节流输入转换为字符流
             String line = null;

             InputStream inputStream = socket.getInputStream();
             BufferedReader bufferedReader2 = new BufferedReader(new InputStreamReader(inputStream));

             //不断读取键盘的数据,然后输出
             while((line = bufferedReader.readLine()) != null){
                 outputStreamWriter.write(line+"\r\n");
                 //刷新
                 outputStreamWriter.flush();

                 //读取服务端回送的数据
                 line = bufferedReader2.readLine();
                 System.out.println("服务端回送的数据是:" + line);

             }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally{
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

}

ChatServer.java

package com.jpzhutech.tcpqq;


import java.io.BufferedReader;
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 ChatServer {

    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
             serverSocket = new ServerSocket(9090);
             //产生socket
             Socket socket = serverSocket.accept();

             //获取socket的输入流对象
             InputStream inputStream = socket.getInputStream();  //得到字节流
             InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
             BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
             String line = null;

            // 获取socket的输出流
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
                    socket.getOutputStream());
            // 获取键盘的输入流对象
            BufferedReader bufferedReader1 = new BufferedReader(
                    new InputStreamReader(System.in));

             while((line = bufferedReader.readLine()) != null){
                 System.out.println("服务器接收到的数据:"+line);

                 System.out.println("请输入回送给客户端的数据");
                 line = bufferedReader1.readLine();
                 outputStreamWriter.write(line+"\r\n");
                 outputStreamWriter.flush();
             }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally{
            if(serverSocket != null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

}

山寨Tomcat服务器


对上述ChatClient.java和ChatServer.java代码的思考,Tomcat服务器实际上的原来和该代码的原理类似,客户端给服务器发一个请求,服务器端会对相应的请求进行处理,上面的代码实现的是客户端说一句话,服务器端会对你说出的话进行响应,很相似,我们可以针对上述代码进行改造,实现一个山寨的Tomcat服务器。实际上浏览器与服务器之间的通讯就是使用了TCP协议。浏览器就是客户端,有兴趣的朋友可以自己实现。

网络编程基础

标签:

原文地址:http://blog.csdn.net/jpzhu16/article/details/51762025

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!