hello/hi的简单的网络聊天程序

hello/hi的简单的网络聊天程序

0 Linux Socket API

Berkeley套接字接口,一个应用程序接口(API),使用一个Internet套接字的概念,使主机间或者一台计算机上的进程间可以通讯。 它可以在很多不同的输入/输出设备和驱动之上运行,尽管这有赖于操作系统的具体实现。 接口实现用于TCP/IP协议,因此它是维持Internet的基本技术之一。 它是由加利福尼亚的伯克利大学开发,最初用于Unix系统。 如今,所有的现代操作系统都有一些源于Berkeley套接字接口的实现,它已成为连接Internet的标准接口。

我们所用的Linux Socket API实际上就是 Berkeley Socket,下面给出一些建立TCP连接常用的Socket接口函数及其功能概要:

  • socket() 创建一个新的确定类型的套接字,类型用一个整型数值标识(文件描述符),并为它分配系统资源。
  • bind() 一般用于服务器端,将一个套接字与一个套接字地址结构相关联,比如,一个指定的本地端口和IP地址。
  • listen() 用于服务器端,使一个绑定的TCP套接字的tcp状态由CLOSE转至LISTEN;操作系统内核为此监听socket所对应的tcp服务器创建一个pending socket队列和一个established socket队列;参数backlog指定pending socket队列的长度,0表示长度可以无限大。pending socket,就是某客户端三次握手的syn包到达,内核为这个syn包对应的tcp请求生成一个socket(状态为SYN_RECV),但三次握手还没有完成时的socket。
  • connect() 用于客户端,为一个套接字分配一个自由的本地端口号。 如果是TCP套接字的话,它会试图获得一个新的TCP连接。
  • accept() 用于服务器端。 它接受一个从远端客户端发出的创建一个新的TCP连接的接入请求,创建一个新的套接字,与该连接相应的套接字地址相关联。
  • send()recv(),或者write()read(),或者recvfrom()sendto(), 用于往/从远程套接字发送和接受数据。
  • close() 用于系统释放分配给一个套接字的资源。 如果是TCP,连接会被中断。

1 hello/hi程序

1.0 功能概述

下面提供一个使用Java语言的简单的hello/hi程序代码,程序分为两部分,分别是服务端和客户端。该程序利用Java Socket API建立一个TCP连接,先从客户端向服务端发送一个“Hi,I am Client”字符串,然后服务端再返回一个“Hi,I am Server”字符串。

1.1 服务端

服务端的功能是使用Java的ServerSocket类的对象监听8090端口,然后使用ServerSocket类的accept()与8090端口建立连接,当收到一个来自客户端的"Hi,I am Client"时,回复一个"Hi,I am Server",代码如下:

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket socket = new ServerSocket(8090);// 监听8090端口
        System.out.println("TCP server ready.");        
        Socket sock = socket.accept();// 建立连接
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
            try (BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
                
                String cmd = reader.readLine();// 读取发送到服务端的数据       
                if ("Hi,I am Client".equals(cmd)) {
                    System.out.println("Client:"+cmd+"\n");
                    writer.write("Hi,I am Server"+ "\n");
                    writer.flush();
                } else {
                    writer.write("require data\n");
                    writer.flush();
                }
            }
        }   
        sock.close();// 关闭连接
        socket.close();// 关闭监听端口
    }
}

1.2 客户端

客户端的功能是获取本机地址,然后与本机的8090端口建立连接。向服务端发送"Hi,I am Client"后等待服务的回应的消息。

public class Client {
    public static void main(String[] args) throws IOException {
        InetAddress addr = InetAddress.getLoopbackAddress();// 获取本机地址,即“127.0.0.1”   
        try (Socket socket = new Socket(addr, 8090)) {// 与本机8090端口建立连接
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
                try (BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8))) {
                    writer.write("Hi,I am Client\n");
                    writer.flush();         
                    String resp = reader.readLine();// 读取本机8090端口返回的数据
                    System.out.println("Server: " + resp);
                }
            }
        }
    }
}

2 分析Java Socket API和Linux Socket API的关系

