目前java网络编程中两个主要的问题:
(1)如何准确的定位网络上一台或多台的主机。
(2)如何在找到主机以后可靠高效的进行数据传输。
在TCP/IP协议中,IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一准确的定位到internet上的另外一台主机;而TCP层则是提供面向连接的可靠传输 (tcp)和面向无连接的不可靠传输(udp)的机制,这是我们网络编程需要关注的主要对象,IP层不需要我们去过多的关注。
目前比较流行的网络编程模型是客户端/服务器(C/S)结构:通信双方一方作为服务器等待用户从客户端提出请求并予以响应。客户端则在需要服务时像服务器提出服务请求。服务器一般作为守护进程始终在运行,监听网络端口,一旦有客户的请求,就会启动一个服务去响应客户,之后继续监听端口,使接下来的客户发出的请求也能够及时的得到响应。
2. 两类数据传输协议:TCP、UDP
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的保证可靠的传输协议。通过TCP协议传输的数据,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket必须建立连接,然后才能在TCP协议的基础上进行通信。当一个socket(通常是ServerSocket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,服务器和客户端都可以发送和接收数据。
UDP(User Datagram Protocol 用户数据报协议)是一种无连接的不可靠的传输协议。每一个数据报都是一个独立的信息,包括完整的源地址和目标地址,它在internet上移任何可能的路由路径传达到目标主机,到达目标主机的时间和内容的准确性都是不能够保证的。
比较:
TCP:1.面向连接的协议,在socket之间进行数据传输之前必须要建立连接,所以在tcp中需要连接时间。
2.TCP传输数据大小限制,一旦建立好连接,socket双方就可以按照一个统一的格式传输大的数据。
3.TCP是一个可靠的协议,它能够确保接收方完全准确无误地获取发送方发送的全部数据。
UDP:1.每个数据报中都给出了完整的地址信息(包括源地址和目标地址),因此传输数据不需要建立socket连接。
2.UDP传输数据有大小限制,每个传输的数据必须限定在64KB之内。
3.UDP是一个不可靠的协议,发送方所发送的数据不一定以相同的次序到达接收方,也不一定能够保证数据的准确性。
应用 :
TCP:在网络通信商有极强的生命力,例如文件传输(FTP)和远程连接(Telnet)都需要可靠传输不定长度的数据。但是另一方面,可靠的传输也是要付出代价的,对传输数据内容正确的校验必然会占用计算机的网络带宽和处理时间,因此TCP的传输效率不如UDP高。
UDP:操作简单,而且不需要建立连接和仅需较少的监护,因此运用于可靠性较高的局域网分散系统中的server/client程序中。例如视频会议系统,并不要求音视频数据的绝对正确,只要保证连贯性就可以了,在这种情况下使用UDP会更加合理一些。
3. 基于socket的java网络编程
(1)什么是socket?
网络上的两个程序通过一个双向的通讯实现数据的交换,这个双向链路的一端称为一个socket。socket通常用来实现服务器与客户端的连接。一个socket由一个主机IP地址和一个端口号唯一确定,socket是目前TCP/IP协议非常流行的编程界面。
(2)socket通信的过程
server端监听某个端口是否有请求连接,client端向server端发出Connect请求,server端向client端返回accept消息。一个消息就建立起来了。server端和client端都可以通过send和write等方法与对方进行通信。
socket的工作过程包含以下四个基本的步骤:
<1> 创建socket;
<2> 打开连接到socket的输入/输出流;
<3> 按照一定的协议对socket进行读/写操作;
<4> 关闭socket。
(3)如何创建socket
java在java.ne包中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。已经封装的很好,使用起来非常方便。其构造方法如下:
Socket(InetAddress address,int port);(最常用的一种)
Socket(InetAddress address,int port,boolean stream);
Socket (String host,int port);
Socket (String host,int port,boolean stream);
Socket (SocketImpl impl);
Socket (String host,int port,InetAddress localAddr,int localPort);
Socket (InetAddress address,int port,InetAddress localAddr,int localPort);
ServerSocket(int port);(最常用的一种)
ServerSocket(int port,int backlog);
ServerSocket(int port,int backlog,InetAddress bindAddr);
其中address、host和port分别是双向连接中另一方的IP地址、主机名和端口号,stream是指socket是流socket还是数据报socket,localPort表示本地主机的端口号,localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建Socket又可以用来创建ServerSocket。例如:
Socket client = new Socket(“127.0.01.”,80);
ServerSocket server = new ServerSocket(80);
*注意:在选择端口时,必须要小心。每个端口提供一种特定的服务,只有给出正确的端口,才能获取响应的服务。特别是0~1023端口为系统所保留的端口,例如21是telnet服务的端口,23是ftp服务的端口,80是http服务的端口,所以在我们写socket选择端口的时候,最好是选择一个大于1023的端口号,以免和系统保留的端口号造成冲突产生意想不到的后果。
在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建Socket或ServerSocket时必须捕获或抛出异常。
4. 一个简单的Client/Server程序
客户端程序:
import java.io.*;
import java.net.*;
public class TalkClient {
public static void main(String args[]){
try{
//向本机的5000端口发出客户请求
Socket socket = new Socket("127.0.01.",5000);
System.out.println("连接服务器成功!");
//由系统标准输入设备构造BufferedReader对象
BufferedReader sin= new BufferedReader(new InputStreamReader(System.in));
//由Socket对象得到输出流,并构造PrintWriter对象
PrintWriter os = new PrintWriter(socket.getOutputStream());
//由Socket对象得到输入流,并构造响应的BufferedReader对象
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readLine;
//从系统标准输入读入一行字符串
readLine = sin.readLine();
//如果标准输入的字符串为“bye”则停止循环
while(!readLine.equals("bye")){
os.println(readLine);
os.flush();
System.out.println("Client:"+readLine);
System.out.println("Server:"+is.readLine());
readLine = sin.readLine();
}
System.out.println("通信结束!");
os.close();
is.close();
socket.close();
}catch (Exception e) {
System.out.println("Error: "+e);
}
}
}
服务端程序:
import java.io.*;
import java.net.*;
public class TalkServer {
public static void main(String args[]){
try{
ServerSocket server =null;
try{
//创建一个ServerSocket在端口5000监听客户请求
server = new ServerSocket(5000);
System.out.println("监听端口成功!");
}catch (Exception e) {
System.out.println("can not listen to :"+e);
}
Socket socket=null;
try{
//使用accept()阻塞等待客户的请求,有默认客户,请求到来则产生一个Soclet对象,并继续执行
socket=server.accept();
}catch (Exception e) {
System.out.println("Error: "+e);
}
String line;
//由Socket对象得到输入流,并构造响应的BufferedReader对象
BufferedReader is =new BufferedReader(new InputStreamReader(socket.getInputStream()));
//由Socket对象得到输出流,并构造PrintWriter对象
PrintWriter os =new PrintWriter(socket.getOutputStream());
//由系统标准输入设备构造BufferedReader对象
BufferedReader sin =new BufferedReader(new InputStreamReader(System.in));
System.out.println("Client:"+is.readLine());
line = sin.readLine();
//如果标准输入的字符串为“bye”则停止循环
while(!line.equals("bye")){
os.println(line);
os.flush();
System.out.println("Server:"+line);
System.out.println("Client:"+is.readLine());
line=sin.readLine();
}
System.out.println("通信结束!");
os.close();
is.close();
server.close();
socket.close();
}catch (Exception e) {
// TODO: handle exception
System.out.println("Error: "+e);
}
}
}
*友情提示,若出现can not listen to :java.net.SocketException: Unrecognized Windows Sockets error: 0: JVM_Bind
Error: java.lang.NullPointerException
Error: java.lang.NullPointerException异常,则监听的端口被占用,建议更换其他端口。
程序运行结果: