java网络程序设计初窥
一、网络编程基本概念
1.OSI与TCP/IP体系模型
2.IP和端口
解决了文章最开始提到的定位的问题。
IP在互联网中能唯一标识一台计算机,是每一台计算机的唯一标识(身份证);网络编程是和远程计算机的通信,所以必须先能定位到远程计算机;IP帮助解决此问题;一台计算机中可能有很多进程,具体和哪一个进程进行通信,这就得靠端口来识别;
IP和端口能唯一定位到需要通信的进程。这里的IP表示地址,区别于IP协议。在OSI体系还是TCP/IP体系中,IP协议位于网际层,来封装IP地址到报文中。
3.TCP和UDP协议
TCP 是Tranfer Control Protocol 的简称,是一种 面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便 在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
UDP 是User Datagram Protocol 的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
比较:
UDP:
- 每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
- UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
- UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:
- 面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接时间。
- TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的数据。
- TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
数据桢:
应用:
- TCP 在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
- UDP 操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
4.Socket
Socket是网络驱动层提供给应用程序编程接口和一种机制。我们可以把 Socket 比喻成是一个港口码头。应用程序只要把货物放到港口码头上,就算完成了货物的运送。对于接收方应用程序也要创建一个港口码头,只需要等待货物到达码头后将货物取走。
Socket 是在应用程序中创建的,它是通过一种绑定机制与驱动程序建立关系,告诉自己所对应的 IP 和 Port。在网络上传输的每一个数据帧,必须包含发送者的 IP 地址和端口号。创建完 Socket 以后,应用程序写入到 Socket 的数据,由 Socket 交给驱动程序向网络上发送数据,计算机从网络上收到与某个 Socket 绑定的 IP 和 Port 相关的数据后,由驱动程序再交给 Socket ,应用程序就可以从这个 Socket 中读取接收到的数据。网络应用程序就是这样通过 Socket 发送和接收的。
Socket数据发送过程:
Socket数据接收过程:
5.常用应用层协议
二、TCP通信
TCP网络程序设计是指利用Scoket类编写通信程序。
具体步骤:
(1)服务器程序创建一个ServerSocket(服务器端套接字),调用accept()方法等待客户连接。
(2)客户端程序创建一个Scoket,请求与服务器建立连接。
(3)服务器接收客户的连接请求,同时创建一个新的Socket与客户建立连接。服务器继续等待新的请求。
1.InteAddress类
Java中的InetAddress是一个代表IP地址的封装。IP地址可以由字节数组和字符串来分别表示,InetAddress将IP地址以对象的形式进行封装,可以更方便的操作和获取其属性。InetAddress没有构造方法,可以通过两个静态方法获得它的对象。
// 根据主机名来获取对应的InetAddress实例
InetAddress ip = InetAddress.getByName("www.baidu.com");
// 判断是否可达 System.out.println("baidu是否可达:" + ip.isReachable(2000));
// 获取该InetAddress实例的IP字符串 System.out.println(ip.getHostAddress());
// 根据原始IP地址(字节数组形式)来获取对应的InetAddress实例
InetAddress local = InetAddress.getByAddress(new byte []{127,0,0,1}); System.out.println("本机是否可达:" + local.isReachable(5000));
// 获取该InetAddress实例对应的全限定域名
System.out.println(local.getCanonicalHostName());
2.ServerSocket(重点)
ServerSocket用来表示服务器套接字。服务器套接字通过制定的端口来等待连接的套接字,它的主要功能是等待来自网络上的连接“请求”。它可通过制定的端口来等待连接的套接字。服务器套接字可以一次与一个套接字连接。如果多台客户机同时提出连接请求,服务器套接字会将请求连接的用户机存入到队列中,然后从中取出一个套接字,与服务器新建的套接字连接起来。若请求连接数大于最大的容纳数,则多出的连接请求被拒绝。队列默认大小是50.
代码如下:
try {
ServerSocket serverSocket1=new ServerSocket();//创建非绑定服务器的套接字
ServerSocket serverSocket2=new ServerSocket(1880);//创建绑定端口的套接字
ServerSocket serverSocket3 =new ServerSocket(1880,500);//创建绑定端口且指定最大长度的套接字
} catch (IOException e) {
e.printStackTrace();
}
accept侦听并接受到此套接字的连接。此方法在进行连接之前一直阻塞。
实例:
try {
ServerSocket serverSocket1=new ServerSocket(1880);//创建非绑定服务器的套接字
System.out.println("成功创建服务器套接字");
System.out.println("等待客户机连接");
Socket socket=serverSocket1.accept();//等待与客户机连接
System.out.println("与客户端建立起连接");
} catch (IOException e) {
e.printStackTrace();
}
3.Socket(重点)
分析:如果没有客户机请求连接,则与客户端建立起连接不会输出。
Socket类是套接字段。
直接实例:
客户端向服务端发送信息,服务端读取客户端发送的信息,并将读取的数据写入到输出流。
服务端程序:
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null; // 创建服务器端套接字
Socket clientSocket = null; // 创建客户端套接字
String str = null;
DataOutputStream out = null; // 创建DataOutputStream类对象
DataInputStream in = null; // 创建DataInputStream类对象
try {
serverSocket = new ServerSocket(4331); // 实例化ServerSocket对象
clientSocket = serverSocket.accept(); // 接收客户的套接字连接呼叫
in = new DataInputStream(clientSocket.getInputStream()); // 实例化DataInputStream对象
out = new DataOutputStream(clientSocket.getOutputStream()); // 实例化DataOutputStream对象
while (true) {
str = in.readUTF(); // 读取客户放入连接中的信息
out.writeUTF("hello,我是服务器"); // 通过输出流向线路中写信息
out.writeUTF(str);
System.out.println("服务器收到:" + str);
Thread.sleep(1000); // 线程休眠
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.*;
public class SocketUtill {
public static void main(String[] args) {
String str = null;
Socket clientSocket; // 创建客户端套接字
DataInputStream in = null; // 创建DataInputStream对象
DataOutputStream out = null; // 创建DataOutputStream对象
try {
clientSocket = new Socket("127.0.0.1", 4331); // 实例化Socket对象
in = new DataInputStream(clientSocket.getInputStream()); // 实例化DataInputStream对象
out = new DataOutputStream(clientSocket.getOutputStream()); // 实例化DataOutputStream对象
out.writeUTF("你好!!"); // 写数据
while (true) {
str = in.readUTF(); // 读取流中数据
out.writeUTF(((int) (Math.random() * 10) + 1) + ""); // 向流中写入0到10之间的随机数
System.out.println("客户端收到:" + str); // 输出信息
Thread.sleep(1000); // 休眠
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果如图:
三、UDP通信
基于UDP通信的基本模式如下:将数据打包,成为数据包,然后将数据包发往目的地。
接收别人发来的数据包,然后查看数据包。
下面是UDP程序的步骤:
发送数据包的步骤如下:
1.使用DatagramSocket()创建一个数据包套接字。
2.使用DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)创建要发送的数据包。
3.使用DatagramScoket类的send()方法发送数据包。
接收数据包步骤如下:
1.使用DatagramSocket()创建一个数据包套接字,绑定指定的端口。
2.使用DatagramPacket(byte[] buf, int length)创建字节数组来接收数据包。
3.使用DatagramPacket类的receive()方法,接收UDP包。
实例:在服务器端接收数据,再发送数据;在客户端实现发送数据,再接收数据。
服务端程序:
import java.net.*;
public class UDPServer {
public static void main(String[] args) {
DatagramSocket serverSocket;
try {
serverSocket = new DatagramSocket(6000);// 创建DatagramSocket对象
byte[] recvBuf = new byte[100]; // 创建字节数组
DatagramPacket recvPacket = new DatagramPacket(recvBuf,
recvBuf.length); // 创建DatagramPacket
serverSocket.receive(recvPacket); // 接收数据报
String recvStr = new String(recvPacket.getData(), 0, recvPacket
.getLength());
System.out.println("Hello!" + recvStr);
int port = recvPacket.getPort(); // 获取接收端口
InetAddress address = recvPacket.getAddress(); // 创建InetAddress实例
String sendStr = "服务器发送的信息";
byte[] sendBuf;
sendBuf = sendStr.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendBuf,
sendBuf.length, address, port); // 创建数据包
serverSocket.send(sendPacket); // 发送数据包
serverSocket.close(); // 关闭DatagramSocket对象
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.*;
import java.net.*;
public class UDPClient {
public static void main(String[] args) {
DatagramSocket clientSocket;
try {
clientSocket = new DatagramSocket();// 创建套接字对象
String sendStr = "你好,我是客户机";
byte[] sendBuf;
sendBuf = sendStr.getBytes();
InetAddress addr = InetAddress.getByName("127.0.0.1");
int port = 6000; // 指定端口
DatagramPacket sendPacket = new DatagramPacket(sendBuf,
sendBuf.length, addr, port); // 创建数据包
clientSocket.send(sendPacket); // 发送数据包
byte[] recvBuf = new byte[100];
DatagramPacket recvPacket = new DatagramPacket(recvBuf,
recvBuf.length); // 创建数据包对象
clientSocket.receive(recvPacket); // 接收信息
String recvStr = new String(recvPacket.getData(), 0, recvPacket
.getLength());
System.out.println("收到:" + recvStr);
clientSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}