上面两段程序调用ServerSocket类的构造方法和close(),还有Socket类的构造方法、accept()getInputStream()、``getOutputStream()close()`方法。下面通过对ServerSocket类以及Socket类的源码分析找到Java Socket API和Linux Socket API的对应关系。

2.0 SocketImpl类与Linux Socket API的关系

抽象类SocketImpl是实际上实现套接字的所有类的通用超类,它将实际的Socket操作抽象出来,在逻辑上与Linux Socket API的核心操作相对应,如果找到SocketImpl类的操作,就相当于找到了对应的Linux Socket API操作。

2.1 ServerSocket类

2.1.0 SeverSocket()

在服务端程序中,构造了一个ServerSocket类对象监听8090端口:

ServerSocket socket = new ServerSocket(8090);

找到相应的源码:

public ServerSocket(int port) throws IOException {    
    this(port, 50, (InetAddress)null);
}
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
    this.created = false;
    this.bound = false;
    this.closed = false;
    this.closeLock = new Object();
    this.oldImpl = false;
    this.setImpl();
    if (port >= 0 && port <= 65535) {
        if (backlog < 1) {
            backlog = 50;
        }

        try {
            this.bind(new InetSocketAddress(bindAddr, port), backlog);
        } catch (SecurityException var5) {
            this.close();
            throw var5;
        } catch (IOException var6) {
            this.close();
            throw var6;
        }
    } else {
        throw new IllegalArgumentException("Port value out of range: " + port);
    }

可以看到,this.bind(new InetSocketAddress(bindAddr, port), backlog);语句中调用了bind()方法,查看其具体实现:

this.getImpl().bind(epoint.getAddress(), epoint.getPort());
this.getImpl().listen(backlog);

发现SeverSocket.bind()方法与Linux Socket API中的bind()listen()函数相对应。

所以ServerSocket()方法对应的Linux Socket API是socket()bind()listen()

2.1.1 accept()

服务端中还使用ServerSocket.accept()方法初始化了一个Socket对象,下面找到其源码:

public Socket accept() throws IOException {
    if (this.isClosed()) {
        throw new SocketException("Socket is closed");
    } else if (!this.isBound()) {
        throw new SocketException("Socket is not bound yet");
    } else {
        Socket s = new Socket((SocketImpl)null);
        this.implAccept(s);
        return s;
    }
}

发现其对应的Linux Socket API是accept()

2.1.2 close()

public void close() throws IOException {
    synchronized(this.closeLock) {
        if (!this.isClosed()) {
            if (this.created) {
                this.impl.close();
            }

            this.closed = true;
        }
    }
}

显然,ServerSocket.close()对应的Linux Socket API是close()

2.2 Socket类

在客户端和服务端分别构造了一个Socket类对象用于建立Tcp连接和通信。下面分析所用函数的源码。

2.2.0 Socket()

客户端和服务端使用的构造函数分别是Socket(SocketImpl impl)Socket(InetAddress address, int port),前者其实是复制了一个Socket类的对象,只需看后者的源码即可:

public Socket(InetAddress address, int port) throws IOException {
    this(address != null ? new InetSocketAddress(address, port) : null, (SocketAddress)null, true);
}

接着找真正的调用的构造函数:

private Socket(SocketAddress address, SocketAddress localAddr, boolean stream) throws IOException {
    this.created = false;
    this.bound = false;
    this.connected = false;
    this.closed = false;
    this.closeLock = new Object();
    this.shutIn = false;
    this.shutOut = false;
    this.oldImpl = false;
    this.setImpl();
    if (address == null) {
        throw new NullPointerException();
    } else {
        try {
            this.createImpl(stream);
            if (localAddr != null) {
                this.bind(localAddr);
            }

            this.connect(address);
        } catch (IllegalArgumentException | SecurityException | IOException var7) {
            try {
                this.close();
            } catch (IOException var6) {
                var7.addSuppressed(var6);
            }

            throw var7;
        }
    }
}

发现该方法调用了Socket类的bind()connect()方法。

根据前面已经知道,ServerSocket类的ServerSocket.bind()方法与Linux Socket API中的bind()listen()函数相对应,那么在Socket类中是否是这么对应的呢?查看一下Socket.bind()方法的核心语句:this.getImpl().bind(addr, port);发现在Socket.bind()方法对应只是对应Linux Socket API中的bind()

Socket.connect()方法的核心语句如下:

if (!this.oldImpl) {
    this.impl.connect(epoint, timeout);
} else {
    if (timeout != 0) {
        throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
    }

    if (epoint.isUnresolved()) {
        this.impl.connect(addr.getHostName(), port);
    } else {
        this.impl.connect(addr, port);
    }
}

发现对应的Linux Socket API中的函数是connect()

由上面的分析得出,Socket()对于的Linux Socket API是socket()bind()connect()

2.2.1 getInputStream() & getOutputStream()

像之前一样,找到对应的源码:

public InputStream getInputStream() throws IOException {
    if (this.isClosed()) {
        throw new SocketException("Socket is closed");
    } else if (!this.isConnected()) {
        throw new SocketException("Socket is not connected");
    } else if (this.isInputShutdown()) {
        throw new SocketException("Socket input is shutdown");
    } else {
        InputStream is = null;

        try {
            is = (InputStream)AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() {
                public InputStream run() throws IOException {
                    return Socket.this.impl.getInputStream();
                }
            });
            return is;
        } catch (PrivilegedActionException var3) {
            throw (IOException)var3.getException();
        }
    }
}
public OutputStream getOutputStream() throws IOException {
    if (this.isClosed()) {
        throw new SocketException("Socket is closed");
    } else if (!this.isConnected()) {
        throw new SocketException("Socket is not connected");
    } else if (this.isOutputShutdown()) {
        throw new SocketException("Socket output is shutdown");
    } else {
        OutputStream os = null;

        try {
            os = (OutputStream)AccessController.doPrivileged(new PrivilegedExceptionAction<OutputStream>() {
                public OutputStream run() throws IOException {
                    return Socket.this.impl.getOutputStream();
                }
            });
            return os;
        } catch (PrivilegedActionException var3) {
            throw (IOException)var3.getException();
        }
    }
}

可以看到,Socket.getInputStream()Socket.getOutputStream()对应的Linux Java API是recv()send()

2.2.2 close()

public synchronized void close() throws IOException {
    synchronized(this.closeLock) {
        if (!this.isClosed()) {
            if (this.created) {
                this.impl.close();
            }

            this.closed = true;
        }
    }
}

同样,Socket.close()对应的Linux Java API是close()

3 总结

由上面的探究可知,Java Socket API和Linux Socket API的关系如下表:

Java Socket API Linux Socket API
ServerSocket() socket()bind()listen()
ServerSocket.accept() accept()
ServerSocket.bind() bind()listen()
ServerSocket.close() close()
Socket() socket()bind()connect()
Socket.bind() bind()connect()
Socket.getInputStream() recv()
Socket.getOutputStream() send()
Socket.close() close()

可以看到,Java Socket API对Linux Socket API进行了进一步封装,使之更容易根据不同情况进行TCP连接。

参考资料

https://www.cnblogs.com/abcboy/p/9769230.html

https://github.com/mengning/net

https://zh.wikipedia.org/wiki/Berkeley%E5%A5%97%E6%8E%A5%E5%AD%97

posted @ 2019-12-11 15:02  Litosty  阅读(295)  评论(0编辑  收藏  举报