一.Socket概述

Java网络编程主要涉及的内容是Socket编程。

Socket(套接字),是网络上两个程序之间实现数据交换的一端,它既可以发送请求,也可以接受请求,一个Socket由一个IP地址和一个端口号唯一确定,利用Socket能比较方便的实现两端(服务端和客户端)的网络通信。

在Java中,有专门的Socket类来处理用户请求和响应,学习使用Socket类方法,就可以实现两台机器之间通信。

Socket通信是有两种方式的:TCP和UDP。

TCP通信:客户端提供了java.net.Socket类,服务器端提供了java.net.ServerSocket类。

UDP通信:UDP通信不建立逻辑连接,使用DatagramPacket类打包数据包,使用DatagramSocket类发送数据包。

Socket通信模型如下图:

二. TCP通信客户端Socket

Java中专门用来实现Socket客户端的类就叫Socket,这个类实现了客户端套接字,用于向服务器发出连接请求等。

  • 构造方法:
    Socket(String host, int port):创建一个流套接字并将其连接到指定IP地址的指定端口号。
    如果host为null,则相当于指定地址为回送地址。

回送地址(来自百度百科):

127.x.x.x是本机的会送地址,即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回之,不进行任何网络传输。

  • 主要方法:
  • InputStream getInputStream():返回此套接字的输入流。
    关闭生成的InputStream也将关闭相关的Socket。
  • OutputStream getOutputStream():返回此套接字的输出流。
    关闭生成的OutputStream也将关闭相关的Socket。
  • void close():关闭此套接字

三. TCP通信服务器端ServerSocket

Java中专门用来建立Socket服务器的类叫ServerSocket,这个类实现了服务器套接字,该对象等待通过网络的请求。

  • 构造方法:
    ServerSocket(int port):创建绑定到特定端口的服务器套接字。
  • 主要方法:
  • Socket accept():监听并接受连接,返回一个新的Socket对象,用于和客户端通信,该方法会一直阻塞直到建立连接。
  • void close():关闭此套接字。

四.基于TCP的Socket通信

  1. 步骤分析:
  • 服务端先启动,创建ServerSocket对象,等待连接。
  • 客户端启动,创建Socket对象,请求连接。
  • 服务器端接收请求,调用accept方法,并返回一个Socket对象。
  • 客户端的Socket对象获取OutputStream,向服务器端发送数据。
  • 服务器端Socket对象获取InputStream,读取客户端的数据。
  • 服务器端Socket对象获取OutputStream,向客户端发送数据。
  • 客户端的Socket对象获取InputStream,读取服务器的数据。
  • 客户端释放资源,断开连接。
  1. 主要代码
  • 服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;


public class TCPServer {
    public static void main(String[] args) throws IOException {
        System.out.println("Server start...");
        ServerSocket server = new ServerSocket(1234);
        
        while (true) {
            Socket client = server.accept();

            // 字节流转为字符流
            InputStreamReader isr = new InputStreamReader(client.getInputStream());
            // 字符流转换为缓冲流
            BufferedReader br = new BufferedReader(isr);

            // 读取
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println("接收到客户端信息:" + line);
            }

            // 发送信息到客户端 字节流转字符流
            OutputStreamWriter out = new OutputStreamWriter(client.getOutputStream());
            // 字符流转换为缓冲流
            BufferedWriter bw = new BufferedWriter(out);
            
            String str = "你好,服务器收到了";
            bw.write(str);
            bw.flush();

            // 关闭资源
            client.close();
        }
    }
}
  • 客户端
import java.io.*;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args) throws IOException {
        System.out.println("Client start...");
        Socket client = new Socket("localhost", 1234);

        OutputStreamWriter osw = new OutputStreamWriter(client.getOutputStream());
        BufferedWriter bw = new BufferedWriter(osw);

        String str = "你好,我是客户端";
        bw.write(str);
        bw.flush();

        // 发送一个终结符,告诉服务器,已经发送完毕
        client.shutdownOutput();

        InputStreamReader isr = new InputStreamReader(client.getInputStream());
        BufferedReader br = new BufferedReader(isr);

        String line = null;

        while((line = br.readLine()) != null) {
            System.out.println("接收到服务器消息:" + line);
        }

        // 释放资源
        client.close();
    }
}

