Socket套接字

发布于:2025-07-23 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

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实现简易回显服务器

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

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();
    }
}
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实现简易回显服务器

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

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

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();
    }
}

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

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();
    }
}
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();
    }
}

结果展示:

客户端发送请求:

服务器回应:


网站公告

今日签到

点亮在社区的每一天
去签到