黑马程序员-Java网络编程

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

2015.10.11
2016.3.12

目录:
1. IP
2. Socket


1. IP

连接到互联网上每台计算机或其它设备都规定一个唯一的地址,有了它我们才能从千万台计算机中找到目标计算机,并与之通信。可以理解成电话号码,有了这个号码就可以和对方进行通话。

IP是由4个字节组成,也就是4组8位二进制数组成,例如01111111 00000001 00000000 00000000。通常,为了方便记忆,我们转换成十进制来表示。例如以上例子可以写成:127.1.0.0。这种表示形式就称为“点分十进制表示法”。

Java中使用InetAddress类表示IP地址。
使用InetAddress类的静态方法创建一个IP地址对象:

//使用getByName静态方法通过主机名获取IP地址(主机名为域名)
InetAddress baidu = InetAddress.getByName("www.baidu.com");

//使用getByName静态方法通过主机名获取IP地址(主机名为IP地址的字符串表示形式)
InetAddress.getByName("195.158.158.123");

// 使用InetAddress的静态getLocalHost方法获取本地IP地址
InetAddress ip = InetAddress.getLocalHost();
// 使用getHostName方法获取IP地址的主机名
String hostName = ip.getHostName();//DESKTOP-TAU56EN
// 使用getHostAddress方法获取IP地址字符串
String hostAddress = ip.getHostAddress();//192.168.1.7

// 使用isReachable方法判断该IP地址是否可到达
boolean flag = baidu.isReachable(50);// true

2. Socket

简单说套接字就是连接两个网络程序通讯的端点。IP地址标识了互联网上的主机,端口号标识了计算机上运行的进程。IP和端口号就可以组成套接字Socket对象。计算机上的端口号是一个16位的整数,范围在0-65535。其中0-1023端口号一般用于系统服务。所以为了避免冲突,一般选用1023以后的端口使用。两个程序的通信其实就是两个Socket对象间的通信,例如你要去存钱,首先得去找银行进入银行营业大厅,进去之后,你要办理什么类型的服务就去找对应的窗口,比如存取钱在一个窗口,办理外汇在另一个窗口。这就是Socket对象包含IP地址和端口的作用。

套接字是一种模式,要建立连接还要有协议。开放系统互联模型(OSI)中,传输层为会话层提供了端到端的数据传输服务,使用的协议有UDP、TCP。它们是比较基本的协议,其它协议例如FTP、HTTP、DNS等是 基于UDP、TCP建立的。

创建基于UDP的连接

首先来创建发送端部分:

// 创建用来发送数据报包的套接字
DatagramSocket client = new DatagramSocket();
// 创建数据报包
byte[] bys = "这里是客户端".getBytes();
DatagramPacket data = new DatagramPacket(bys, bys.length, 
        InetAddress.getLocalHost(), 58888);
//发送数据报包
client.send(data);
// 关闭套接字
client.close();

接收端部分:

// 创建接收端数据报包套接字
DatagramSocket ser = new DatagramSocket(58888);
// 创建数据报包
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
// 接收数据报包
ser.receive(packet);
// 解析数据报包
int len = packet.getLength();
data = packet.getData();
System.out.println(new String(data, 0, len));
// 关闭套接字
ser.close();

由以上例子中我们来总结创建一个UDP连接的基本步骤:
A:创建发送端/接收端套接字
B:创建数据报包
C:发送/接收解析数据报包
D:关闭Socket

UDP协议的特点:
UDP是面向无线连接的协议,发送端和接收端只负责数据报包的发送和接收,数据报包内部包含传送地址,数据发送后可能会通过不同的路由到达地址。
UDP协议不能对数据的发送进行任何的保证,适合传输可靠性要求不高的情况比如QQ、短信。

API中的方法:
构造方法:
创建空的套接字
——–DatagramSocket()

创建数据报套接字,将其绑定到指定的本地地址
——–DatagramSocket(int port, InetAddress laddr)

发送接收方法:
从此套接字接收数据报包
——–void receive(DatagramPacket p)