注意:

在交互时,服务器accept是阻塞的,read也是阻塞的,所以在发送完数据后,需要调用shutdownOutput来告知发送结束,否则会一直阻塞。

五.UDP相关类DatagramPacket类和DatagramSocket类

  1. 数据包类DatagramPacket
  • 作用:用来封装发送端或接收端要发送或接收的数据。
  • 构造方法
  • DatagramPacket(byte[] buf, int length):构造DatagramPacket,用来接收长度为length的数据包。
  • DatagramPacket(byte[] buf, int length, InetAddress address, int port):构造数据报包,用来将长度为length的包发送到指定主机上的指定端口号。
  • 常用方法
  • public int getLength():获得发送端实际发送的字节数或接收端世界接收的字节数
  • public int getPort():获得发送端或接收端端口号
  1. 发送数据包类DatagramSocket
  • 作用:用来发送和接收数据包对象
  • 构造方法
  • DatagramSocket():构造数据报套接字并将其绑定到本地主机上任何可用的端口。
  • DatagramSocket(int port):创建数据包套接字并将其绑定到本地主机上指定端口。
  • 常用方法
  • public void send(DatagramPacket p):从此套接字发送数据报包
  • public void receive(DatagramPacket p):从此套接字接收数据报包
  • public void close():关闭此数据报套接字
  1. InetAddress类(无构造方法)
  • 作用:代表一个IP地址
  • 静态方法
  • public static InetAddress getLocalHost():返回本地主机
  • public static InetAddress getByName():在给定主机名的情况下确定主机的 IP 地址。
  • 普通方法
  • public String getHostName(): 获取此 IP 地址的主机名。
  • public String getHostAddress():返回 IP 地址字符串(以文本表现形式)

六.基于UDP的Socket通信

  1. 步骤分析
  • 服务器端先启动,创建DatagramSocket对象,监听端口,用于接收
  • 服务器端创建DatagramPacket对象,打包用于接收的数据包
  • 服务器阻塞等待接收
  • 客户端启动,创建DatagramSocket对象,监听端口,用于接收
  • 客户端创建DatagramPacket对象,打包用于发送的数据包
  • 客户端发送数据,服务端接收
  • 服务端接收数据后,创建DatagramPacket对象,打包用于发送的数据包,发送数据
  • 客户端创建DatagramPacket对象,打包用于接收的数据包,阻塞等待接收
  • 客户端接收服务端数据,断开连接,释放资源
  1. 主要代码
  • 服务器
import java.io.IOException;
import java.net.*;

public class UDPServer {
    public static void main(String[] args) throws IOException {
        System.out.println("Server Start...");

        // 保存接收的数据
        byte[] rData = new byte[1024];

        // 接收时监听端口8888
        DatagramSocket ds = new DatagramSocket(8888);
        DatagramPacket rdp = new DatagramPacket(rData, rData.length);

        while (true) {
            ds.receive(rdp);
            System.out.println("Receive:" + new String(rData));

            // 发送数据
            byte[] sData = "你好,我是服务器".getBytes();
            DatagramPacket sdp = new DatagramPacket(sData, sData.length, rdp.getAddress(), rdp.getPort());
    
            ds.send(sdp);
        }
    }
}
  • 客户端
import java.io.IOException;
import java.net.*;

public class UDPClient {
    public static void main(String[] args) throws IOException {
        System.out.println("Client login...");
        // 构建数据包
        byte[] sendData = "Hello,I am Client".getBytes();
        InetAddress sendAddress = InetAddress.getLocalHost();
        DatagramPacket dp = new DatagramPacket(sendData, sendData.length, sendAddress, 8888);
        DatagramSocket ds = new DatagramSocket(1234);
        ds.send(dp);

        // 创建数据包,接收数据
        byte[] receiveData = new byte[1024];
        DatagramPacket rdp = new DatagramPacket(receiveData, receiveData.length);
        ds.receive(rdp);

        System.out.println("Receive:" + new String(receiveData));

        ds.close();
    }
}

注意:

UDP通信双方,对应端口号一致才可以,比如发送方使用1234端口,接收方需要监听1234端口才可以。