TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟网络链路,两端的程序就可以通过虚拟链路进行通信。Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流进行网络通信。

使用ServerSocket创建TCP服务器端

在两个通信实体之间并没有服务器端和客户端之分,但在两个通信实体没有建立虚拟链路之前,必须有一个通信实体先做出“主动姿态”,主动接收来自其他通信实体的连接请求。

Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。ServerSocket包含一个监听来自客户端请求的方法。

方法

Socket accept()

如果接收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket(每个TCP连接有两个Socket)否则该方法将一直处于等待状态,线程也被阻塞

ServerSocket所提供的构造器

ServerSocket(int port)

用指定的端口port来创建一个ServerSocket。该端口应该有一个有效的端口数值,即0~65535

ServerSoccket(int port,int backlog)

增加一个用来改变连接队列长度的参数backlog

ServerSocket(int port,int backlog,InetAddress localAddr)

在机器存在多个IP地址的情况下,允许通过localAddr参数来指定将ServerSocket绑定到指定的IP地址。

使用Socket进行通信

客户端通常可以使用Socket的构造器来连接到指定服务器,Socket通常可以使用如下两个构造器。

构造器

Socket(InetAddress/String remoteAddress,int port)

创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本地主机的默认IP地址,默认使用系统动态分配的端口

Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort)

创建连接到指定远程主机、远程端口的Socket,并指定本地IP地址和本地端口,适用于本地主机有多个IP地址的情形

当客户端、服务器端产生了对应的Socket之后,程序无须再区分服务器端、客户端,而是通过各自的Socket进行通信。Socket提供了两个方法来获取输入流和输出流。

方法

InputStream getInputStream()

返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据

OutputStream getOutputStream()

返回该Socket对象对应的输出流,让程序输出流向Socket中输出数据


代码示例,一个简单的通讯
服务端:

public class Server {

    public static void main(String[] args) throws IOException{

        //创建一个ServerSocket,用于监听客户端Socket的连接请求
        ServerSocket ss = new ServerSocket(30000);
        //采用循环换不断地接收来自客户端的请求
        while (true){
            //每当接收到客户端Socket的请求时,服务器端也对应产生一个Socket
            Socket s = ss.accept();
            //将Socket对应的输出流包装成PrintStream
            PrintStream ps = new PrintStream(s.getOutputStream());
            //进行普通的IO操作
            ps.println("您好,您收到了服务器的问候!");
            //关闭输出流,关闭Socket
            ps.close();
            s.close();
        }
    }
}

客户端:

public class Client {

    public static void main(String[] args) throws IOException{
        //其中“1227.0.0.1”代表本机的IP地址,因为服务端和客户端都是在本机运行
        Socket socket = new Socket("127.0.0.1", 30000);
        //将Socket对应的输入流包装成BufferedReader
        BufferedReader br = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));
        //进行普通IO操作
        String line = br.readLine();
        System.out.println("来自服务器的数据:" + line);
        //关闭输入流,关闭Socket
        br.close();
        socket.close();
    }
}

在实际应用中,程序可能不想让执行网络连接、读取服务器数据的进程一直阻塞,而是希望当网络连接、读取操作超过合理时间之后,系统自动认为操作失败,这个合理时间就是超时时长。(在指定时间内通信未到达,断开连接)

//Socket对象提供了一个setSoTimeout(int timeout)方法来设置超时时长
Socket s = new Socket("127.0.0.1", 30000);
//设置10秒之后即认为超时
s.setSoTimeouut(10000);

当Socket连接服务器超过指定时长,进行处理。

//创建一个无连接的Socket
Socket s = new Socket();
//让该Socket连接到远程服务器,如果经过10秒还未连接上,则认为连接超时
s.connect(new InetSocketAddress (host,port),10000);

两种超时的区别,第一种是读写超时,在已经连通,但在通信过程中,某条信息的传送超过指定时间,报错。第二种是链接超时,还未连通,连接时间超过指定时间,报错。