理解NIO中的Socket

本文详细介绍了Java中Socket通信的使用,包括SeverSocket类的构造方法和配置,Socket类的bind、connect操作,以及TCP的Nagle算法和KeepAlive选项。此外,还提到了基于UDP的Socket通信的基本实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

Socket通信是计算机之间常用的通信技术,http传输协议底层就是靠它的,它的底层用c++实现,Java中对其进行了重量级的封装。我们先来学习Java中Socket怎么使用,下面例子是传输图片的(从C->S)

服务端

package socket;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Sever {
    public static void main(String[] args) throws IOException {
        //服务端
        
        //传的参数有很多种,常用的就是这种直接指定端口为9999来进行服务
        ServerSocket serverSocket = new ServerSocket(9999);
        //这样服务端就会阻塞自己,直到有客户端来进行连接
        Socket socket = serverSocket.accept();

        //进行活动
        byte[] bytes = new byte[1024];
        //1.因为要接收,所以从socket中获得input流
        InputStream inputStream = socket.getInputStream();

        int read = inputStream.read(bytes);
        //2.通过文件输出流来从内存存到硬盘
        FileOutputStream fileOutputStream = 
                new FileOutputStream(new File("D://temp.jpg"));
        //3.开始接收
        while (read!=-1){
            fileOutputStream.write(bytes,0,read);
            read=inputStream.read(bytes);
        }
        //关闭,关闭的顺序像栈一样,最早打开的最晚关闭
        fileOutputStream.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
    }
}

客户端 

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

public class Client {
    public static void main(String[] args) throws IOException {
        //客户端
        //前面表示ip地址,后面指定连接端口
        //host主机不存在时会抛出UnknownHostException异常
        Socket socket = new Socket("localhost",9999);

        //进行活动
        String pngFile = "C:\\Users\\89507\\Desktop\\aaa.png";
        //1.获取文件输入流把文件加载到内存
        FileInputStream fileInputStream =
                new FileInputStream(new File(pngFile));
        byte[] bytes = new byte[1024];
        //2.打开socket的输出流,准备传输
        OutputStream outputStream = socket.getOutputStream();
        int read = fileInputStream.read(bytes);
        //3.进行传输
        while (read!=-1){
            outputStream.write(bytes,0,read);
            read=fileInputStream.read(bytes);
        }
        //关闭
        outputStream.close();
        fileInputStream.close();
        socket.close();
    }
}

一些小点:

SeverSocket中的.accept()方法具有阻塞特性,即没有客户端来连接的话他不会往下运行。他会返回一个socket对象,服务端与客户端通过操作这个对象来进行传输

Socket中的InputStream.read()方法具有阻塞特性,即如果没读到东西的话,他会一直尝试读取

C/S端那边要传输数据,那边通过socket开启OutputSteam()流,用这个流进行write()方法;另一端则开启InputStream流,用read()方法来读取传来的数据

close()方法:如果调用SocketInputSteam的close方法时,顺便也会把Socket进行close

    public void close() throws IOException {
        // Prevent recursion. See BugId 4484411
        if (closing)
            return;
        closing = true;
        if (socket != null) {
            if (!socket.isClosed())
                socket.close();
        } else
            impl.close();
        closing = false;
    }

在服务端和客户端互传对象的时候:如果服务端先获得ObjectInputStream对象,客户端就要现货的ObjectOutput对象,反之亦然;特别需要注意的一点是:如果两边同时先获得ObjectInputStream对象,客户端会优先获得该对象,这样服务端走到获取对象那一步时会阻塞

SeverSocket类

有这样一个构造方法:

    public ServerSocket(int port, int backlog) throws IOException {
        this(port, backlog, null);
    }

1.其backlog的含义是设置最大等待队列长度,如果队列已满,则拒绝该链接,默认值为50

2.bind()方法使用场景是在新建SeverSocket时没用写端口,这个时候系统会自动选取一个空闲端口,不过我们想让他搞成我们想要的端口时使用

        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress(8888));

那么问题来了,这个InetSocketAddress是什么呢?

    他是表示此类实现IP套接字地址(IP地址+端口号)。其职能用于bind操作

3.设置端口是否允许被复用

 serverSocket.setReuseAddress(true);

即允许端口复用,设置为false则表明该端口只能特定的给一个client提供服务

Socket类的使用

1.socket类也有bind操作,其操作对象也是InetSocketAddress,给自己设置端口和地址

2.connect,使用来连接sever端(前提是初始化Socket对象的时候没有设置连接目标)。第二个构造方法可以设置timeout

3.有许多可以获取本地或连接到远程Sever端信息的API,如下:

   public InetAddress getInetAddress()
   public InetAddress getLocalAddress()
   public int getPort()
   public int getLocalPort()

   .....

4.Socket选项TcpNoDelay

    public void setTcpNoDelay(boolean on) throws SocketException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        getImpl().setOption(SocketOptions.TCP_NODELAY, Boolean.valueOf(on));
    }

作用是启动/禁用TCP_NODELAY(即启用/禁用Nagle算法)

    什么是Nagle算法呢?

        该算法可以将许多要发送的数据进行本地缓存(这一过程叫Nagling),以减少发送数据来提高网络软件运行效率

       其原理是在未确认ACK之前让发送器吧数据送到缓存里,后面的数据也继续放入缓存中,知道得到确认ACK或者直到“攒到”一定大小的数据在发送

其原理是这样子:

Nagle算法使用与大包,高延迟的场合,而对于要求交互素的的B/S或C/S就不合适了。

Socket在创建的时候都是使用Nagle算法的

5.KeepAlive:当其设置为trul是,一段时间内没用任何数据发过来,那么端点就会发送一个ACK包探测对方,探测双方的TCP/IP连接是否有效,否则一方宕机是这边还保存着连接无疑很浪费资源

基于UDP实现的Socket通信

基本实现:(客户端给服务端输入数据)需要注意的是UDP必须得在两台机器上进行运行

import java.io.IOException;
import java.net.*;

public class Sever {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(8888);
        byte[] bytes = new byte[12];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, 10);
        socket.receive(datagramPacket);
        socket.close();
    }
}
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;


public class Client {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket();
        socket.connect(new InetSocketAddress("localhost",8888));
        String s = "1234567890";
        byte[] bytes = s.getBytes();
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        socket.send(datagramPacket);
        socket.close();
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值