Socket

写在前面的话

最近准备深入学习Java,所以又把Socket学了了一遍。一边看着IMOOC的学习视频(Java Socket应用---通信是这样练成的),一边做着笔记,一边敲着代码,很爽。你也可以试一试。

网络基础知识

1、两台计算机间进行通讯需要以下三个条件:

IP地址、协议、端口号

2、TCP/IP协议

是目前世界上应用最为广泛的协议,是以TCP和IP为基础的不同层次上多个协议的集合,也成TCP/IP协议族、或TCP/IP协议栈

TCP:Transmission Control Protocol 传输控制协议

IP:Internet Protocol 互联网协议

3、TCP/IP五层模型

应用层:HTTP、FTP、SMTP、Telnet等

传输层:TCP/IP

网络层:(通信)

数据链路层:(数据字节封装)

物理层:网线、双绞线、网卡等

4、IP地址

为实现网络中不同计算机之间的通信,每台计算机都必须有一个唯一的标识——IP地址。
IPV4:32位二进制

5、端口

区分一台主机的多个不同应用程序,端口号范围为0-65535,其中0-1023位为系统保留。

如:HTTP:80 / FTP:21 / Telnet:23

IP地址+端口号组成了所谓的Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础

6、Socket套接字:

网络上具有唯一标识的IP地址和端口组合在一起才能构成唯一能识别的标识符套接字。

Socket原理机制:

通信的两端都有Socket

网络通信其实就是Socket间的通信

数据在两个Socket间通过IO传输

7、Java中的网络支持

针对网络通信的不同层次,Java提供了不同的API,其提供的网络功能有四大类:

InetAddress:用于标识网络上的硬件资源,主要是IP地址

URL:统一资源定位符,通过URL可以直接读取或写入网络上的数据

Sockets:使用TCP协议实现的网络通信Socket相关的类

Datagram:使用UDP协议,将数据保存在用户数据报中,通过网络进行通信。

二、InetAddress

InetAddress类用于标识网络上的硬件资源,标识互联网协议(IP)地址。

try {
    InetAddress inetAddress = InetAddress.getLocalHost();
    System.out.println("计算机名:" + inetAddress.getHostName());
    System.out.println("IP:" + inetAddress.getHostAddress());
    byte[] bytes = inetAddress.getAddress(); // 获取字节数组形式的IP地址
    System.out.println("字节数组形式的IP:" + Arrays.toString(bytes));
    System.out.println(inetAddress); // 直接输出InetAddress对象

    System.out.println("---------------------------");

    // 根据主机名获取InetAddress实例
    // InetAddress addressByName = InetAddress.getByName("byxy");
    // InetAddress addressByName = InetAddress.getByName("192.168.0.105");
    byte[] ip = {-64, -88, 0, -56};
    InetAddress addressByName = InetAddress.getByAddress(ip);
    System.out.println("计算机名:" + addressByName.getHostName());
    System.out.println("IP:" + addressByName.getHostAddress());


} catch (UnknownHostException e) {
    e.printStackTrace();
}

URL类

1、URL API

String baseUrl = "https://fengwenyi.com";
try {
    URL fengwenyi = new URL(baseUrl);
    URL url = new URL(fengwenyi, "/article.html?id=18");
    System.out.println("协议:" + url.getProtocol());
    System.out.println("主机:" + url.getHost());
    System.out.println("端口:" + url.getPort()); // -1
    /*
    public int getPort()获取此 URL的端口号。
    结果
    端口号,如果端口未设置,则为-1
    */
    System.out.println("文件路径:" + url.getPath());
    System.out.println("文件名:" + url.getFile());
    System.out.println("相对路径:" + url.getRef());
    System.out.println("查询字符串:" + url.getQuery());
} catch (MalformedURLException e) {
    e.printStackTrace();
}
url.png

2、使用URL读取网页内容

通过URL对象的openStream()方法可以得到指定资源的输入流,通过流能够读取或访问网页上的资源

