码迷,mamicode.com
首页 > 编程语言 > 详细

黑马程序员-Java 网络编程

时间:2015-04-18 20:40:35      阅读:199      评论:0      收藏:0      [点我收藏+]

标签:黑马程序员   网络编程   socket   

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——

一、概要

学习网络编程需要向对网络模型有一定的了解,主要需要了解的网络模型有OSI参考模型和TCP/IP参考模型,现在TCP/IP模型应用最为广泛,网络编程一般都是针对TCP/IP协议参考模型的编程。但是作为学习时,OSI的学习也是必不可少的,OSI分为七层协议,分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。TCP/IP模型只有四层,分别是:网络访问层、互联网层、传输层和应用层。接下来要学习的内容主要在传输层(UDPTCP协议)。

二、TCPUDP和套接字

说道TCPUDP不得不对两者进行一次比较,首先TCPUDP的相同之处在于其二者都是传输层的协议,提供端到端服务。不同之处在于:

  • UDPUDP不需要建立连接,协议头部简单,不需要反馈帧,适用于对数据准确度要求不到量大,允许丢帧出现,每个暑假的大小限制在64K内,是一种不可靠连接,但是速度快。适用于对数据可靠度要求不高,占用带宽较大,允许丢帧,传输延迟小的情况:如在线播放视频、实时语音/视频聊天、远程监控等。
  • TCPTCP传输数据前需要先建立连接,即三次握手,断开连接时需要四次以释放连接,协议头部数据复杂,数据需要反馈帧来确保数据的每一帧都成功送达,是一种可靠连接,但是效率较UDP稍低。TCP适合用于对数据可靠度要求高的情况:如文件传输、浏览网页、网络聊天、控制信息等。
    套接字是TCP/IP的基本单元,一个IP地址和一个端口号在一起可以称为一个套接字,针对不同的协议UDP对应的套接字为DatagramSocketTCP对应的套接字为SocketServerSocket,是为网络服务提供的一种机制,通信的两端都能拿到Socket,网络通信其实就是Socket之间的通信,数据在两端通过SocketIO进行传输。通信如下图所示:
技术分享
套接字间的通信

三、UDP

Java中操作UDP协议的相关类为DatagramSocket是用来发送和接收数据报包的套接字,数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编址和路由的。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。 、DatagramPacketUDP的数据包,用于UDP数据的封装。
一般使用UDP进行数据传输时的步骤如下:

  1. 建立UDP套接字服务。
  2. 封装待发送的数据(用DatagramPacket类进行封装)。
  3. 使用第一步建立的套接字服务将数据发送出去(使用send(DatagramPacket)方法)。
  4. 关闭资源。

UDP数据的一般接收步骤如下:

  1. 建立UDP套接字服务,指定监听的端口。
  2. 定义数据包,用于存储待接收的数据。
  3. 通过UDP套接字服务接收数据(使用receive(DatagramPacket)方法接收数据)。
  4. 通过数据包取出接收到的数据。
  5. 关闭资源。

其中套接字的receive方法是一种线程阻塞式的方法,如同读取键盘输入数据一样,没有数据会等待数据,当数据到来时会唤醒线程。下面通过一个简易聊天程序演示UDP套接字服务的使用:

