网络通信协议
定义:
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规
则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完
成数据交换。
网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protoal传输控制协议/英特网互联协议),它是一个包括TCP协议
和IP协议,UDP协议(User Datagram Protocol)和其它一些协议的协议组。
IP地址和端口号
要想使网络中的计算机能够进行通信,必须为每台计算机指定一个标识号,通过这个标识号来指定接受数据的计算机或者发送数据的计算机。
在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机。
通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的。端口号
是用两个字节(16位的二进制数)表示的,它的取值范围是0~65535,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使
用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。
InetAdders
InetAdderss类:
用于封装一个IP地址,并提供了一系列与IP地址相关的方法
演示:
public static void main(String[] args) throws Exception { InetAddress local = InetAddress.getLocalHost(); InetAddress remote = InetAddress.getByName("www.oracle.cn"); System.out.println("本机的IP地址:" + local.getHostAddress()); System.out.println("oracle的IP地址:" + remote.getHostAddress()); System.out.println("oracle的主机名为:" + remote.getHostName()); }
UDP与TCP协议
UDP协议
概念:
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,
发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。(例如看电视,电视不会因为你不在电视前面就不
播放)。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数
据包,也不会对接收结果产生太大影响。
但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。
UDP传输数据被限制在64K以内。
TCP协议
TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。
第一次握手,客户端向服务器端发出连接请求,等待服务器确认。
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手,客户端再次向服务器端发送确认信息,确认连接。
由于TCP协议的面向连接特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议,例如在下载文件时,
如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议。
UDP通信
DatagramPacket
DatagramPacket类的实例对象就相当于一个集装箱,用于封装UDP通信中发送或者接收的数据。
构造方法:
接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
常用方法:
DatagramSocket
DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来。然而运输货物只有“集装箱”是不够的,还需要有码头。在程序中
需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类。DatagramSocket类的作用就类似于码头,使用这个类的实例
对象就可以发送和接收DatagramPacket数据包。
构造方法:
常用方法:
演示:
发送端:
//发送端 public class UDPsend { public static void main(String[] args) throws IOException { //1、打包 //明确数据 byte[] bytes="你好吗".getBytes(); //明确目的地的IP地址 InetAddress inet=InetAddress.getByName("127.0.0.1"); //装包 DatagramPacket dp=new DatagramPacket(bytes, bytes.length,inet,6666); //2、创建快递公司 DatagramSocket ds=new DatagramSocket(); //3、发送 ds.send(dp); //释放资源 ds.close(); } }
接收端:
//接收端 public class UDPrecieve { public static void main(String[] args) throws IOException { //明确端口号 DatagramSocket ds=new DatagramSocket(6666); //创建接收数据的字节数组 byte[] bytes=new byte[1024]; //创建接受的数据包 DatagramPacket dp=new DatagramPacket(bytes, bytes.length); //接收 ds.receive(dp); //获取接收包上的数据 int length=dp.getLength(); String ip=dp.getAddress().getHostAddress(); int port=dp.getPort(); System.out.println("ip地址为"+ip+"端口号为"+port+"发送的内容为"+new String(bytes,0,length)); //释放资源 ds.close(); } }
TCP通信
TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。
区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。
而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事
先启动,等待客户端的连接。
ServerSocket
ServerSocket类可以实现一个服务器端的程序
构造方法:
使用该构造方法在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)。
常用方法:
ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。
当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求,accept()方法才会返回一个Scoket对象用于和客户端实现通信,
程序才能继续向下执行。
Socket
Socket类,用于实现TCP客户端程序。
构造方法:
常用方法:
①int getPort() 该方法返回一个int类型对象,该对象是Socket对象与服务器端连接的端口号
②InetAddress getLocalAddress() 该方法用于获取Socket对象绑定的本地IP地址,并将IP地址封装成InetAddress类型的对象返回
③void close() 该方法用于关闭Socket连接,结束本次通信。
④InputStream getInputStream()
该方法返回一个InputStream类型的输入流对象,如果该对象是由服务器端的Socket返回,就用于读取客户端发送的数据,反之,用于读取服务器端发送的数据
⑤OutputStream getOutputStream()
该方法返回一个OutputStream类型的输出流对象,如果该对象是由服务器端的Socket返回,就用于向客户端发送数据,反之,用于向服务器端发送数据
在Socket类的常用方法中,getInputStream()和getOutStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互
的,从而实现通信。
实例:
TCP网络程序模拟
普通版本:
客户端:
public class TCPclient { public static void main(String[] args) throws IOException, IOException { //1、创建Socket对象,连接服务器 Socket socket=new Socket("127.0.0.1",7777); //2、通过客户端套接字对象Socket对象中获取字节输出流的方法 //返回一个OutputStream类型的输出流对象 OutputStream out=socket.getOutputStream(); //3、将数据写向服务器 out.write("服务器你好".getBytes()); //接收服务器端的回复 InputStream in=socket.getInputStream(); byte[] bytes=new byte[1024]; int len=in.read(bytes); System.out.println(new String(bytes,0,len)); //释放资源 socket.close(); } }
服务器端:
//服务器端 public class TCPserver { public static void main(String[] args) throws IOException { //创建服务器套接字(通过端口号连接) ServerSocket server=new ServerSocket(7777); //调用accept方法与客户端获取链接(调用时服务器端处于阻塞状态,等待客户端发送请求,收到请求后返回socket对象) Socket sorcket=server.accept(); //返回一个InputStream类型的输入流对象 InputStream in=sorcket.getInputStream(); byte[] bytes=new byte[1024]; int len=in.read(bytes); System.out.println(new String(bytes,0,len)); //服务器给客户端回复 OutputStream out=sorcket.getOutputStream(); out.write("收到".getBytes()); server.close(); } }
多线程版本:
客户端:
//客户端 public class TCPClient { public static void main(String[] args) throws IOException, IOException { Socket socket=new Socket("192.168.1.171", 7000); OutputStream out=socket.getOutputStream(); //明确数据源 FileInputStream fis=new FileInputStream("d:\\test\\WallpaperStudio10-37768.jpg"); int len=0; byte[] bytes=new byte[1024]; //文件复制 //发生错误,流当中没有文件结尾,不存在-1 while((len=fis.read(bytes))!=-1){ out.write(bytes,0,len); } //告诉服务器端不要再读了,到末尾了 socket.shutdownOutput(); //服务器端回复 InputStream in=socket.getInputStream(); len=in.read(bytes); System.out.println(new String(bytes,0,len)); //释放资源 socket.close(); fis.close(); } }
服务器端(直接写任务):
//服务器端任务 public class UPload implements Runnable{ private Socket socket; public UPload(Socket sokect) { super(); this.socket = sokect; } //重写run()方法执行任务 public void run() { FileOutputStream fos=null; try{ //明确数据源 InputStream in=socket.getInputStream(); //明确目的地 File file=new File("d:\\upload"); if(!file.exists()){ file.mkdirs(); } //域名+毫秒值+6位随机数(避免文件重名) String filename="oracle"+System.currentTimeMillis()+".jpg"; fos=new FileOutputStream(file+File.separator+filename); //开始复制 int len=0; byte[] bytes=new byte[1024]; while((len=in.read(bytes))!=-1){ fos.write(bytes, 0, len); } //回复客户端 OutputStream out=socket.getOutputStream(); out.write("上传成功".getBytes()); }catch(IOException ex){ ex.printStackTrace(); }finally { } //释放资源 try { fos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
测试:
public class demo { public static void main(String[] args) throws IOException { ServerSocket server=new ServerSocket(7000); while(true){ //循环接收客户端发送过来的请求并返回一个socket对象 Socket socket=server.accept(); //将此socket对象传给客户端并开启一个新的线程 new Thread(new UPload(socket)).start(); } } }