Socket套接字

目录

1.Socket套接字

1.1数据报套接字

1.2流套接字

1.3原始套接字

2.UDP数据报套接字编程

2.1API介绍

2.2实现简易回显服务器

3.TCP流套接字编程

3.1API介绍

3.2实现简易回显服务器


1.Socket套接字

Socket套接字,是由操作系统提供用于网络通讯的API,是基于TCP/IP协议实现的,主要作用于传输层,因此程序员只需制定好应用层的协议,再调用此API,即可实现网络通讯。

socket套接字分为三类

1.1数据报套接字

该套接字使用的传输层协议是UDP,即

1.无连接

2.不可靠传输

3.面向数据报

4.传送数据限制大小

1.2流套接字

该套接字使用的传输层协议是TCP,即

1.有连接

2.可靠传输

3.面向字节流

4.传输数据不限大小

1.3原始套接字

原始套接字⽤于⾃定义传输层协议,⽤于读写内核没有处理的IP协议数据。

2.UDP数据报套接字编程

2.1API介绍

DatagramSocket是UDP的Socket,用于发送和接收数据报,通过不同的构造方法,可以指定作为客户端还是服务端,客户端一般是不需要指定端口号,由操作系统分配,而服务端的端口号则需程序员指定,有了固定的端口号,方便为客户端服务。

|------------------------------------------------------------|---------------------------------------------|
| 方法名 | 方法说明 |
| public DatagramSocket() | 创建一个UDP套接字的Socket,绑定到本机随机端口,由操作系统决定,一般用作客户端 |
| public DatagramSocket(int port) | 创建一个UDP套接字的Socket,绑定到本机指定端口,一般用作服务端 |
| public void send(DatagramPacket p) | 此套接字发送数据报 |
| public synchronized void receive(DatagramPacket p) | 此套接字等待接收数据报,没有接收到会阻塞等待 |
| public void close() | 关闭套接字 |

DatagramPacket是用于UDP中封装数据报的。

|------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| 方法名 | 方法说明 |
| public DatagramPacket(byte buf[], int length) | 创建一个DatagramPacket用来接收数据报,数据保存在字节数组中,大小为length。 |
| public DatagramPacket(byte buf[], int offset, int length, SocketAddress address) | 创建一个DatagramPacket用来发送数据报,发送的数据在buf数组中,从offset位置开始,发送length个长度的数据到指定的主机中。 |
| public synchronized InetAddress getAddress() | 从接收的数据报中,获取发送方的主机IP地址,或者从发送的数据报中,获取接收端主机的IP地址 |
| public synchronized int getPort() | 从接收的数据报中,获取发送方的主机端口号,或者从发送的数据报中,获取接收端主机的端口号 |
| public synchronized byte[] getData() | 获取数据报中数据 |

构造UDP发送的数据报时,需要传⼊ SocketAddress ,该对象可以使⽤ InetSocketAddress

来创建。

|---------------------------------------------|-----------------------|
| 构造方法 | 说明 |
| InetSocketAddress(InetAddress addr,int por) | 创建一个Socket地址,主机IP和端口号 |

2.2实现简易回显服务器

实现一个简易回显服务器,客户端输入什么,服务端回应什么。

java 复制代码
package UDP;

import java.io.IOException;
import java.net.BindException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;

/**
 * 基于UDP的回显服务器
 */
public class UDPEchoServer {
    //创建一个DatagramSocket用来发送和接收数据包
    private DatagramSocket socket;

    //构造函数中指定服务器的端口号
    public UDPEchoServer(int port) throws SocketException {
        //对端口号进行校验
        if (port < 1024 || port > 65535) {
            throw new BindException("端口号必须在1025 ~ 65535之间");
        }
        //实例化后服务器已启动
        this.socket = new DatagramSocket(port);
    }