// 使用url 读取网页内容
String baseUrl = "https://fengwenyi.com";
try {
    // url
    URL url = new URL(baseUrl);
    // 字节输入流
    InputStream inputStream = url.openStream();
    // 转为字符输入流
    // InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
    // 缓冲
    BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    // 读取
    String data = bufferedReader.readLine();
    while (data != null) {
        System.out.println(data); // 当前行
        data = bufferedReader.readLine(); // 一行
    }
    // 关闭资源
    bufferedReader.close();
    inputStreamReader.close();
    inputStream.close();
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
url-读取网页内容.png

四、TCP编程

1、TCP协议是面向连接的、可靠的、有序的、以字节流的方式发送数据,通过三次握手方式建立连接,形成传输数据的通道,在连接中进行大量数据的传输,效率会稍低

2、Java中基于TCP协议实现网络通信的类

客户端的Socket类

服务器端的ServerSocket类

image

3、Socket通信的步骤

① 创建ServerSocket和Socket

② 打开连接到Socket的输入/输出流

③ 按照协议对Socket进行读/写操作

④ 关闭输入输出流、关闭Socket

4、服务器端:

① 创建ServerSocket对象,绑定监听端口

② 通过accept()方法监听客户端请求

③ 连接建立后,通过输入流读取客户端发送的请求信息

④ 通过输出流向客户端发送乡音信息

⑤ 关闭相关资源

try {
    // 创建服务端socket
    ServerSocket serverSocket = new ServerSocket(9001);
    // 监听
    System.out.println("******服务端即将启动*********");
    Socket socket = serverSocket.accept();
    // 获取数据
    InputStream inputStream = socket.getInputStream(); // 字节输入流
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream); // 字符流
    BufferedReader bufferedReader = new BufferedReader(inputStreamReader); // 缓冲
    String info = null;
    while ((info = bufferedReader.readLine()) != null) {
        System.out.println("我是服务器,客户端说:" + info);
    }

    socket.shutdownInput();

    // 给客户端响应
    OutputStream outputStream = socket.getOutputStream();
    PrintWriter printWriter = new PrintWriter(outputStream);
    printWriter.write("登录成功,欢迎你");
    printWriter.flush();

    socket.shutdownOutput();

    // 关闭
    printWriter.close();
    outputStream.close();
    bufferedReader.close();
    inputStreamReader.close();
    inputStream.close();
    serverSocket.close();
} catch (IOException e) {
    e.printStackTrace();
}

5、客户端:

① 创建Socket对象,指明需要连接的服务器的地址和端口号

② 连接建立后,通过输出流想服务器端发送请求信息

③ 通过输入流获取服务器响应的信息

④ 关闭响应资源

try {
    // 客户端socket
    Socket socket = new Socket("127.0.0.1", 9001);
    // 发送信息
    OutputStream outputStream = socket.getOutputStream(); // 字节输出流
    PrintWriter printWriter = new PrintWriter(outputStream); // 打印流
    printWriter.write("用户名:admin;密码:admin");
    printWriter.flush();

    socket.shutdownOutput();

    InputStream inputStream = socket.getInputStream();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
    String data = null;
    while ((data = bufferedReader.readLine()) != null) {
        System.out.println("我是客户端,服务器说:" + data);
    }

    socket.shutdownInput();

    // 关闭资源
    bufferedReader.close();
    inputStream.close();
    printWriter.close();
    outputStream.close();
    socket.close();
} catch (IOException e) {
    e.printStackTrace();
}

测试:

服务器

tcp-1-server.png

客户端

tcp-2client.png

6、应用多线程实现服务器与多客户端之间的通信

① 服务器端创建ServerSocket,循环调用accept()等待客户端连接

② 客户端创建一个socket并请求和服务器端连接

③ 服务器端接受苦读段请求,创建socket与该客户建立专线连接

④ 建立连接的两个socket在一个单独的线程上对话

⑤ 服务器端继续等待新的连接

ServerThread

package com.fengwenyi.demo.socket.tcp.thread.server;

import java.io.*;
import java.net.Socket;

/**
 * @author Wenyi Feng
 */
public class ServerThread extends  Thread {

    // socket
    Socket socket = null;

    public ServerThread (Socket socket) {
        this.socket = socket;
    }

