详解什么是 socket、套接字、“插座”!

小猴子的技术笔记

    欢迎大家搜索“小猴子的技术笔记”关注我的公众号,有问题可以及时和我交流。

    你知道插座吗?你知道网络编程中的插座吗?也许你会有点迷惑,什么是插座!但是我如果说出“套接字”、“socket”这样的关键字你就会恍然大悟。

    所谓的“插座”叫做套接字又叫做socket,用来表示一个端点,可以与网络中其他的socket进行连接,然后进行数据的传输。

    我们都知道在网络上中可以通过IP地址确定唯一的一台主机,然后主机和主机之间进行通讯。但是准确来说:网络通讯中的双方并不是主机,而是主机中的进程。这就需要确定主机中那个进程进行的网络通讯,因此还需要一个端口号来确定主机中的唯一进程。

    如果从操作系统的角度来说:套接字是使用标准的Unix文件描述符来与其他计算机进行通讯的一种方式。

    IP+PORT的组合就构成了网络中唯一标识符“套接字”。端口号的范围是0-65535,但是低于256的端口号进行了保留,作为系统的标准的应用程序端口。比如HTTP的默认的80,POP3的110,Telent的23,FTP的21等。

    套接字允许两个进程进行通讯,这两个进程可能运行在同一个机器上,也可能运行在不同机器上。
在这里插入图片描述
    套接字主要有两类:流式套接字和数据报套接字。

    流式套接字提供了面向连接、可靠的数据传输服务,可以非常准确的实现按照顺序接收数据。如果你通过流式套接字发送"h","e","l","l","o"五个字符,它到达另一端的顺序也将会是"h","e","l","l","o"。原因就在于流式套接字使用的是TCP进行数据传输,能够保证数据的安全性。流式套接字是最常使用的,一些众所周知的协议使用的都是它,如HTTP,TCP,SMTP,POP3等。
在这里插入图片描述
    数据报套接字:提供无连接的服务,你不需要像流式套接字那样建立一个连接,而只需要将地址信息一同打包然后发出去。该服务使用的是UDP进行传输,延迟小,效率高但是不能保证数据传输的准确性。

    如果我们创建一个ServerSocket需要经历以下几个步骤:bind-->listen-->accpet-->recv-->write-->close。这也是底层Linux/或者Unix对一个端口监听经历的步骤。

    bind:绑定一个地址和端口,确认端点的信息。


ServerSocket serverSocket = new ServerSocket(8888);

    这里也许你看到了并没有指明IP,那么“ServerSocket”会自动获取本机的ip地址作为填充,源码如下:

public ServerSocket(int port) throws IOException {
    this(port, 50, null);
}
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
    setImpl();
    if (port < 0 || port > 0xFFFF)
        throw new IllegalArgumentException("Port value out of range: " + port);
    if (backlog < 1)
      backlog = 50;
    try {
        bind(new InetSocketAddress(bindAddr, port), backlog);
    } catch(SecurityException e) {
        close();
        throw e;
    } catch(IOException e) {
        close();
        throw e;
    }
}
public InetSocketAddress(InetAddress addr, int port) {
    holder = new InetSocketAddressHolder(
                    null,
                    addr == null ? InetAddress.anyLocalAddress() : addr,
                    checkPort(port));
}

    会对“InetAddress”进行判断,如果为空了就获取本地的地址。

    listen:当端口和ip绑定之后就会进行到监听状态,这个时候会监听是否有连接请求上来。

    accept:如果客户端的ip和端口符合服务端,也就是插头能够对应上插座了,就接收这个客户端的连接。这里“ServerSocket.accept()”将“listen”和“accept”进行了整合,监听并且接受一个连接。


Socket socket = serverSocket.accept();

在这里插入图片描述
    send:发送数据,需要先获得socket的的输出流,然后通过输出流进行数据的发送。


OutputStream out = socket.getOutputStream();

    拿到的是输出流,因此发送的是字节,需要将字符串进行字节的转换之后,在进行编码之后发送。如果没有进行编码的话,默认采用的是系统的默认编码。


out.write("hello".getBytes(StandardCharsets.UTF_8));

    recv: 接受数据,也需要先获取到socket的输入流。


InputStream inputStream = socket.getInputStream();

    获取输入流之后按照字节进行读取数据。如果数据发送完毕了,也就是调用了“socket.close()”就会读取到“-1”,表示没有数据可以读到了。

InputStream in = socket.getInputStream();
StringBuffer sb = new StringBuffer();
int len;
while ((len = in.read(bytes)) != -1) {
    sb.append(new String(bytes, 0, len));
}
System.out.println("接收到消息:" + sb.toString());

    close:关闭(这个不是必须的,如果是长链接,那么将不会关闭socket,除非服务发生异常)


serverSocket.close();

在这里插入图片描述
    上述的客户端是阻塞式的,一次只能接受一个socket客户端进行连接。

    客户端要经历:connect-->recv/send-->close

Socket socket = new Socket("localhost", 8888);

    recv:获取到输入流,读取字节数字。

byte[] bytes = new byte[1024];
InputStream in = socket.getInputStream();
StringBuffer sb = new StringBuffer();
int len;
while ((len = in.read(bytes)) != -1) {
    sb.append(new String(bytes, 0, len));
}
System.out.println("接收到消息:" + sb.toString());

    write:获取到输出流,将数据按照字节输出给服务端。

OutputStream out = socket.getOutputStream();
out.write("hello".getBytes(StandardCharsets.UTF_8));

    close:关闭资源

in.close();
out.close();
socket.close();
System.out.println("数据接收完毕");

在这里插入图片描述
    至此基于一个简单的socket网络编程就完成了。我们需要重点了解,socket是基于IP+Port组合的,用于网络中的通信之间的建立。网络编程会在我们的日常开发中起到很重要的作用,很多框架都是基于socket演变而来的,比如tomcat、netty等。
    最后欢迎大家添加我的微信公众号号"小猴子的技术笔记",如果有问题可以及时和我交流。

阅读 420
9 声望
1 粉丝
0 条评论
你知道吗?

9 声望
1 粉丝
宣传栏