IP地址标识Internet上的计算机,端口号标识正在计算机上运行的进程(程序)。端口号与IP地址的组合得出一个网络套接字(Socket),端口号被规定为一个16的整数0~65535。其中0~1023被预先定义的服务通信占用(如telnet占用端口23,http占用端口80等)。除非我们需要访问这些特定服务,否则就应该使用1024~65535这些端口中的某一个进行通信,以免发生端口冲突。当两个程序需要通信时,它们可以通过使用Socket类建立套接字对象并连接在一起。比如,有人让你去“中山广场邮局”,你可能反问“我去做什么”,因为他没告诉你“端口”,你不知道去处理何种业务。他说“中山广场邮局,8号窗口”,那么你到达地址“中山广场邮局”,找到“8号窗口”,就知道8号窗口处理特快专递业务,而且必须有个先决条件,就是你到达“中山广场邮局8号窗口”,该窗口必须有一位业务员在等待客户,否则就无法建立通信业务。
1.套接字连接
所谓套接字连接,就是客户端的套接字对象和服务器端的套接字对象通过输入输出流连接在一起,现在我们分3个步骤来说明套接字连接的基本模式。
①服务器建立ServerSocket对象。
ServerSocket对象负责等待客户端请求建立套接字连接,类似邮局某个窗口的业务员。也就是说,服务器必须事先建立一个等待客户请求(什么请求呢?建立套接字连接的请求) 的ServerSocket对象,ServerSocket的否则方法如下:
ServerSocket(int port)
port是一个端口号,port必须和客户请求的端口号相同。
当建立服务器套接字时可能发生IOException异常,因此要想下面那样建立接受客户的服务器套接字。
try { ServerSocket waitSocketConnection = new ServerSocket(PORT); } catch (IOException e) { e.printStackTrace(); }
当服务器的ServerSocket对象waitSocketConnection建立后,就可以使用方法accept()接收客户端的套接字连接请求,如下所示:
waitSocketConnection.accept();
接收客户端的套接字也可能发生IOException异常,因此要像下面那样建立接收客户端的套接字 :
try { Socket socketAtServer = waitSocketConnection.accept(); } catch (IOException e) { e.printStackTrace(); }
所谓接收客户的套接字请求,就是accept()方法会返回一个Socket对象socketAtServer(服务器端的套接字对象)。但是,accept()方法不会立刻返回,该方法会堵塞服务器端当前线程的执行,知道有客户端请求建立套接字连接。也就是说,如果没有客户请求建立套接字连接,那么下述代码中的“System.out.println("ok");”总不会被执行:
try { Socket socketAtServer = waitSocketConnection.accept(); System.out.println("ok"); } catch (IOException e) { e.printStackTrace(); }
注:ServerSocket对象可以调用setSoTimeout(int timeout)方法设置超时值(单位是毫秒),timeout是一个正直。当ServerSocket对象调用accept()方法堵塞的时间一旦超过timeout时,将触发SocketTimeoutException。
②客户端创建Socket对象。
客户端程序可以使用Socket类创建对象,Socket的构造方法如下:
socket(String host, int port)
参数host是服务器的IP地址,port是一个端口号。
创建Socket对象可能发生IOException异常,因此要像下面那样建立到服务器的套接字连接:
try { Socket socketAtClient = new Socket("localhost", 4431); } catch (Exception e) { e.printStackTrace(); }
客户端建立socketAtClient对象的过程就是向服务器端发出套接字连接请求,如果服务器端相应的端口上有ServerSocket对象正在使用accept()方法等待客户,那么双方的套接字对象socketAtClient和socketAtServer就都诞生了。
也可以使用Socket类不带参数的构造方法
public Socket()
创建一个套接字对象,该对象不请求任何连接。该对象再调用
public void connect(SocketAddress endpoint) throws IOException
请求与参数SocketAddress指定地址的套接字建立连接。为了使用connect()方法,可以使用SocketAddress的子类InetSocketAddress创建一个对象。InetSocketAddress的构造方法如下:
public InetSocketAddress(InetAddress addr, int port);
③流连接。
客户端和服务器端的套接字对象诞生以后,还必须进行输入/输出流的连接。服务器端的这个Socket对象(socketAtServer)使用getOutputStream()获得的输出流 将指向客户端Socket对象socketAtClient使用方法getInputStream()获得的那个输入流。同样,服务器端的这个Socket对象(socketAtServer)使用方法getInputStream()获得的输入流 指向客户端Socket对象(socketAtClient)使用方法getOutputStream()获得的那个输出流。因此,当服务器向这个输出流写入信息时,客户端通过相应的输入流就能读取,反之亦然,如下图:
需要注意的是,从套接字连接中读取数据与从文件中读取数据有着很大的不同,尽管二者都是输入流。从文件中读取数据是,所有的数据都已经在文件中了。而使用套接字连接时,可能在另一端数据发送出来之前,就已经开始试着读取了,这就会堵塞本线程,直到该读取方法成功读取到信息,本线程才继续执行后续的操作。
连接建立后,服务器端的套接字对象调用getInetAddress()方法可以获取一个InetAddress对象,该对象含有客户端的IP地址和域名。同样,客户端的套接字对象调用getInetAddress()方法可以获取一个InetAddress对象,该对象含有服务器端的IP地址和域名。
2.套接字关闭
套接字调用close()方法可以关闭双方的套接字连接,只要一方关闭连接,就会导致对方发生IOException异常。
简单例子(客户端与服务器端通信):
服务器端的代码如下:
package com.hsp.socket; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { ServerSocket waitSocketConnection = null; Socket socketAtServer = null; DataInputStream in = null; DataOutputStream out = null; try { //中山广场邮局(本地),8号窗口(4431)的业务员 waitSocketConnection = new ServerSocket(4431);//端口号 //accept方法会引起堵塞,直到接收到客户端的套接字连接请求 //业务员时刻准备接待客户,直到业务员离开(waitSocketConnection.close()) socketAtServer = waitSocketConnection.accept(); in = new DataInputStream(socketAtServer.getInputStream()); out = new DataOutputStream(socketAtServer.getOutputStream()); while(true){ int m = 0; m = in.readInt(); System.out.println("test2:"+m); out.writeUTF("你说的数对应的数字是:"+m); System.out.println("服务器收到:"+m); Thread.sleep(500);//当前线程休眠500毫秒 } } catch (Exception e) { e.printStackTrace(); } } }
客户端的代码如下:
package com.hsp.socket; import java.io.DataInputStream; import java.io.DataOutputStream; import java.net.Socket; public class Client { public static void main(String[] args) { String s = null; Socket socketAtClient = null; DataInputStream in = null; DataOutputStream out = null; int i = 0; try { //建立一位客户(该客户与中山广场邮局,8号窗口的业务员建立联系) //创建客户的过程就是与业务员建立连接的过程。如果业务员不存在,则无法建立连接,并且会抛出异常 socketAtClient = new Socket("localhost", 4431); in = new DataInputStream(socketAtClient.getInputStream()); out = new DataOutputStream(socketAtClient.getOutputStream()); while(true){ i = (i+1)%128; System.out.println("test client"+i); out.writeInt(i); s = in.readUTF(); System.out.println("客户端收到:"+s); Thread.sleep(500); } } catch (Exception e) { e.printStackTrace(); } } }