    /**
     * 处理用户请求
     * @throws IOException
     */
    public void start() throws IOException {
        //循环处理提供服务
        System.out.println("服务器已经启动");
        while (true) {
            //创建一个DatagramPacket接收请求
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
            //等待接收用户发来的请求
            socket.receive(requestPacket);
            //解析用户发来的请求
            String request = new String(requestPacket.getData(), 0,requestPacket.getLength(),"UTF-8");
            //处理用户的请求,并做出响应
            String response = process(request);
            //利用DatagramPacket封装响应
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(StandardCharsets.UTF_8), 0,
                    response.getBytes().length, requestPacket.getSocketAddress());
            //发送响应
            socket.send(responsePacket);
            //打印日志
            System.out.printf("[%s,%d] repuest: %s,response: %s\n", responsePacket.getAddress().toString(),
                    responsePacket.getPort(), request, response);
        }
    }

    /**
     * 计算响应
     * @param request
     * @return
     */
    protected String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UDPEchoServer udpEchoServer = new UDPEchoServer(8888);
        udpEchoServer.start();
    }
}
java 复制代码
package UDP;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

/**
 * 客户端
 */
public class UDPEchoClient {
    //创建一个DatagramSocket来收发数据报
    private DatagramSocket socket;
    //声明服务器的IP和端口号
    private String severIP;
    private int severPort;
    public UDPEchoClient(String severIP,int severPort) throws SocketException {
        //让操作系统为客户端指定一个随机端口号,并指定需要连接的客户端的IP和端口号
        socket = new DatagramSocket();
        this.severIP = severIP;
        this.severPort = severPort;
    }



    public void start() throws IOException {
        System.out.println("客户端已启动");
        while (true) {
            //1.接收用户发来的请求
            System.out.println("请输入请求");
            Scanner scanner = new Scanner(System.in);
            String request = scanner.nextLine();
            //校验请求
            if(request.isEmpty() || request == null){
                System.out.println("请求为空,请重新输入");
                continue;
            }
            //2.使用DatagramPacket封装请求
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(StandardCharsets.UTF_8),0
            ,request.getBytes().length,new InetSocketAddress(severIP,severPort));
            //3.发送请求
            socket.send(requestPacket);
            //4.使用DatagramPacket等待并接收服务端发来的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(responsePacket);
            //5.解析并处理响应
            String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"UTF-8");
            //6.打印结果
            System.out.printf("request: %s, response: %s\n",request,response);
        }

    }

    public static void main(String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1",8888);
        udpEchoClient.start();
    }
}

客户端发送请求:

服务器结果显示

3.TCP流套接字编程

3.1API介绍

SeverSocket是创建服务端socket的API

|----------------------------------------------------------|----------------------------------------------|
| 方法名 | 方法说明 |
| public ServerSocket(int port) throws IOException | 创建服务端Socket,并为服务端指定端口号port |
| public Socket accept() throws IOException | 服务端开始监听,有客户端连接后,返回一个客户端Socket,其中包含着双方通信的信息流。 |
| public void close() throws IOException | 关闭套接字 |

Socket是客户端Socket

|------------------------------------------------------------------|---------------------------|
| 方法名 | 方法说明 |
| public Socket(String host, int port) | 创建客户端Socket,并且指定服务端IP和端口号 |
| public InetAddress getInetAddress() | 返回此套接字所连接的地址 |
| public InputStream getInputStream() throws IOException | 获取此套接字的输入流 |
| public OutputStream getOutputStream() throws IOException | 获取此套接字的输出流 |

3.2实现简易回显服务器

实现一个简易回显服务器。

下面展示一个无法连接多个客户端的服务器实现,由于是单线程,只能同时连接一个客户端。

java 复制代码
package TCP;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * 基于TCP实现的简易回显服务器
 */
public class TCPEchoSever {
    //创建一个TCP的Socket
    protected ServerSocket socket;

    /**
     *构造方法
     * @param port 服务端端口号
     */
    public TCPEchoSever(int port) throws IOException {
        //校验端口号
        if(port <1035 || port > 65535){
            throw new BindException("端口号只能在1035 ~ 65535之间");
        }
        //初始化socket,启动服务器
        socket = new ServerSocket(port);
    }