import java.io.*;
import java.net.*;
// 定义发送线程
class ChatSender implements Runnable{
    private DatagramSocket mSocket;
    public ChatSender(DatagramSocket socket) {
        mSocket = socket;
    }
    public void run() {
        try {
            // 用于读取键盘输入数据
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(System.in));
            String line = null;
            while((line = reader.readLine()) != null) {
                // 当输入end后就不再发送
                if(line.equals("end")) break;
                // 封装发送数据包
                DatagramPacket packet = 
                       new DatagramPacket(line.getBytes(),line.length(),
                                    InetAddress.getByName("127.0.0.1"), 8888);
                // 发送数据
                mSocket.send(packet);
                // 接收回复数据
                byte[] buf = new byte[1024];
                DatagramPacket rpacket = new DatagramPacket(buf, buf.length);
                mSocket.receive(rpacket);
                int len = rpacket.getLength();
                String data = new String(rpacket.getData(), 0, len);
                // 显示回复数据
                System.out.println("回复:"+data);
            }
            // 关闭流和套接字
            reader.close();
            mSocket.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

class ChatReceiver implements Runnable {
    private DatagramSocket mSocket;
    public ChatReceiver(DatagramSocket socket) {
        mSocket = socket;
    }
    public void run() {
        try {
            while(true) {
                // 接收消息数据
                byte[] buf = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                mSocket.receive(packet);
                int len = packet.getLength();
                // 回复数据(转成大写)
                String data = new String(packet.getData(), 0, len).toUpperCase();
                if(data.contains("END")) break;
                // 封装回复数据
                DatagramPacket spacket = 
                       new DatagramPacket(data.getBytes(),data.length(),
                                    InetAddress.getByName("127.0.0.1"), packet.getPort());
                mSocket.send(spacket);
            }
            mSocket.close();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}
class ChatDemo {
    public static void main(String[] args) throws Exception{
        // 创建一个udp套接字给发送者
        new Thread(new ChatSender(new DatagramSocket())).start();
        // 创建一个监听8888端口的套接字给接收者
        new Thread(new ChatReceiver(new DatagramSocket(8888))).start();
    }
}

程序的运行效果如下图所示,每当输入一条数据,然后发送,便会得到一条回应,内容是将发送过去的内容装换成大写并返回。

技术分享
UDP聊天程序运行效果

另外值得说明的是,UDP可以发送广播数据,当将目标地址写为192.168.0.255这种以255结尾的类型IP地址时,此地址为广播地址,在同一个局域网,并且在同一个网段时(也就是地址为192.168.0.1-192.168.0.254内时,便都可以接收到其发送的数据),也可以将目标地址设置为255.255.255.255,这种广播发送的范围会更加广泛,一般整个局域网都可以接收到(如果路由器不丢弃的话)。这种功能可用于扫描等,如飞秋上线时通知其他同局域网内的用户。

四、TCP

TCP用于可靠数据的传输,当数据不能丢包或者不能出错时都会使用TCP协议来传输数据,既然可靠性提高了,那么必须牺牲点什么,TCP牺牲的便是效率,TCP连接通过三次握手建立了有连接的服务,其三次握手可以简单地说如三句话,“我来了”,“欢迎欢迎”,“谢谢”。也就是第一次高速对方我需要请求连接,第二次对方回应你的请求,第三此我收到了对方的请求然后回应对方正式开始。
UDP操作上的不同再用,TCP将请求者和被请求者分别称为客户端和服务端,对应的套接字类分别为SocketServerSocketTCP的数据不用封装成数据包,二是通过IO流相互通信。
下载文件,上传文件等一般都是使用TCP协议,下面通过一个示例演示通过TCP协议进行文件的并发上传操作,文件的并发上传需要注意的是要为每个Socket单独启动一个线程处理上传任务,不能让任何动作影响ServerSocketaccept()方法,这样才能保证并发上传文件。示例代码如下:

import java.io.*;
import java.net.*;
class UploadClient {
    public static void main(String[] args) throws Exception {
        if(args.length < 1) {
            System.out.println("请指定一个文件"); return ;
        }

        File file = new File(args[0]);
        if(!(file.exists() && file.isFile())) {
            System.out.println("文件不存在"); return ;
        }

        // 上传到指定的位置
        Socket socket = new Socket("127.0.0.1", 8888);
        BufferedInputStream bin = 
            new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream bout = 
            new BufferedOutputStream(socket.getOutputStream());
        byte[] buf = new byte[1024];
        int len = 0;
        // 将文件写入流中
        while((len=bin.read(buf)) != -1) {
            bout.write(buf, 0, len);
        }
        //告诉服务端数据已写完
        socket.shutdownOutput();

        // 读取服务端的回复
        InputStream in = socket.getInputStream();
        byte[] buffer = new byte[1024];
        int num = in.read(buffer);
        System.out.println(new String(buffer, 0, num));

        bin.close();
        socket.close();
    }
}

class UploadServer {
    public static void main(String[] args) throws Exception {
        ServerSocket mServerSocket = new ServerSocket(8888);
        while(true) {
            // 接收客户端的请求
            Socket socket = mServerSocket.accept();
            // 但接受到了客户端的上传后,便为其开启一个线程用于上传数据
            new Thread(new FileUploadThread(socket)).start();
        }
        //mServerSocket.close();
    }
}
class FileUploadThread implements Runnable {
    private Socket mSocket;
    FileUploadThread(Socket socket) {
        mSocket = socket;
    }
    public void run() {
        // 获取客户端的ip
        String ip  = mSocket.getInetAddress().getHostAddress();
        try {
            System.out.println(ip + "开始上传");
            BufferedInputStream bin = 
                new BufferedInputStream(mSocket.getInputStream());
            // 用客户端ip和当前时间作为文件名称
            String filename = "files\\" + ip + "-" 
                + Long.toHexString(System.currentTimeMillis()) + ".jpg";
            File file = new File(filename);
            BufferedOutputStream bout = 
                new BufferedOutputStream(new FileOutputStream(file));
            byte[] buf = new byte[1024];
            int len = 0;
            while((len=bin.read(buf))!=-1) {
                bout.write(buf, 0, len);
            }
            // 上传结束后,回复客户端一句“上传成功”
            OutputStream out = mSocket.getOutputStream();
            out.write("上传成功".getBytes());
            bout.close();
            mSocket.close();
        }
        catch (Exception e) {
            throw new RuntimeException(ip+"上传失败");
        }
    }
}

使用ServerSocket完成一个简易的服务器,然后使用浏览器访问这个服务器,根据不同的访问内容,返回不同的响应内容,如访问"/Hello"返回“你好啊!”,访问"/Time"返回当前时间等,示例代码如下:

import java.io.*;
import java.net.*;
import java.util.*;
import java.text.*;
class HttpServer {
    public static void main(String[] args) throws Exception {
        ServerSocket mServer = new ServerSocket(8888);
        while(true) {
            // 接受客户端请求
            Socket socket = mServer.accept();
            // 获取请求流
            BufferedInputStream bin = 
                new BufferedInputStream(socket.getInputStream());
            byte[] buffer = new byte[1024];
            int len = bin.read(buffer);
            // 获取请求内容如"/index.html"为"127.0.0.1/index.html"的
            String request = new String(buffer, 0, len).split(" ")[1];
            System.out.println(request);

            // 回复流
            BufferedOutputStream bout = 
                new BufferedOutputStream(socket.getOutputStream());
            // 请求"http://127.0.0.1/Hello"时返回"你好啊!"
            if(request.equalsIgnoreCase("/Hello")) {
                bout.write("你好啊!".getBytes());
            } else if(request.equalsIgnoreCase("/Time")) {
                // "/Time"时,返回当前时间
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy:MM:DD HH:mm:ss");
                String time  = dateFormat.format(new Date());
                bout.write(time.getBytes());
            } else if(request.equalsIgnoreCase("/Exit")) {
                // "/Exit"时,关闭服务器
                bout.write("已暂停服务".getBytes());
                break;
            }
            bout.close();
        }
        mServer.close();
    }
}

在浏览器中访问"http://localhost:8888/time"时,会获取服务器的当前时间,如下图:

技术分享
浏览器访问效果图

五、其他

  1. URL类:类 URL 代表一个统一资源定位符,它是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
  2. URLConnection类:代表程序和URL之间的通信链接,一般的使用
    1. 通过在 URL 上调用 openConnection 方法创建连接对象。
    2. 处理设置参数和一般请求属性。
    3. 使用 connect 方法建立到远程对象的实际连接。
    4. 远程对象变为可用。远程对象的头字段和内容变为可访问。
      通过URLConnection从网络上下载一张图片,将其保存至本地。示例代码如下:
import java.net.*;
import java.io.*;
class  URLConnectionDemo {
    public static void main(String[] args) throws Exception {
        // 一个网络图片地址
        URL url = new URL("http://www.itheima.com/images_new/logo.jpg");
        // 打开网络连接
        URLConnection conn = url.openConnection();
        // 输入流
        BufferedInputStream bin = new BufferedInputStream(conn.getInputStream());
        // 输出流,用于保存下载的图片文件
        BufferedOutputStream bout = 
            new BufferedOutputStream(new FileOutputStream("pic.jpg"));
        byte[] buf = new byte[1024];
        int len = 0;
        while((len = bin.read(buf)) != -1) {
            bout.write(buf, 0, len);
        }
        // 关闭流
        bin.close();
        bout.close();
    }
}
  1. InetSocketAddress类:此类实现 IP 套接字地址(IP 地址 + 端口号)。即这个类将IP地址和端口封装在了一起,使用起来更加方便些。
  2. 域名解析:是指解析出域名指向网站的IP地址,一般域名解析的步骤为:首先查看系统hosts文件中是否已经存在对应的域名关系,若已经存在,则之间访问其对应的ip,如localhost对应的ip为127.0.0.1,这是本机的回环地址,访问127.0.0.1就是在访问本机自身。如果hosts文件中没有待解析的域名,则系统会启动域名解析服务,即想域名解析服务器请求对应的ip地址(这些域名解析服务器一般都是固定的公共域名解析服务器,电信的、联通的等),找到域名对应的地址后便可以请求改地址。

黑马程序员-Java 网络编程

标签:黑马程序员   网络编程   socket   

原文地址:http://blog.csdn.net/decting/article/details/45110559

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