从此套接字发送数据报包
——– void send(DatagramPacket p)


创建基于TCP的连接

发送端部分:

// 创建用来发送数据报包的套接字
Socket client = new Socket(InetAddress.getLocalHost(), 58888);
// 获取TCP通道输出流
PrintWriter pw = new PrintWriter(client.getOutputStream(), true);
pw.println("这里是客户端");
// 关闭套接字
client.close();

接收端部分:

// 创建接收端数据报包套接字
ServerSocket ser=new ServerSocket(58888);
// 监听Socket
Socket get=ser.accept();
// 获取TCP通道输入流
BufferedReader br=new BufferedReader(new InputStreamReader(get.getInputStream()));
// 读取数据
String line=br.readLine();
System.out.println(line);
// 关闭套接字
ser.close();

由以上例子中我们来总结创建一个TCP连接的基本步骤:
A:创建发送/接收端套接字
B:建立连接
C:获取输出/输入流
D:传输数据
E:关闭套接字

API中的方法:
发送端套接字的方法:
创建一个流套接字并将其连接到指定 IP 地址的指定端口号
——–Socket(InetAddress address, int port)
创建一个流套接字并将其连接到指定主机上的指定端口号。
——–Socket(String host, int port)
返回此套接字的输入流。
——–InputStream getInputStream()
返回此套接字的输出流
——–OutputStream getOutputStream()
此套接字的输入流置于“流的末尾”
——–void shutdownInput()
禁用此套接字的输出流。
——–void shutdownOutput()

接收端套接字的方法:
创建绑定到特定端口的服务器套接字
——–ServerSocket(int port)
利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
——–ServerSocket(int port, int backlog)
将 ServerSocket 绑定到特定地址(IP 地址和端口号)
——–void bind(SocketAddress endpoint)
侦听并接受到此套接字的连接,阻塞式
——–Socket accept()
通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位
——–void setSoTimeout(int timeout)

TCP协议的特点:
TCP是面向连接的协议,发送端和接收端必须建立连接通道后才能进行数据的传送,因此TCP的建立开销大,同时由于建立了通道,所以数据的传送量大;TCP协议建立了完善的机制来确保数据可靠的传送。因此TCP协议适合电子邮件、HTTP等。

示例:
利用上面学到的知识,制作一个多客户端上传文件到同一个服务器端的简单程序。本例中使用了TCP协议来传送数据。
服务端程序:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/*
 * 
 *服务器接收端,可以同时接收多个客户端的文件的上传,并按照上传文件的文件名保存到指定的文件夹下。
 * 
 * */
public class Server {
    // 服务器TCP协议的套接字
    final ServerSocket server;
    // 接收客户端文件的文件夹
    final File receiveFolder;

    public Server() throws IOException {
        // 创建一个端口为58888服务器套接字
        this.server = new ServerSocket(58888);
        // 初始化receiveFolder,客户端上传的文件将保存在该文件夹内
        receiveFolder = new File("D:\\receiveFolder");
        // 监测该文件夹是否被创建,否则新建
        if (!receiveFolder.exists())
            receiveFolder.mkdirs();
        // 监测来自客户端的请求
        while (true) {
            // 使用accept方法监测套接字请求,在没有请求时,该方法将一直阻塞
            Socket client = server.accept();
            // 启动客户端处理线程
            new Thread(new ClientThread(client)).start();
        }
    }

    // 该线程类用于处理客户端的请求
    private class ClientThread implements Runnable {
        // 客户端的套接字
        final Socket client;
        // 客户端的地址
        final InetAddress clientIP;
        // TCP通道输入流
        final BufferedInputStream bis;
        // TCP通道输出流
        final BufferedOutputStream bos;

        // final String FILENAME = "*._.*";

        ClientThread(final Socket client) throws IOException {
            this.client = client;
            // 初始化客户端的IP地址
            this.clientIP = client.getInetAddress();
            // 使用Socket.getInputStream()获取通道输入流
            this.bis = new BufferedInputStream(client.getInputStream());
            // 使用Socket.getOutputStream()获取输出流
            this.bos = new BufferedOutputStream(client.getOutputStream());
        }

        @Override
        public void run() {
            // 上传的文件
            File receive = null;
            byte[] bys = new byte[1024];
            int length = 0;

            // 接收文件名,并创建receive
            try {
                // 接收文件名
                String fileName = null;
                // 读取通道输入流中的数据
                if ((length = bis.read(bys)) != -1) {
                    fileName = new String(bys, 0, length);
                }
                // 发送反馈
                String sign = "文件名获取成功";
                bos.write(sign.getBytes());
                bos.flush();
                // 获取客户端的主机名
                String clientHostName = clientIP.getHostName();
                System.out.println(fileName);
                // 上传的文件将保存到以客户端主机名命名的文件夹下
                File folder = new File(receiveFolder, clientHostName);
                folder.mkdirs();
                // 上传的文件
                receive = new File(folder, fileName);
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 接收文件,写入到硬盘
            // 创建缓冲输出流,接收客户端文件数据并写入到硬盘,使用try-with-sources语句自动关闭输出流
            try (BufferedOutputStream write = new BufferedOutputStream(new FileOutputStream(receive));) {
                while ((length = bis.read(bys)) != -1) {
                    // 写出数据
                    write.write(bys, 0, length);
                    // 刷新
                    write.flush();
                }
            } catch (IOException e) {
                // 处理接收文件部分的异常
                System.out.println("接收文件异常");
            }

            // 文件接收完毕,发送反馈并关闭套接字
            try {
                byte[] sign = "服务器端接收完毕".getBytes();
                bos.write(sign);
                bos.flush();
                // 关闭此客户端套接字,与此关联的通道流(bis和bos)也随之关闭
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) throws IOException {
        // 启动服务器
        new Server();
    }

}

客户端程序:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * TCP协议的客户端,可以上传文件到服务器
 */
public class Client {
    // 客户端套接字
    final Socket client = new Socket(InetAddress.getLocalHost(), 58888);
    // 上传的文件
    File target;
    // TCP通道输入流
    final BufferedInputStream bis;
    // TCP通道输出流
    final BufferedOutputStream bos;

    public Client(final Path path) throws UnknownHostException, IOException {
        // 选择上传的文件
        target = path.toFile();
        // 使用缓冲字节流包装通道输入流
        bis = new BufferedInputStream(client.getInputStream());
        // 使用缓冲字节流包装通道输出流
        bos = new BufferedOutputStream(client.getOutputStream());
    }

    // 上传文件的方法
    public void upLoad() {
        byte[] bys = new byte[1024];
        int len = 0;

        // 上传文件名
        String fileName = target.getName();
        try {
            // 将文件名写入通道输出流
            bos.write(fileName.getBytes());
            // 刷新
            bos.flush();
            // 读取文件名上传成功的反馈
            len = bis.read(bys);
            String sign = new String(bys, 0, len);
            if (sign.equals("文件名获取成功"))
                System.out.println("准备上传文件,请稍等...");
        } catch (IOException i) {
            System.out.println("上传文件名异常");
        }

        // 上传文件
        try (BufferedInputStream read = new BufferedInputStream(new FileInputStream(target))) {
            // 读取文件数据
            while ((len = read.read(bys)) != -1) {
                // 将数据写入通道输出流
                bos.write(bys, 0, len);
                // 刷新
                bos.flush();
            }
            // 关闭客户端套接字的输出流
            client.shutdownOutput();
        } catch (IOException i) {
            System.out.println("上传文件异常");
        }

        // 读取服务器的反馈
        try {
            len = bis.read(bys);
            String sign = new String(bys, 0, len);
            // 打印反馈到控制台
            System.out.println(sign);
            // 关闭套接字
            client.close();
        } catch (IOException i) {
            System.out.println("接收反馈异常");
        }
    }

    public static void main(String[] args) throws IOException, IOException {
        // 测试
        Client test = new Client(Paths.get("D:\\迅雷游戏\\XLGameBox\\Uninstaller.exe"));
        test.upLoad();
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值