    /**
     * 监听客户端
     * @throws IOException
     */
    public void  start() throws IOException {
        //循环监听处理客户端的请求
        while (true) {
            //开始监听
            Socket clientSocket = socket.accept();
            //处理请求
            processConnection(clientSocket);
        }
    }

    /**
     * 连接客户端处理业务
     * @param clientSocket 客户端socket
     */
    protected void processConnection(Socket clientSocket) {
        //校验客户端socket对象
        if(clientSocket == null){
            throw new RuntimeException("clientSocket为空,无法操作");
        }
        System.out.printf("[%s,%d] 客户端已经上线\n",clientSocket.getInetAddress(),
                clientSocket.getPort());
        //1.创建输入输出流来获取请求和计算响应
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            //2.接收用户请求,利用Scanner简化读取
            Scanner scanner = new Scanner(inputStream);
            //按照每次读取一行的协议
            while(scanner.hasNextLine()){
                //从网卡中获取请求
                String request = scanner.nextLine();
                //3.解析请求并计算响应
                String response = process(request);
                //4.将响应利用PrintWriter简化写入网卡中
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                //将内核缓存区内容强制刷新进网卡
                printWriter.flush();
                //5.打印日志
                System.out.printf("[%s,%d],request: %s, response: %s\n",
                        clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
            }
            System.out.printf("[%s:%d],客户端已经下线\n",clientSocket.getInetAddress(),clientSocket.getPort());

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

    }

    /**
     * 计算响应
     * @param request 客户请求
     * @return 响应
     */
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoSever tcpEchoSever = new TCPEchoSever(9999);
        tcpEchoSever.start();
    }
}

思考过后,可以用多个线程处理多个客户端,并且采用线程池的方法,防止同一时间内客户端请求数量过大,服务器配置不够。

java 复制代码
package TCP;

import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TCPThreadPoolSever extends TCPEchoSever{
    /**
     * 构造方法
     *
     * @param port 服务端端口号
     */
    public TCPThreadPoolSever(int port) throws IOException {
        super(port);
    }

    /**
     * 利用线程池,根据主机核显数配置
     * @throws IOException
     */
    @Override
    public void start() throws IOException {
        //利用线程池,防止客户端数量过大冲破服务器
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,
                1,TimeUnit.SECONDS,new LinkedBlockingQueue<>(1));
        while (true) {
            //开始监听
            Socket clientSocket = socket.accept();
            threadPoolExecutor.submit(()->{
                processConnection(clientSocket);
            });
        }
    }

    public static void main(String[] args) throws IOException {
        TCPThreadPoolSever tcpThreadPoolSever = new TCPThreadPoolSever(9999);
        tcpThreadPoolSever.start();
    }
}
java 复制代码
package TCP;



import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TCPEchoClient {
    //创建一个客户端socket
    private Socket socket;
    //描述服务器的IP和端口号
    private String severIp;
    private int severPort;

    /**
     * 构造方法
     * @param severIp 服务器IP
     * @param severPort 服务器端口号
     */
    public TCPEchoClient(String severIp,int severPort) throws IOException {
        //初始化客户端socket
        socket = new Socket(severIp,severPort);

    }

    /**
     * 启动客户端
     */
    public void start(){
        System.out.println("客户端已启动");
        //1.获取输入输出流
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            //2.获取用户请求
            while (true) {
                System.out.println("->");
                Scanner scanner = new Scanner(System.in);
                String request =scanner.nextLine();
                //3.校验请求
                if(request.isEmpty()||request == null){
                    System.out.println("请求不能为空,请重新输入");
                    continue;
                }
                //4.将请求写入网卡发送出去
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();
                //5.等待接收响应
                Scanner responseScanner = new Scanner(inputStream);
                //6.解析响应数据
                String response = responseScanner.nextLine();
                //7.打印日志
                System.out.printf("request: %s,response:%s\n",request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        TCPEchoClient tcpEchoClient = new TCPEchoClient("192.168.0.106",9999);
        tcpEchoClient.start();
    }
}

结果展示:

客户端发送请求:

服务器回应: