简单说一些常见的协议所属的OIS层:
应用层:远程登录协议Telnet、文件传输协议FTP,超文本传输协议HTTP,域名服务DNS,简单邮件传输协议SMTP,邮局协议POP3等
传输层:传输控制协议TCP—-面向连接的可靠传输协议,用户数据报协议UDP—–是无连接的,不可靠的传输协议
网络层:网际协议IP,Internet互联网控制报文协议ICMP、Internet组管理协议IGMP
TCP:Transfer Control Protocol面向连接的可靠传输协议
发送方和接收方需要建立连接。双方之间的通信通过socket
一方的socket(通常是Server Socket)等待建立连接,另一方的socket可以要求进行连接。一旦两个socket连接起来,双方就可以进行双向的数据传输,双方都可以进行发送和接受数据的操作
传输的数据大小没有限制
HTTP、FTP、Telnet等应用都需要这种可靠的通信通道
UDP:User Datagram Protocol用户数据报协议。
无连接的协议,每个数据包都是一个独立的信息,包括完整的源地址或目的地址。
在网络上以任何可能得路径传往目的地,因此是否到达目的地,到达目的地的时间及内容的正确性都是不能被保证的;发送的内容可能无序
传输数据的大小有限制,每个被传输的数据报必须限定在64KB之内
IP:用来标识网络上的计算机
端口号:用来指明该计算机的应用程序。16位数字表示,范围是0~65535,1024以下的端口号保留给预定义的服务。如,http使用80端口
Java中,通过java.net包中的类,Java程序能够使用TCP或UDP协议在互联网上进行通讯
URL:Uniform Reosurce Locator统一资源定位符,表示Internet上某一资源的地址,通过URL我们可以访问Internet上的各种网络资源
URL:协议标识符—-HTTP,FTP,File+资源名字—主机名,文件名,端口号,引用
eg:http://java.su.com:80/docs/books/tutorial/index.html#DOWN
Java中,可以创建表示URL地址的URL对象。
通过该URL对象,可以获得URL对象的各个属性及获得URL的实际比特或内容消息。
/**
getDefaultPort(): 返回默认的端口号。
getFile(): 获得URL指定资源的完整文件名。
getHost(): 返回主机名。
getPath(): 返回指定资源的路径。
getPort(): 返回端口号,默认为-1。
getProtocol(): 返回表示URL中协议的字符串对象。
getRef(): 返回URL中的HTML文档标记,即#号标记。
getUserInfo: 返回用户信息。
toString: 返回完整的URL字符串。
*/
public class UrlTest1 {
public static void main(String[] args) throws MalformedURLException {
URL url = new URL("http://java.su.com:80/docs/books/tutorial/index.html#DOWN");
System.out.println("URL 是 " + url.toString());
System.out.println("协议是 " + url.getProtocol());
System.out.println("文件名是 " + url.getFile());
System.out.println("主机是 " + url.getHost());
System.out.println("路径是 " + url.getPath());
System.out.println("端口号是 " + url.getPort());
System.out.println("默认端口号是 "+url.getDefaultPort());
System.out.println("引用 "+url.getRef());
}
}输出结果:
URL 是 http://java.su.com:80/docs/books/tutorial/index.html#DOWN
协议是 http
文件名是 /docs/books/tutorial/index.html
主机是 java.su.com
路径是 /docs/books/tutorial/index.html
端口号是 80
默认端口号是 80
引用 DOWN
/**
*通过URL对象获得URLConnection对象,代表应用程序和URL之间的通信连接,此类的实例可用于读取和写入此URL引用的资源
*/
public class UrlTest2 {
public static void main(String[] args) throws IOException {
URL url = new URL("https://www.baidu.com");
//获得URLConnection对象
URLConnection conn = url.openConnection();
//获得读取该URL引用资源的字节输入流
InputStream is = conn.getInputStream();
/**
* 也可以直接通过URL对象获得引用资源的读取通道---字节输入流
* InputStream is = url.openStream();
* 也可以利用缓冲字符流读取数据,更方便
* BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
*/
FileOutputStream fos = new FileOutputStream("d:/aa.html");
byte[] buffer = new byte[2048];
int len;
while(-1 != (len = is.read(buffer))){
fos.write(buffer,0,len);
}
fos.close();
is.close();
}
}Java中InetAddress类封装数字式的IP地址和该地址的域名。
InetAddress类没有明显的构造函数。生成该对象,运用一个可用的工厂方法。
/**
*均可能会抛出异常UnknownHostException
*1.返回象征本地主机的InetAddress对象
*2.返回一个传给它的主机名的InetAddress
*3.返回代表有一个特殊名称分解的所有地址的InetAddress类数组
/
InetAddress addr1 = InetAddress.getLocalHost();
InetAddress addr2 = InetAddress.getByName("www.baidu.com");
InetAddress[] addrs = InetAddress.getAllByName("www.sohu.com");网络通信的实现依赖于套接字Socket
Socket:连接运行在网络上的两个程序间的双向通讯的端点
通俗理解来说,就好比两个人打电话,套接字就扮演了手机的角色,作为两个人可以打电话的工具或者说是媒介。
对于TCP
使用Socket进行网络通信的过程:
1.服务器将套接字绑定到特定的端口,监听客户端的连接请求
2.客户端根据服务器程序所在的主机名和端口号发出连接请求
3.如果一切正常,服务器接收连接请求,并获得一个新的绑定到不同端口地址的套接字。原来的套接字继续监听是否有其他客户端的请求
4.客户端和服务器端通过读写套接字进行通讯
Java中如何实现呢?
使用ServerSocket和Socket实现服务器端和客户端的Socket通信
Step:
1.建立Socket连接
2.获得输入/输出流
3.读/写数据
4.关闭输入/输出流
5.关闭socket
//服务端
public class TcpServer {
public static void main(String[] args) throws IOException {
//建立ServerSocket监听客户端的Socket连接请求
ServerSocket ss = new ServerSocket(5000);
//监听是否有客户端的请求,否则阻塞等待,不往下执行
Socket s = ss.accept();
//获得输入流,读取客户端发送的数据
InputStream is = s.getInputStream();
/*疑问:至今未解,为何当利用如下代码读取数据的时候,就无法实现向客户端发送数据呢?循环跳出也不会往下执行,这是为什么?如果有经过的大神,希望可以解答一下~
byte[] buffer = new byte[1024];
int len;
while(-1 != (len = is.read(buffer))){
String str = new String(buffer,0,len);
System.out.println(str);
}*/
byte[] buffer = new byte[1024];
int len = is.read(buffer);
String str = new String(buffer,0,len);
System.out.println(str);
//获得输出流,向客户端发送数据
OutputStream os = s.getOutputStream();
os.write("hello client".getBytes());
//关闭输入、输出流及Socket
os.close();
is.close();
s.close();
}
}
//客户端
public class TcpClient {
public static void main(String[] args) throws IOException {
//建立连接指定地址和端口的Socket
Socket s = new Socket("10.170.34.89",5000);
//获得输出流,向服务器端发送数据
OutputStream os = s.getOutputStream();
os.write("hello server".getBytes());
//获得输入流,读取服务器端发来的数据
InputStream is = s.getInputStream();
byte[] buffer = new byte[1024];
int len = is.read(buffer);
String str = new String(buffer,0,len);
System.out.println(str);
}
}上述只是一个客户端和服务端建立Socket连接并进行收发数据的小Demo,服务器先读客户端发来的数据,再向客户端发送数据;而客户端先向服务器端发送数据,再读取服务器端发来的数据。但是在实际应用中的客户端和服务端并不是这样的模型,应该是服务器端和客户端之间的数据传输是任意的,它们可以随时收发数据。一个线程是满足不了上述要求的,这就需要我们通过多个线程来是实现随时收发数据。对于服务端来说,有读取数据的线程,也有发送数据的线程,只要来一个Socket连接,那么我们就分别创建基于该Socket的一个读线程和一个写线程,分别去读取数据和发送数据。对于客户端来说,也是一样的,有读取数据的线程,也有发送数据的线程,通过开启读写数据的线程从而实现随时收发数据。
服务端:
public class ServerMain {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(4000);
//服务端一直处于监听状态,只要有客户端发来Socket连接请求,就创建新的读写线程与该客户端进行通信
while(true){
Socket socket = ss.accept();
new ServerInputStreamThread(socket).start();
new ServerOutputStreamThread(socket).start();
}
}
}/**
* 服务端的发送数据线程
*/
public class ServerOutputStreamThread extends Thread{
private Socket socket;
public ServerOutputStreamThread(Socket socket){
this.socket = socket;
}
public void run(){
try {
OutputStream os = socket.getOutputStream();
//一直处于发送数据的状态,只要有用户输入,就将数据发送
while (true){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
os.write(str.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}/**
* 服务端的读取数据线程
*/
public class ServerInputStreamThread extends Thread {
private Socket socket;
public ServerInputStreamThread(Socket socket){
this.socket = socket;
}
public void run(){
try {
InputStream is = socket.getInputStream();
//一直处于等待读取数据的状态,只要客户端有数据发来,就将数据读取
while(true){
byte[] buffer = new byte[1024];
int len = is.read(buffer);
String str = new String(buffer,0,len);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}上述Demo在只有一个客户端的时候,服务器和客户端可以正常通信,但是当我们开启两个客户端的时候,两个客户端向服务器发送数据没有任何问题,因为每个客户端发起Socket连接请求的时候,都指定了IP和端口号。但是当服务器向客户端发送数据的时候,出现了问题,此时服务器发送的数据应该到哪个客户端呢?
这是因为我们都是在本地模拟的客户端和服务端,虽然开启了两个程序来模拟两个客户端,但是每个客户端向服务端发起请求的时候,服务端分别为“两个客户端”创建了Socket对象,由于实际上两个客户端的IP和端口号是一致的,所以在服务端向客户端发送消息的时候,由于两个Socket包含的客户端的信息一致,所以发送消息出现了异常。但是在实际应用场景中,客户端是在不同的计算机上,所以并不会出现这种收发消息异常的情况。因为每当有客户端发起Socket连接请求的时候,服务端都会创建一个Socket对象,并将该对象传入读写线程内,实现针对该客户端的读写操作。
对于UDP
同样也使用Socket,只不过UDP发送的数据报是网上传输的独立数据包,Java中使用DatagramPacket类来表示数据报对象,DatagramSocket类利用UDP协议来实现客户与服务器的Socket
send()发送数据报
receive()接收数据报
对于UDP协议来说,服务端和客户端的执行程序差别不是很大。
Step:
1.创建Socket对象
2.创建数据报DatagramPacket对象
3.读/写数据
4.关闭Socket对象
//视为客户端
public class UdpDemo1 {
public static void main(String[] args) throws Exception {
//创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();
String str = "hello world";
//创建要发送的DatagramPacket数据报,其中包含要发往的目标的IP和端口号
DatagramPacket packet = new DatagramPacket(str.getBytes(),str.length(), InetAddress.getByName("localhost"),4000);
//发送数据报
socket.send(packet);
byte[] buffer = new byte[1024];
//创建一个空的数据报用于接受消息
DatagramPacket packet2 = new DatagramPacket(buffer,buffer.length);
//接受数据报
socket.receive(packet2);
String recev = new String(packet2.getData(),0,packet2.getLength());
System.out.println(recev);
socket.close();
}
}//视为服务端
public class UdpDemo2 {
public static void main(String[] args) throws IOException {
//创建DatagramSocket对象,绑定主机的端口号
DatagramSocket socket = new DatagramSocket(4000);
byte[] buffer = new byte[1024];
//创建空的数据报用于接收数据
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
//接收数据报
socket.receive(packet);
String recev = new String(packet.getData(),0,packet.getLength());
System.out.println(recev);
String str = "welcome to Java World";
//根据接收到的数据报中含有的IP地址和端口号信息创建要发送的数据报对象
DatagramPacket packet2 = new DatagramPacket(str.getBytes(),str.length(),packet.getAddress(),packet.getPort());
//发送数据报
socket.send(packet2);
socket.close();
}
}上述Demo中的两个main方法,一方只能是先收数据,再根据收到数据的IP和Port发送数据,而另一方只能是先发数据,然后再收数据。就像之前TCP的多线程Demo一样,双方之间的收发数据应该是任意的,随时的,而不是需要一方去等待。
UDP是无状态的,之前的做的TCP接到客户端请求后马上做一个线程,将连接对象Socket传递进去进行处理!但是UDP的话是没有连接对象的,只要消息包的概念!理想状况是根据客户端的地址来创建发送消息的线程,以下Demo中模拟的时候收到的消息包就直接做一个线程进行处理了。
服务端:
/**
* 模拟的时候收到的消息包就直接做一个线程进行处理了,理想的话应该是根据客户端的地址来创建线程
*/
public class ServerMain {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(4000);
while(true){
byte[] data=new byte[1024];
DatagramPacket packet=new DatagramPacket(data,data.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength()));
new ServerSendThread(socket,packet).start();
}
}
}
/**
* 服务端发送数据报线程
*/
public class ServerSendThread extends Thread {
private DatagramPacket packet;
private DatagramSocket socket;
public ServerSendThread(DatagramSocket socket,DatagramPacket packet){
this.socket = socket;
this.packet = packet;
}
public void run(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(true){
try {
String in = br.readLine();
DatagramPacket sendPacket = new DatagramPacket(in.getBytes(),in.length(),packet.getAddress(),packet.getPort());
socket.send(sendPacket);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}客户端:
public class ClientMain {
public static void main(String[] args) throws SocketException, UnknownHostException {
DatagramSocket socket = new DatagramSocket();
new ClientSendThread(socket).start();
new ClientReceiveThread(socket).start();
}
}
/**
* 客户端发送数据报线程
*/
public class ClientSendThread extends Thread {
private DatagramSocket socket;
public ClientSendThread(DatagramSocket socket){
this.socket = socket;
}
public void run(){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while(true){
try {
String in = br.readLine();
//设置数据报发往的IP和端口号
DatagramPacket packet = new DatagramPacket(in.getBytes(),in.length(), InetAddress.getLocalHost(),4000);
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 客户端接收数据报线程
*/
public class ClientReceiveThread extends Thread {
private DatagramSocket socket;
public ClientReceiveThread(DatagramSocket socket){
this.socket = socket;
}
public void run(){
byte[] buffer;
while(true){
buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
try {
socket.receive(packet);
String str = new String(packet.getData(),0,packet.getLength());
System.out.println(str);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}