网络编程是指编写运行在多个设备(计算机)的程序,这些设备都通过网络连接起来。
java.net 包中 J2SE 的 API 包含有类和接口,它们提供低层次的通信细节。你可以直接使用这些类和接口,来专注于解决问题,而不用关注通信细节。
前言
java.net包中提供了两种常见的网络协议支持:
- TCP:Transmission Control Protocol,传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通讯协议,TCP层位于IP层之上,应用层之下的中间层。TCP保障了两个应用程序之间的可靠通信,通常用于互联网协议,称之为TCP/IP。
- UDP:User Datagram Protocol,用户数据报协议,位于OSI模型的传输层,一个无连接的协议,提供了应用程序之间要发送数据的数据报,由于UDP缺乏可靠性的且属于无连接的协议,因此应用程序通常需要设计冗余措施,允许接受一些丢失、错误或者重复的数据包。
Socket编程
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
- 服务器server实例化一个ServerSocket对象,表示通过服务器上的端口通信。
- 服务器调用ServerSocket类的accept方法,该方法会阻塞等待,知道客户端连接到服务器server的给定端口。
- 服务器等待时候,客户端实例化一个Socket对象,指定服务器的名称和端口号请求连接。
- Socket类的构造函数会试图把客户端连接到指定的服务器和端口号,若通信建立,则在客户端创建一个socket对象能够和服务器进行通信。
- 服务器端,accept方法返回服务器上一个新的socket引用,这个socket会连接到客户端的socket。
连接建立之后,通过IO流进行通信,每一个socket都会有一个输出流和一个输入流,客户端的输出流会连接到server的输入流,而客户端client的输入流连接到server服务端的输出流。
TCP是一个双向的通信协议,因此数据可以通过两个数据流在同一个时间发送,下面会给出部分方法。
ServerSocket 类的方法
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
- public ServerSocket(int port) throws IOException:
创建绑定到特定端口的服务器套接字。 - public ServerSocket(int port, int backlog) throws IOException:
利用指定的backlog创建服务器套接字并且绑定到指定的本地端口号。 - public ServerSocket(int port, int backlog, InetAddress address) throws IOException:
使用指定端口、监听backlog和需要绑定到本地IP地址创建服务器。 - public ServerSocket() throws IOException:
创建非绑定服务器套接字。
下面给出ServerSocket的常用方法:
- public int getLocalPort():返回该套接字在其上侦听的端口号。
- public Socket accept() throws IOException:监听并且接受套接字的连接。
- public void setSoTimeout(int timeout):通过指定超时值启用或者禁用SO_TIMEOUT,毫秒单位。
- public void bind(SocketAddress host, int backlog):将ServerSocket绑定到特定地址(IP 地址和端口号)。
Socket类方法
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
- public Socket(String host, int port) throws UnknownHostException, IOException.:
创建一个流套接字并将其连接到指定主机上的指定端口号。 - public Socket(InetAddress host, int port) throws IOException:
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。 - public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.:
创建一个套接字并将其连接到指定远程主机上的指定远程端口。 - public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.:
创建一个套接字并将其连接到指定远程地址上的指定远程端口。 - public Socket():
通过系统默认类型的SocketImpl创建未连接的套接字。
下面在给出Socket的常用方法:
- public void connect(SocketAddress host, int timeout) throws IOException:把该套接字连接到服务器,指定一个超时数值。
- public InetAddress getInetAddress():返回套接字连接的地址。
- public int getPort():返回套接字连接到的远程端口。
- public int getLocalPort():返回套接字绑定的本地端口。
- public SocketAddress getRemoteSocketAddress():返回此套接字连接的端点的地址,如果未连接则返回 null。
- public InputStream getInputStream() throws IOException:返回此套接字的输入流。
- public OutputStream getOutputStream() throws IOException:返回套接字的输出流。
- public void close() throws IOException:关闭套接字。
InetAddress 类的方法
这个类表示互联网协议IP地址,给出常用方法:
- static InetAddress getByAddress(byte[] addr):
在给定原始 IP 地址的情况下,返回 InetAddress 对象。 - static InetAddress getByAddress(String host, byte[] addr):
根据提供的主机名和 IP 地址创建 InetAddress。 - static InetAddress getByName(String host):
在给定主机名的情况下确定主机的 IP 地址。 - String getHostAddress() :
返回 IP 地址字符串(以文本表现形式)。 - String getHostName() :
获取此 IP 地址的主机名。 - static InetAddress getLocalHost():
返回本地主机。 - String to String():
IP地址转换为String。
Server代码:
package javaNet;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketTest extends Thread {
private ServerSocket serverSocket;
public ServerSocketTest(int port) throws Exception {
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
}
@Override
public void run() {
while (true) {
try {
System.out.println("等待远程连接,端口号为:" + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("远程主机地址:" + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("谢谢连接我:" + server.getLocalSocketAddress() + "\nGoodbye!");
server.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int port = 8888;
try {
Thread t = new ServerSocketTest(port);
t.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Client代码:
package javaNet;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ClientSocket {
public static void main(String[] args) {
// TODO Auto-generated method stub
String serverName = "localhost";
int port = 8888;
try {
System.out.println("连接到主机:" + serverName + " 端口号:" + port);
Socket client = new Socket(serverName, port);
System.out.println("远程主机地址:" + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("Hello from " + client.getLocalSocketAddress());
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("服务器响应:" + in.readUTF());
client.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
同步和异步
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。
-
同步 : 自己亲自出马持银行卡到银行取钱(使用同步 IO 时,Java 自己处理IO 读写);
-
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO 时,Java 将 IO 读写委托给OS 处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS 需要支持异步IO操作API);
阻塞和非阻塞
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
- 阻塞 :ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
- 非阻塞 :柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器通知可读写时再继续进行读写,不断循环直到读写完成)