从零构建netty--socket基础

socket基础

对于java网络编程来讲,咱们能接触到的最底层即是socket了。我相信大部分阅读此篇文章的同仁都用过socket。可是,我这篇教程的理念之一就是事无巨细,或者说,啰里啰嗦。由于原本就是一个思惟的过程,因此,看官老爷们,就当作意识流风格来看吧(也是由于我文笔较差,所以文章的观赏性可能不是太好,给本身找个理由)。java

我在刚学编程的时候,实际上是一脸茫然的,虽然也会为屏幕输出“hello world",感到兴奋,可是兴奋之余会以为,嗯~?,这东西跟我想象的不同啊,就一个黑屏上输出几个字符,能干啥?这玩意跟网站啥的不沾边啊。包括学习了一些算法、数据结构后,仍是以为,网站啥的,跟我学的不沾边。算法

后来在学习参加了一个项目,涉及到网络编程,项目简单点说就是树莓派跟pc经过路由器进行数据交换。从那时候起开始使用socket,固然那个时候不求甚解,根本跟学过的网络模型也对不上号。什么三次握手,四次挥手,没用到啊。先无论,干就完了。shell

秉着这个徐循渐进的学习及思考过程,我就开始了socket编程。编程

连接

来一段服务端socket示例:服务器

代码 1-1网络

public class OioServer {
    private ServerSocket serverSocket;
    private void openServer(int port) throws IOException {
        // 1 建立ServerSocket
        serverSocket = new ServerSocket();
        // 2 绑定端口
        SocketAddress socketAddress = new InetSocketAddress(port);
        serverSocket.bind(socketAddress);
    }

    @Test
    public void testOpenServer() throws IOException {
        OioServer oioServer = new OioServer();
        oioServer.openServer(8081);
        // block
        Scanner scan = new Scanner(System.in);
        scan.next();
    }
}

运行test后,服务器开始监听port端口了。而后咱们验证下是否是真的在监听,我再window上测试的,因此这里以window为例。首先要打开cmd窗口,而后输入命令数据结构

代码 1-2app

netstat -aon|findstr "8081"

图1-1
image-20210317210840754.pngsocket

能够看到真的在监听了, 如今虽然开始监听了,可是,真的有客户端从8081进来该怎么办?咱们还须要处理链接,这个链接就是咱们的主角socket.学习

修改1-1代码以下:

代码1-2

public class OioServer {
    private ServerSocket serverSocket;
    private void openServer(int port) throws IOException {
        // 1 建立ServerSocket
        serverSocket = new ServerSocket();
        // 2 绑定端口
        SocketAddress socketAddress = new InetSocketAddress(port);
        serverSocket.bind(socketAddress);
    }

    @Test
    public void testOpenServer() throws IOException {
        OioServer oioServer = new OioServer();
        oioServer.openServer(8081);
        Socket socket = oioServer.listenAccept();
    }
}

此时,服务端就运行有客户端进来链接了。而后咱们再写一个客户端。

代码 1-3

public class OioClient{
   
    public Socket connect(String host, int port) throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(host, port));
        return socket
    }

    @Test
    public void testClient() throws IOException, InterruptedException {
        OioClient oioServer = new OioClient();
        oioServer.connect("127.0.0.1", 8081);
         // block
         Scanner scan = new Scanner(System.in);
         scan.next();
    }
}

运行test,为例不让test退出,咱们用标准出入block住test线程。记住,如今咱们服务端还在监听,我服务端和客户端都跑在同一台pc上。效果如图

图1-2
image-20210319171212314.png

能够看到第二行本地地址是127.0.0.1:6283远程地址是127.0.0.1:8081,那咱们是否是能够猜到,这个TCP链接是客户端的,客户端的IP是6283,咱们虽然没指定,可是系统会给咱们随机分配一个可用的端口,不信能够试一下,下次可能就不同了。

瓜熟蒂落,第三行就是咱们服务端的TCP链接。

那么咱们是否是能够有一个这样的结论:socket其实就是一个TCP链接的封装(固然也能够是UDP,这里不作讨论)?或者socket的下层是TCP/IP层?

到如今咱们大概知道了socket跟TCP的联系,既跟网络模型的联系。

有了TCP连接,咱们就能够开始传输数据了。不少网上例子是,简历socket链接后,客户端给服务器发送数据,而后服务器接收数据并回复。可是请不要误解,过程不必定所有是这样的。咱们客户端和服务端创建连接后,两边都获得一个socket,这两个socket是等价的。若是你对TCP有了解的话,必定会知道TCP的四次挥手,网上关于四次挥手的图大概也都是下图这个样子:

图1-3

image-20210319172545627.png

可是呢,四次挥手一样能够由服务端发起。所以,就算是在TCP层,两个socket的是等价的。

通信

咱们获得活跃的socket后,就能够对socket进行读写操做了。既然socket没什么区别,那咱们能够无差别对待socket。咱们来写socket读写取的方法:

代码1-4

public class SocketUtils {
    /**
     * 从socket中读数据
     */
    public static String read(Socket socket) {
        try {
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            StringBuilder sb = new StringBuilder();
            while ((len = inputStream.read(bytes)) != -1) {
                //注意指定编码格式,发送方和接收方必定要统一,建议使用UTF-8
                sb.append(new String(bytes, 0, len, "UTF-8"));
            }
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 往socket中写数据
     */
    public static void write(Socket socket, String response) {
        try {
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(response.getBytes("UTF-8"));
            outputStream.flush();
            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上面代码以入参的形势将socket传入,而后就行读写,注意,咱们这两个并无调用方法socket.close,也就是说socket能够重复读写数据,而网上大部分教程直接会吧socket给关闭,形成初学者觉得每次读写玩就得关闭socket的错觉。

在方法read中,根据(len = inputStream.read(bytes)) != -1来判断是否读取完数据。而写入数据端怎么告诉接收端它完成了写入呢?咱们能够用socket.shutdownOutput()方法。

好,接下来咱们就来测试下发送与接收数据。

代码1-4

public class OioServer {
    ...

    @Test
    public void testOpenServer() throws IOException {
         OioServer oioServer = new OioServer();
        oioServer.openServer(8081);
        Socket socket = oioServer.listenAccept();
        SocketUtils.write(socket, "Can you hear me?");
        String msg = SocketUtils.read(socket);
        System.out.println(msg);
        // block
        Scanner scan = new Scanner(System.in);
        scan.next();
    }
}

代码1-5

public class OioClient {

   ...
    
    @Test
    public void testClient() throws IOException, InterruptedException {
        OioClient oioServer = new OioClient();
        Socket socket = oioServer.connect("127.0.0.1", 8081);
        String msg = SocketUtils.read(socket);
        System.out.println(msg);
        SocketUtils.write(socket, "Yes, I can hear you!");
        // block
        Scanner scan = new Scanner(System.in);
        scan.next();
    }
}

先执行testOpenServer()再执行testClient()。就会完成对话。固然咱们能够重复调SocketUtils.read SocketUtils.write进行通信,只要两边socket不关闭。

小结

此篇文章总结了Socket的一些使用,包括服务器启动,socket创建,并证实了socket的等价性

相关文章
相关标签/搜索