    // 操作
    @Override
    public void run() {
        super.run();

        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        OutputStream outputStream = null;
        PrintWriter printWriter = null;

        try {
            inputStream = socket.getInputStream(); // 字节输入流
            inputStreamReader = new InputStreamReader(inputStream); // 字符流
            bufferedReader = new BufferedReader(inputStreamReader); // 缓冲
            String info = null;
            while ((info = bufferedReader.readLine()) != null) {
                System.out.println("我是服务器,客户端说:" + info);
            }

            socket.shutdownInput();

            // 给客户端响应
            outputStream = socket.getOutputStream();
            printWriter = new PrintWriter(outputStream);
            printWriter.write("登录成功,欢迎你");
            printWriter.flush();

            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭
            try {
                if (printWriter != null)
                    printWriter.close();
                if (outputStream != null)
                    outputStream.close();
                if (bufferedReader != null)
                    bufferedReader.close();
                if (inputStreamReader != null)
                    inputStreamReader.close();
                if (inputStream != null)
                    inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

Server

final AtomicInteger count = new AtomicInteger(0);
try {
    // 创建服务端socket
    ServerSocket serverSocket = new ServerSocket(9001);
    // 监听
    System.out.println("******服务端即将启动*********");
    Socket socket = null;
    while (true) {
        socket = serverSocket.accept();
        ServerThread serverThread = new ServerThread(socket);
        serverThread.start();
        count.getAndIncrement();
        System.out.println("客户端数量:" + count);
        InetAddress inetAddress = socket.getInetAddress();
        System.out.println("当前客户端IP:" + inetAddress.getHostAddress());
    }
} catch (IOException e) {
    e.printStackTrace();
}

Client

try {
    // 客户端socket
    Socket socket = new Socket("127.0.0.1", 9001);
    // 发送信息
    OutputStream outputStream = socket.getOutputStream(); // 字节输出流
    PrintWriter printWriter = new PrintWriter(outputStream); // 打印流
    printWriter.write("用户名:admin;密码:admin");
    printWriter.flush();

    socket.shutdownOutput();

    InputStream inputStream = socket.getInputStream();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
    String data = null;
    while ((data = bufferedReader.readLine()) != null) {
        System.out.println("我是客户端,服务器说:" + data);
    }

    socket.shutdownInput();

    // 关闭资源
    bufferedReader.close();
    inputStream.close();
    printWriter.close();
    outputStream.close();
    socket.close();
} catch (IOException e) {
    e.printStackTrace();
}

测试:

tcp-3-thread.png

UDP编程

UDP协议(用户数据报协议)是无连接的、不可靠的、无序的,速度快

进行数据传输时,首先将要传输的数据定义成数据报(Datagram),大小限制在64k,在数据报中指明数据索要达到的Socket(主机地址和端口号),然后再将数据报发送出去

DatagramPacket类:表示数据报包

DatagramSocket类:进行端到端通信的类

1、服务器端实现步骤

① 创建DatagramSocket,指定端口号

② 创建DatagramPacket

③ 接受客户端发送的数据信息

④ 读取数据

try {
    /**
     * 接收数据
     */
    // 1、创建服务器端DatagramSocket,指定端口
    DatagramSocket datagramSocket = new DatagramSocket(9002);
    // 2、创建数据报,用于接收客户端发送的数据
    byte[] data = new byte[1024]; // 创建字节数据,指定接收数据报的大小
    DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
    // 3、接收客户端发送的数据
    System.out.println("服务器端已经启动,等待客户端发送数据");
    datagramSocket.receive(datagramPacket); // 此方法在接收到数据报之前会一直阻塞
    // 4、读取数据
    String info = new String(data, 0, datagramPacket.getLength());
    System.out.println("我是服务器,客户端说:" + info);

    /**
     * 响应
     */
    // 1、定义客户端的地址、端口号、数据
    InetAddress inetAddress = datagramPacket.getAddress();
    int port = datagramPacket.getPort();
    byte[] data2 = "登录成功,欢迎你".getBytes();
    // 2、创建数据报
    DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length, inetAddress, port);
    // 3、响应客户端
    datagramSocket.send(datagramPacket2);
    // 4、关闭资源
    datagramSocket.close();

} catch (SocketException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

2、客户端实现步骤

① 定义发送信息

② 创建DatagramPacket,包含将要发送的信息

③ 创建DatagramSocket

④ 发送数据

try {
    /**
     * 发送数据
     */
    // 1、定义服务器的地址、端口号、数据
    InetAddress inetAddress = InetAddress.getByName("localhost");
    int port = 9002;
    byte[] data = "用户名:admin;密码:123".getBytes();
    // 2、创建数据报,包含发送的数据信息
    DatagramPacket datagramPacket = new DatagramPacket(data, data.length, inetAddress, port);
    // 3、创建DatagramSocket对象
    DatagramSocket datagramSocket = new DatagramSocket();
    // 4、想服务器端发送数据报
    datagramSocket.send(datagramPacket);

    /**
     * 接收数据
     */
    // 1、接收响应的信息
    byte[] data2 = new byte[1024];
    DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length);
    // 2、接收服务器端数据
    datagramSocket.receive(datagramPacket2);
    // 3、读取数据
    String reply = new String(data2, 0, datagramPacket2.getLength());
    System.out.println("我是客户端,服务器端说:" + reply);
    // 4、关闭资源
    datagramSocket.close();
} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (SocketException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

测试:

udp-server.png
udp-client.png
udp-server-data.png

注意问题:

1、多线程的优先级问题:

根据实际的经验,适当的降低优先级,否侧可能会有程序运行效率低的情况

2、是否关闭输出流和输入流:

对于同一个socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可

3、使用TCP通信传输对象,IO中序列化部分

4、socket编程传递文件,IO流部分

资料

1、代码:socket-demo

2、视频资料:Java Socket应用---通信是这样练成的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容