详解Java网络编程

  端口和套接字

  当网络中的两台计算机进行通信时,除了确定计算机在网络中的IP外,还需要确定计算机中的一个端口,端口并不是实际的物理设备,它是一个应用程序,这个应用程序来负责两台计算机的通信。

  一个IP标识了一台主机(服务器),主机可以提供多种服务,如web服务、ftp服务、远程桌面等。主机的每个服务都会等待客户端的连接,客户端如何区别这些服务呢?这就需要端口来区分了。

  端口被规定为一个在0~65535之间的整数,这个整数和提供服务的应用程序关联。如web服务一般是80端口,ftp服务一般是21端口、远程桌面一般是3389端口。

  在同一台计算机上端口号不能重复,否则,就会产生端口号冲突。程序员或计算机管理员在分配端口号时,需要遵循下面的规则:

  (1)1~1023之间的端口号,是由ICANN来管理的,不能分配给用户自己的应用程序;

  (2)1024~5000一般被TCP/IP程序作为临时端口号使用;

  (3)分配的端口号不能发生冲突;

  (4)应分配数值大于5000的端口号给用户开发的应用程序。

  TCP用主机的IP加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口。套接字用(IP地址:端口号)表示,区分不同应用程序进程间的网络通信和连接,套接字主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。

  在Java语言中,使用Socket类来创建套接字,使用Socket类在两台计算机之间进行通信时,是有主次之分的,一个称为服务器程序,一个称为客户端程序。在服务器端使用ServerSocket类创建套接字,主要是监听指定的端口,等待客户端的连接;在客户端使用Socket类创建套接字,用于连接服务器端的套接字。服务器端和客户端的套接字的IP地址和端口号要一致。

  在服务器端创建ServerSocket对象,并绑定监听端口。调用ServerSocket对象的accept()方法监听客户端的请求。与客户端建立连接后,它会返回一个已连接的Socket对象,并通过输入流读取客户端发送的请求信息,然后通过输出流向客户端发送响应信息,最后关闭socket及相关资源。

  在客户端创建Socket对象,需要指定连接服务器的地址和端口号,和服务器建立连接后,通过输出流向服务端发送请求信息,然后通过输入流获取服务器的响应信息,最后关闭socket及相关资源。

  构建TCP服务器

  在客户端和服务器端通信模式中,可以使用ServerSocket对象来构建TCP服务端。服务端需要创建监听特定端口的ServerSocket,ServerSocket负责接收客户连接请求,并生成与客户端连接的Socket。

  ServerSocket类的构造方法说明如下:

  ServerSocket()

  该构造方法创建一个未绑定的服务器套接字。

  ServerSocket(int port)

  该构造方法创建绑定到指定端口的服务器套接字。参数port为指定的端口号。

  ServerSocket(int port, int backlog)

  该构造方法创建服务器套接字并将其绑定到具有指定backlog的指定本地端口号。参数port为端口号,参数backlog为请求传入连接队列的最大长度。如果连接请求在队列已满时到达,则连接将被拒绝。

  ServerSocket(int port, int backlog, InetAddress bindAddr)

  该构造方法使用指定的端口、侦听backlog和要绑定到的指定的IP地址创建服务器。参数bindAddr为指定IP地址的InetAddress对象。

  ServerSocket类的常用方法说明如下:

  Socket accept()

  该方法用于侦听与此套接字的连接请求,并接收该请求,该方法将阻塞,直到建立连接。该方法将返回一个新的套接字。

  InetAddress getInetAddress()

  该方法返回此服务器套接字的本地地址。如果套接字在关闭前已绑定,则此方法将在套接字关闭后继续返回本地地址。

  void bind(SocketAddress endpoint)

  该方法将服务器套接字绑定到特定地址(IP地址和端口号)。如果地址为空,那么系统将获取一个临时端口和一个有效的本地地址来绑定套接字。参数endpoint是SocketAddress类型,指定了要绑定的IP地址和端口号。

  boolean isBound()

  该方法返回服务器套接字的绑定状态。

  netAddress getInetAddress()

  该方法返回此服务器套接字的本地地址。

  int getLocalPort()

  该方法返回此套接字侦听的端口号。

  void close()

  该方法关闭该套接字。关闭该套接字后,当前在accept()塞的任何线程都将抛出SocketException。

  在服务器端创建ServerSocket对象,并绑定监听端口。调用ServerSocket对象的accept()方法监听客户端的请求。与客户端建立连接后,通过输入流读取客户端发送的请求信息,然后通过输出流向客户端发送响应信息,最后关闭socket及相关资源。

  案例1:编写一个TCP服务端程序。建立TcpServer类,在TcpServer类中创建套接字,侦听指定的端口,等待连接请求。

  在PCoreUnit11项目新建tcp包,在tcp包下新建TcpServer类。代码如下:

  packagetcp;import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.PrintStream;import java.net.InetAddress;import java.net.ServerSocket;import java.net.Socket;/*** @ClassName: TcpServer* @Description: 网络编程(构建TCP服务器)案例1* @author 编程训练营* @date**/publicclass TcpServer {// 定义ServerSocket对象 ServerSocket serverSocket = null; // 定义端口号int port; // 构造方法public TcpServer() {this.port = 9099; } /** * @Title: getIp * @Description: 获取服务器的IP地址 * @param@return 参数 * @return String 返回类型 * @throws */public String getIp() {if( null != serverSocket ) { InetAddress address = serverSocket.getInetAddress();return address.getHostAddress(); }return ""; * @Title: init * @Description: 初始化和启动服务器 * @param 参数 * @return void 返回类型publicvoid init() { System.out.println("服务器启动...");try { /** * 创建一个ServerSocket,这里可以指定连接请求的队列长度 new ServerSocket(port,3) * 意味着当队列中有3个连接请求时,如果客户端再请求连接,就会被服务器拒绝 */ serverSocket = new ServerSocket(port, 3); // 服务器循环监听连接请求while (true) { // 从请求队列中取出一个客户端的连接请求 Socket client = serverSocket.accept(); // 建立一个新线程处理这次连接new HandlerThread(client); } } catch (Exception e) { System.out.println("服务器异常: " + e.getMessage()); * @ClassName: HandlerThread * @Description: 客户端请求线程处理类 * @author 编程训练营 * @date *privateclass HandlerThread implements Runnable { // 定义socket Socket socket; // 构造方法public HandlerThread(Socket socket) {this.socket = socket; // 启动线程new Thread(this).start(); /** * <p> * Title: run * </p> * Description: * * @see java.lang.Runnable#run() */ @Overridepublicvoid run() {try { // 读取客户端数据 BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 这里要注意和客户端输出流的写方法对应,否则会抛 EOFException String clientInputStr = input.readLine(); // 处理客户端数据 System.out.println("客户端发过来的内容:" + clientInputStr); // 向客户端回复信息 PrintStream out = new PrintStream(socket.getOutputStream()); System.out.print("请输入: "); // 发送键盘输入的一行 String s = new BufferedReader(new InputStreamReader(System.in)).readLine(); out.println(s); out.close(); input.close(); } catch (Exception e) { System.out.println("服务器 run 异常: " + e.getMessage()); } finally {if (socket != null) {try { socket.close(); } catch (Exception e) { socket = null; System.out.println("服务端 finally 异常:" + e.getMessage()); }}}}}}

  编写TCP服务端程序的主要步骤如下:

  实例化ServerSocket对象,创建套接字;调用ServerSocket对象的accept()方法监听端口;accept()方法监听到连接请求后,会返回Socket对象,accept()是堵塞方法,如果没有客户端的连接请求,方法后面的代码不会执行;建立一个新的线程,利用I/O流与客户端进行通信,通信完成后,关闭Socket。在tcp包下新建TcpServerTest测试类,启动服务器。代码如下:

  * @ClassName: TcpServerTest* @datepublicclassTcpServerTest { * @Title: main * @Description: Java程序入口main方法 * @param@param args 参数publicstaticvoid main(String[] args) { // 实例化TcpServer对象 TcpServer tcpserver = new TcpServer(); // 启动服务 tcpserver.init();}

  程序TcpServerTest实例化TcpServer对象,并调用TcpServer对象的init()方法启动服务器。

  TCP客户端用于连接TCP服务器,并向服务器发送请求,接收服务器返回的数据。客户端的套接字由Socket类创建。

  Socket类的主要构造方法说明如下:

  Socket(InetAddress address, int port)

  该构造方法创建流套接字并将其连接到指定IP地址处的指定端口号。

  Socket类的主要方法说明如下:

  void bind(SocketAddress bindpoint)

  该方法将将套接字绑定到bindpoint指定的IP地址和端口号。

  void connect(SocketAddress endpoint)

  该方法将此套接字连接到指定的IP地址和端口号,IP地址和端口号由endpoint指定。

  void connect(SocketAddress endpoint, int timeout)

  该方法将此套接字连接到指定的IP地址和端口号,IP地址和端口号由endpoint指定。参数timeout为连接超时时间。

  该方法关闭此套接字。

  案例1:编写TCP客户端程序。建立TcpClient类,连接前面课程创建的TCP服务,并输出服务器的返回数据到控制台。

  在tcp包下新建TcpClient类。代码如下:

  importjava.io.IOException;* @ClassName: TcpClient* @Description: 网络编程(构建TCP客户端)案例1publicclass TcpClient { // 定义一个套接字 Socket socket; // 定义IP地址 String ip;public TcpClient(String ip, int port) {this.ip = ip;this.port = port; * @Title: send @Description: 向TCP服务器发送请求 @param 参数 * @return void 返回类型 @throwspublicvoid send() { System.out.println("客户端正在连接服务器...");while (true) { // 实例化套接字,指定IP地址和端口号 // 套接字实例化后,会自动向服务器发送请求 socket = new Socket(ip, port); // 向服务器端发送数据 String str = new BufferedReader(new InputStreamReader(System.in)).readLine(); out.println(str); // 读取服务器端返回的数据 String ret = input.readLine(); System.out.println("服务器端返回过来的是: " + ret); // 如接收到 "OK" 则断开连接if ("OK".equals(ret)) { System.out.println("客户端将关闭连接");break; } System.out.println("客户端异常:" + e.getMessage()); } catch (IOException e) { System.out.println("客户端 finally 异常:" + e.getMessage()); }

  编写TCP客户端程序的主要步骤如下:

  (1)实例化Socket对象,创建套接字。套接字的IP地址需要和服务器的IP地址一致,如果TCP服务器没有指定IP地址,而是采用默认的IP地址,客户端套接字的IP地址可以是本地地址127.0.0.1,端口号必须一致。

  (2)应用I/O类向服务器端写入数据,发送请求;

  (3)应用I/O类读取服务端返回的数据;

  (4)通信完成后,关闭Socket。

  在tcp包下新建TcpClientTest测试类。代码如下:

  * @ClassName: TcpClientTestpublicclassTcpClientTest { // 实例化client对象 TcpClient client = new TcpClient("127.0.0.1",9099); // 向服务端发送请求 client.send();

  TcpClientTest程序实例化TcpClient对象,调用TcpClient对象的send()方法向服务器端发送请求。

  客户端和服务器端测试步骤:

  启动TCP服务器TcpServerTest;启动客户端程序TcpClientTest;在客户端程序控制台窗口输入hello,向服务器端发送输入的内容;服务器端控制台窗口回显客户端发送的内容;在服务器端控制台窗口输入OK,服务端向客户端返回在控制台输入的内容;客户端判断返回的内容是OK时,结束连接。程序执行结果如下图所示:

  举报/反馈