一、基于TCP协议的网络编程

1、TCP/IP是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路;

  一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路来进行通信;

2、Socket编程主要是指基于TCP/IP协议的网络编程。

Java对基于TCP/IP协议的网络通信提供了良好的封装;

Java使用Socket对象来代表两端(服务器程序和客户端程序)的通信端口;并通过Socket产生的IO流来进行通信。

其中 ServerSocket 类表示 Socket 服务器端,Socket 类表示 Socket 客户端。这两个类中的方法都比较相同。

简单来说,Java的网络编程是使用Socket对象进行的I/O编程。

1、ServerSocket 类

用来实现服务器套接字。服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。

服务器套接字一次可以与一个套接字连接,如果多台客户端同时提出连接请求,请求连接的客户端会被存入一个队列中,然后从中取出一个套接字与服务器新建的套接字连接起来。

若请求连接大于最大容纳数,则多出的连接请求被拒绝;默认的队列大小是 50。

1. ServerSocket 的构造方法

ServerSocket()           创建非绑定服务器套接字。

ServerSocket(int port)           创建绑定到特定端口的服务器套接字。backlog (默认队列大小是50)

ServerSocket(int port, int backlog)           利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号。

ServerSocket(int port, int backlog, InetAddress           使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。

2. ServerSocket 的常用方法

 Socket

accept()           侦听并接受到此套接字的连接。

 void

bind(SocketAddress           将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

 void

bind(SocketAddress           将 ServerSocket 绑定到特定地址(IP 地址和端口号)。

 void

close()           关闭此套接字。

 ServerSocketChannel

getChannel()           返回与此套接字关联的唯一 ServerSocketChannel 对象(如果有)。

 InetAddress

getInetAddress()           返回此服务器套接字的本地地址。

 int

getLocalPort()           返回此套接字在其上侦听的端口。

 SocketAddress

getLocalSocketAddress()           返回此套接字绑定的端点的地址,如果尚未绑定则返回 null

 int

getReceiveBufferSize()           获取此 ServerSocket 的 SO_RCVBUF 选项的值,该值是将用于从此 ServerSocket 接受的套接字的建议缓冲区大小。

调用 accept() 方法会返回一个和客户端 Socket 对象相连接的 Socket 对象。

  • 服务器端的 Socket使用 getOutputStream() 方法获得的输出流,将指向客户端 Socket 使用 getInputStream() 方法获得那个输入流。
  • 同样,服务器端的 Socket使用的 getInputStream() 方法获得的输入流,将指向客户端 Socket 使用的 getOutputStream() 方法获得的那个输出流。
  • 也就是说,当服务器向输出流写入信息时,客户端通过相应的输入流就能读取,反之同样如此。

2、Socket 类

用来实现客户端套接字。用于呼叫远端机器上的一个端口,主动向服务器端发送数据(当连接建立后也能接收数据)。

1. Socket 的构造方法


Socket()           通过系统默认类型的 SocketImpl 创建未连接套接字

Socket(InetAddress           创建一个流套接字并将其连接到指定 IP 地址的指定端口号。

Socket(InetAddress address, int port, InetAddress           创建一个套接字并将其连接到指定远程地址上的指定远程端口。

Socket(Proxy           创建一个未连接的套接字并指定代理类型(如果有),该代理不管其他设置如何都应被使用。

Socket(String           创建一个流套接字并将其连接到指定主机上的指定端口号。

Socket(String host, int port, InetAddress           创建一个套接字并将其连接到指定远程主机上的指定远程端口。

2. Socket 的常用方法


方法摘要

 void

bind(SocketAddress           将套接字绑定到本地地址。

 void

close()           关闭此套接字。

 void

connect(SocketAddress           将此套接字连接到服务器。

 void

connect(SocketAddress           将此套接字连接到服务器,并指定一个超时值。

 boolean

isBound()           返回套接字的绑定状态。

 boolean

isClosed()           返回套接字的关闭状态。

 boolean

isConnected()           返回套接字的连接状态。

 boolean

isInputShutdown()           返回是否关闭套接字连接的半读状态 (read-half)。

 boolean

isOutputShutdown()           返回是否关闭套接字连接的半写状态 (write-half)。

 void

setSoLinger(boolean on, int linger)           启用/禁用具有指定逗留时间(以秒为单位)的 SO_LINGER。

 void

setSoTimeout(int timeout)           启用/禁用带有指定超时值的 SO_TIMEOUT,以毫秒为单位。

SO_TIMEOUT选项

该选项表示accept()方法等待客户端连接的时间,以毫秒为单位。

当accept()方法在超时时间内没有获得连接,就会抛出SocketTImeoutException。

选项必须在进入阻塞操作前被启用才能生效。超时值必须是 > 0 的数。超时值为 0 被解释为无穷大超时值。 

SO_REUSEADDR选项

关闭 TCP 连接时,该连接可能在关闭后的一段时间内保持超时状态。

此时,是否允许新的ServerSocket绑定到与旧的ServerSocket 同样的端口上,在某些操作系统上允许重用端口,有些则不允许。很多服务器程序都是用固定的端口,当程序关闭后,端口可能还被占用一段时间,如果此时立刻重启服务器,服务器就会无法绑定端口,抛出BindException异常。为了确保不发生这种异常,就可以调用ServerSocket的setReuseAddress(boolean on)方法:

if(!serverSocket.getResuseAddress()) 

serverSocket.setReuseAddress(boolean on);

该方法必须在绑定端口前设置。

SO_RCVBUF选项

用于设置内部套接字接收缓冲区的大小和设置公布到远程同位体的 TCP 接收窗口的大小。 

该方法必须在绑定端口前设置。
 

 

基于TCP的套接字编程实现流程

服务器端流程:

  1. 创建服务器套接字(ServerSocket)
  2. 将套接字绑定到一个本地地址和端口上(bind)
  3. 将套接字设定为监听模式,准备接受客户端请求(listen)
  4. 阻塞等待客户端请求到来。当请求到来后,接受连接请求,返回一个新的对应于此客户端连接的套接字socketClient(accept)
  5. 用返回的套接字socketClient和客户端进行通信(IO流操作)
  6. 返回,等待另一个客户端请求(accept)
  7. 关闭套接字(close)

客户端流程:

  1. 创建客户端套接字(Socket)
  2. 向服务器发出连接请求(connect)
  3. 和服务器进行通信(IO流操作)
  4. 关闭套接字(close)

 

/**
 * 服务器端
 */
public class Server {

    public static void main(String[] args) throws IOException {
        // 创建一个ServerSocket,用于监听客户端的连接请求
        ServerSocket serverSocket = new ServerSocket();
        serverSocket.bind(new InetSocketAddress("127.0.0.1", 8000));

        // 使用循环不断地接受来自客户端的连接
        while (true) {
            Socket socket= serverSocket.accept();

            // IO流交互通信
            PrintStream printStream = new PrintStream(socket.getOutputStream(), true, "UTF-8");
            printStream.println("服务器说:" + socket.getInetAddress() + ",来了老弟");

            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            System.out.println("来自客户端的信息:" + in.readLine());

            // 关闭
            printStream.close();
            in.close();
            socket.close();
        }
        
    }
}

/**
 * 客户端的代码
 */
public class Client {
    public static void main(String[] args) throws IOException {
        // 创建一个Socket,向服务器发出连接请求
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress("127.0.0.1", 8000));

        // IO流交互通信
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
        System.out.println("来自服务器的信息:" + reader.readLine());

        PrintStream ps = new PrintStream(socket.getOutputStream(), true, "UTF-8");
        ps.println("客户端向你问好");

        // 关闭
        reader.close();
        socket.close();
    }
}

 

 

—— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。