【Java】网络编程(Socket)

网络编程

Socket

我们开发的网络应用程序位于应用层,TCP和UDP属于传输层协议,在应用层如何使用传输层的服务呢?在应用层和传输层之间,则使用套接字Socket来进行分离

套接字就像是传输层为应用层开的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,是不知道也不需要知道的,也不会关心它如何传输,这属于网络其他层次工作

Socket实际是传输层供给应用层的编程接口。Socket就是应用层与传输层之间的桥梁。使用Socket变成可以开发客户机和服务器的应用程序,可以在本地网络上通信,也可以通过Internet在全球范围内通信

Java网络编程中的常用类

Java为了跨平台,在网络应用通信时是不允许直接调用操作系统接口的,而是由java.net包来提供网络功能。

InetAddress的使用

作用:封装计算机的IP地址和DNS(没有端口信息)

特点:

这个类没有构造方法,如果想要得到对象,只能通过静态方法:getLocalHost() 、 getByName() 、 getALLByName() 、 getAddress() 、 getHostName()

获取本机信息

获取本机信息需要使用getLocalHost()方法创建InetAddress对象,这个对象包含了本机的IP地址,计算机名等信息

java 复制代码
public class InetTest {
    public static void main(String[] args) {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            String hostAddress = localHost.getHostAddress();
            String hostName = localHost.getHostName();
            System.out.println(hostAddress);
            System.out.println(hostName);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
}

根据域名获取计算机的信息

根据域名获取计算机信息时需要使用getByName("域名")方法创建InetAddress对象

java 复制代码
public class InetTest2 {
    public static void main(String[] args) {
        try {
            InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
            System.out.println(inetAddress.getHostAddress());
            System.out.println(inetAddress.getHostName());

        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
}

根据IP地址获取计算机的信息

根据IP地址获取计算机的信息时需要使用getByName("IP")方法创建InetAddress对象

java 复制代码
public class InetTest3 {
    public static void main(String[] args) {
        try {
            InetAddress inetAddress = InetAddress.getByName("110.242.68.4");
            System.out.println(inetAddress.getHostName());
            System.out.println(inetAddress.getHostAddress());
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
}
InetSocketAddress的使用

作用:包含IP和端口信息,常用于Socket通信。此类实现IP套接字地址(IP地址+端口号),不依赖任何协议

InetSocketAddress相比较InetAddress多了一个端口号,端口的作用:一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现

java 复制代码
public class InetSocketTest {
    public static void main(String[] args) {
        InetSocketAddress inetSocketAddress = new InetSocketAddress("www.baidu.com",80);
        System.out.println(inetSocketAddress.getAddress().getHostAddress());
        System.out.println(inetSocketAddress.getHostName());
    }
}
URL的使用

IP地址标识了Internet上唯一的计算机,而URL则标识了这些计算机上的资源。URL代表一个资源定位符,它是指向互联网"资源"的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或者搜索引擎的查询

为了方便程序员编程,JDK中提供了URL类,该类的全名是java.net.URL,有了这样一个类,就可以使用它的各种方法来对URL对象进行分割、合并等处理

java 复制代码
public class UrlTest {
    public static void main(String[] args) {
        try {
            URL url = new URL("http://www.edu2act.cn/task/list/finished/");
            System.out.println("获取当前协议的默认端口:" + url.getDefaultPort());
            System.out.println("访问资源:" + url.getFile());
            System.out.println("主机名" + url.getHost());
            System.out.println("访问资源的路径:" + url.getPath());
            System.out.println("协议" + url.getProtocol());
            System.out.println("参数部分" + url.getQuery());
        } catch (MalformedURLException e) {
            throw new RuntimeException(e);
        }
    }
}

通过URL实现最简单的网络爬虫

java 复制代码
public class UrlTest2{
    public static void main(String[] args)throws Exception {
        URL url = new URL("http://dbms.wangding.co/design/ch04-database-design-1/");
        try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
            StringBuilder sb = new StringBuilder();
            String temp;
            while ((temp = br.readLine()) != null) {
                sb.append(temp);
            }
            System.out.println(sb);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
TCP通信的实现和项目案例
TCP通信实现原理

前边我们提到TCP协议是面向的连接的,在通信时客户端与服务器端必须建立连接。在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序,简称客户端,而在第一次通讯中等待连接的程序被称作服务器端(Server)程序,简称服务器。一旦通讯建立,则客户端和服务器端完全一样,没有本质的区别。

请求-相应 模式

  • Socket类:发送TCP信息
  • ServerSocket类:创建服务器

套接字Socket是一种进程间的数据交换机制。这些进程既可以在同一机器上,也可以在通过网络连接的不同机器上。换句话说,套接字起到通信端点的作用。单个套接字是一个端点,而一对套接字则构成一个双向通信信道,使非关联进程可以在本地或通过网络进行数据交换。一旦建立套接字连接,数据即可在相同或不同的系统中双向或单向发送,直到其中一个端点关闭连接。套接字与主机地址和端口地址相关联。主机地址就是客户端或服务器程序所在的主机的IP地址。端口地址是指客户端或服务器程序使用的主机的通信端口。

在客户端和服务器中,分别创建独立的Socket,并通过Socket的属性,将两个Socket进行连接,这样,客户端和服务器通过套接字所建立的连接使用输入输出流进行通信。

TCP/IP套接字是最可靠的双向流协议,使用TCP/IP可以发送任意数量的数据。

实际上,套接字只是计算机上已编号的端口。如果发送方和接收方计算机确定好端口,他们就可以通信了。

客户端与服务器端的通信关系图:

TCP/IP通信连接的简单过程

位于A计算机上的TCP/IP软件向B计算机发送包含端口号的消息,B计算机的TCP/IP软件接收该消息,并进行检查,查看是否有它知道的程序正在该端口上接收消息。如果有,他就将该消息交给这个程序。

通过Socket的编程顺序

1、创建服务器ServerSocket,在创建时,定义ServerSocket的监听端口(在这个端口接收客户端发来的消息)

2、ServerSocket调用accept()方法,使之处于阻塞状态。

3、创建客户端Socket,并设置服务器的IP及端口。

4、客户端发出连接请求,建立连接。

5、分别取得服务器和客户端Socket的InputStream和OutputStream。

6、利用Socket和ServerSocket进行数据传输。

7、 关闭流及Socket。

TCP通信入门案例

创建服务端

java 复制代码
public class BasicSocketServer {
    public static void main(String[] args) throws IOException {
        System.out.println("服务器已启动,等待监听....");
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8888);
            Socket socket = serverSocket.accept();
            //连接成功后会得到与客户端对应的Socket对象,并解除线程阻塞
            InputStream in = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            System.out.println(br.readLine());
            OutputStream out = socket.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            serverSocket.close();
        }
    }
}

创建客户端

java 复制代码
public class BasicSocketClient {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        PrintWriter pw = null;
        try {
            socket = new Socket("127.0.0.1",8888);
            OutputStream out = socket.getOutputStream();
            pw = new PrintWriter(out);
            pw.write("服务端,你好!");
            pw.flush();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            pw.close();
            socket.close();
        }
    }
}
TCP单向通信

单向通信是指通信双方中,一方固定为发送端,一方固定为接收端

创建服务端

java 复制代码
public class OneWaySocketServer {
    public static void main(String[] args) {
        System.out.println("服务器启动,开始监听");
        try(ServerSocket serverSocket = new ServerSocket(8888);) {
            Socket socket = serverSocket.accept();
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            System.out.println("连接成功");
            while (true){
                String str = br.readLine();
                System.out.println("客户端说" + str);
                if ("exit".equals(str)){
                    break;
                }
                pw.println(str);
                pw.flush();
            }
        } catch (IOException e) {
            System.out.println("服务器启动失败");
            throw new RuntimeException(e);
        }
    }
}

创建客户端

java 复制代码
public class OneWaySocketClient {
    public static void main(String[] args) {
        try(Socket socket = new Socket("127.0.0.1",8888)) {
            Scanner scanner = new Scanner(System.in);
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while (true){
                String s = scanner.nextLine();
                pw.println(s);
                pw.flush();
                if ("exit".equals(s)){
                    break;
                }
                String serverInput = br.readLine();
                System.out.println("服务器返回的" + serverInput);

            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
TCP双向通信

双向通信是指通信双方中,任何一方都可为发送端,任何一方都可为接收端

服务端

java 复制代码
public class TwoWaySocketServer {
    public static void main(String[] args) {
        System.out.println("服务器启动,监听8888端口");
        try(ServerSocket serverSocket = new ServerSocket(8888);) {
            Socket socket = serverSocket.accept();
            //创建键盘输入对象
            Scanner scanner = new Scanner(System.in);
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            while (true){
                String str = br.readLine();
                System.out.println("客户端说:" + str);
                String keyInput = scanner.nextLine();
                pw.println(keyInput);
                pw.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

客户端

java 复制代码
public class TwoWaySocketClient {
    public static void main(String[] args) {
        try(Socket socket = new Socket("127.0.0.1",8888)) {
            Scanner scanner = new Scanner(System.in);
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            while (true){
                String keyInput = scanner.nextLine();
                pw.println(keyInput);
                pw.flush();
                String str = br.readLine();
                System.out.println("服务端说:" + str);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
创建点对点的聊天应用

创建服务端

主线程

java 复制代码
public class ChatSocketServer {
    public static void main(String[] args) {
        try(ServerSocket serverSocket = new ServerSocket(8888)) {
            System.out.println("服务端启动,等待连接");
            Socket socket = serverSocket.accept();
            new Thread(new SendThread(socket)).start();
            new Thread(new ReceiveThread(socket)).start();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

接收消息线程

java 复制代码
public class ReceiveThread implements Runnable{
    private Socket socket;
    public ReceiveThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        this.receiveMsg();
    }
    private void receiveMsg(){
        try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){
            while (true){
                String msg = br.readLine();
                System.out.println("客户端说:" + msg);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

发送消息线程

java 复制代码
public class SendThread implements Runnable{
    private Socket socket;
    public SendThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        this.sendMsg();
    }
    private void sendMsg(){
        try(Scanner scanner = new Scanner(System.in);
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
        ){
            while (true){
                String msg = scanner.nextLine();
                pw.println(msg);
                pw.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

创建客户端

主线程

java 复制代码
public class ChatSocketClient {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("127.0.0.1",8888);
            System.out.println("连接成功");
            new Thread(new ClientSendThread(socket)).start();
            new Thread(new ClientReceiveThread(socket)).start();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

接收消息线程被

java 复制代码
public class ClientReceiveThread implements Runnable{
    private Socket socket;
    public ClientReceiveThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        receiveMsg();
    }

    private void receiveMsg() {
        try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            while (true){
                String msg = br.readLine();
                System.out.println("服务端说:" + msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

发送消息线程

java 复制代码
public class ClientSendThread implements Runnable{
    private Socket socket;
    public ClientSendThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        this.sendMsg();
    }

    private void sendMsg() {
        try(Scanner scanner = new Scanner(System.in);
            PrintWriter pw = new PrintWriter(socket.getOutputStream())){
            while (true){
                String msg = scanner.nextLine();
                pw.println(msg);
                pw.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
优化点对点的聊天应用
java 复制代码
public class GoodTCP {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入:server,<port> 获取 <ip>,<port>");
            String str = scanner.nextLine();
            String[] arr = str.split(",");
            if("server".equals(arr[0])){
                System.out.println("TCP Server Listen at" + arr[1] + "......");
                serverSocket = new ServerSocket(Integer.parseInt(arr[1]));
                socket = serverSocket.accept();
                new Receive(socket);
            }else {
                socket = new Socket(arr[0],Integer.parseInt(arr[1]));
                System.out.println("连接成功");
            }
            new Thread(new Send(socket, scanner)).start();
            new Thread(new Receive(socket)).start();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(serverSocket != null){
                serverSocket.close();
            }
        }
    }
}
java 复制代码
public class Send implements Runnable{
    private Socket socket;
    private Scanner scanner;

    public Send(Socket socket, Scanner scanner) {
        this.socket = socket;
        this.scanner = scanner;
    }

    @Override
    public void run() {
        this.sendMsg();
    }

    private void sendMsg(){
        try(PrintWriter pw = new PrintWriter(socket.getOutputStream());
        ){
            while (true){
                String msg = scanner.nextLine();
                pw.println(msg);
                pw.flush();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
java 复制代码
public class Receive implements Runnable{
    private Socket socket;
    public Receive(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        sendMsg();
    }
    private void sendMsg(){
        try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){
            while (true){
                String msg = br.readLine();
                System.out.println("客户端说:" + msg);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
一对多应用

服务端应该将serverSocket.accept()放入while(true)循环中

一对多聊天服务器设计

难点在于解决线程同步

当没有消息发送时,发送线程处于等待状态,当接收线程接收到消息后,唤醒所有等待的发送线程

java 复制代码
public class ChatRoomServer {
    public static String buf;
    public static void main(String[] args) {
        System.out.println("Chat Server Version 1.0");
        System.out.println("Listen at 8888......");
        try(ServerSocket serverSocket = new ServerSocket(8888)){
            while (true){
                Socket socket = serverSocket.accept();
                System.out.println("连接到" + socket.getInetAddress());
                new Thread(new ChatReceiveThread(socket)).start();
                new Thread(new ChatSendThread(socket)).start();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
java 复制代码
public class ChatReceiveThread implements Runnable {
    private Socket socket;

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

    @Override
    public void run() {
        receiveMsg();
    }

    private void receiveMsg() {
        try(BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));){
            while (true){
                String msg = br.readLine();
                synchronized ("abc"){
                    ChatRoomServer.buf = "[" + this.socket.getInetAddress() + "]" + msg;
                    "abc".notifyAll();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
java 复制代码
public class ChatSendThread implements Runnable{
    Socket socket;

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

    @Override
    public void run() {
        sendMsg();
    }

    private void sendMsg() {
        try(PrintWriter pw = new PrintWriter(socket.getOutputStream());){
            while (true){
                synchronized ("abc"){
                    //先让发送消息的线程处于等待状态
                    "abc".wait();
                    //将公共数据区的数据发送给客户端
                    pw.println(ChatRoomServer.buf);
                    pw.flush();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
UDP通信实现原理

UDP协议与之前讲到的TCP协议不同,是面向无连接的,双方不需要建立连接便可通信。UDP通信所发送的数据需要进行封包操作(使用DatagramPacket类),然后才能接收或发送(使用DatagramSocket类)。

DatagramPacket:数据容器(封包)的作用

此类表示数据报包。 数据报包用来实现封包的功能。

常用方法

方法名 使用说明
DatagramPacket(byte[] buf, int length) 构造数据报包,用来接收长度为 length 的数据包
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号
getAddress() 获取发送或接收方计算机的IP地址,此数据报将要发往该机器或者是从该机器接收到的
getData() 获取发送或接收的数据
setData(byte[] buf) 设置发送的数据

DatagramSocket:用于发送或接收数据报包

当服务器要向客户端发送数据时,需要在服务器端产生一个DatagramSocket对象,在客户端产生一个DatagramSocket对象。服务器端的DatagramSocket将DatagramPacket发送到网络上,然后被客户端的DatagramSocket接收。

DatagramSocket有两种常用的构造函数。一种是无需任何参数的,常用于客户端;另一种需要指定端口,常用于服务器端。如下所示:

  • DatagramSocket() :构造数据报套接字并将其绑定到本地主机上任何可用的端口。
  • DatagramSocket(int port) :创建数据报套接字并将其绑定到本地主机上的指定端口。

常用方法

方法名 使用说明
send(DatagramPacket p) 从此套接字发送数据报包
receive(DatagramPacket p) 从此套接字接收数据报包
close() 关闭此数据报套接字

UDP通信编程基本步骤:

1、创建客户端的DatagramSocket,创建时,定义客户端的监听端口。

2、创建服务器端的DatagramSocket,创建时,定义服务器端的监听端口。

3、在服务器端定义DatagramPacket对象,封装待发送的数据包。

4、客户端将数据报包发送出去。

5、服务器端接收数据报包。

UDP通信入门案例

服务端

java 复制代码
public class UDPServer {
    public static void main(String[] args) {
        //创建服务端接收数据的DatagramSocket对象
        try(DatagramSocket datagramSocket = new DatagramSocket(9999)){
            //创建数据缓冲区
            byte[] b = new byte[1024];
            //创建数据报包对象
            DatagramPacket datagramPacket = new DatagramPacket(b,b.length);
            //等待接收客户端所发送的数据
            datagramSocket.receive(datagramPacket);
            //取出数据
            String str = new String(datagramPacket.getData(),0,datagramPacket.getLength());
            System.out.println(str);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

客户端

java 复制代码
public class UDPClient {
    public static void main(String[] args) {
        try(DatagramSocket datagramSocket = new DatagramSocket(8888);) {
            byte[] bytes = "Triticale".getBytes();
            DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,new InetSocketAddress("127.0.0.1",9999));
            datagramSocket.send(datagramPacket);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
基本数据类型通信

服务端

java 复制代码
try(DataInputStream dis = new DataInputStream(new ByteArrayInputStream(datagramPacket.getData()));){
	System.out.println(dis.readLong());
}

客户端

java 复制代码
long n = 2000l;
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
	DataOutputStream dos = new DataOutputStream(bos);){
	dos.writeLong(n);
	byte[] arr = bos.toByteArray();
	DatagramPacket datagramPacket1 = new DatagramPacket(arr,arr.length,new InetSocketAddress("127.0.0.1",9999));
}
传递自定义数据类型

创建服务端

java 复制代码
public class ObjectTypeUDPServer {
    public static void main(String[] args) {
        try(DatagramSocket datagramSocket = new DatagramSocket(9999)){
            byte[] bytes = new byte[1024];
            DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
            datagramSocket.receive(datagramPacket);
            try(ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(datagramPacket.getData()))){
                Person person = (Person) ois.readObject();
                System.out.println(person.toString());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

创建客户端

java 复制代码
public class ObjectTypeUDPClient {
    public static void main(String[] args) {
        try(DatagramSocket datagramSocket = new DatagramSocket(8888);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos)){
            Person person = new Person();
            person.setName("张三");
            person.setAge(18);
            oos.writeObject(person);
            byte[] byteArray = bos.toByteArray();
            DatagramPacket datagramPacket = new DatagramPacket(byteArray,byteArray.length,new InetSocketAddress("127.0.0.1",9999));
            datagramSocket.send(datagramPacket);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
相关推荐
四谎真好看1 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程1 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t1 小时前
ZIP工具类
java·zip
lang201509282 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
报错小能手3 小时前
linux学习笔记(43)网络编程——HTTPS (补充)
linux·网络·学习
pengzhuofan3 小时前
第10章 Maven
java·maven
百锦再3 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说3 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端
壹佰大多3 小时前
【spring如何扫描一个路径下被注解修饰的类】
java·后端·spring
A Runner for leave3 小时前
网络与通信安全课程复习汇总3——身份认证
网络·密码学