网络编程之TCP
什么是七层协议?
IOS(国际标准委员会组织)将数据的传递从逻辑上划分了以下七层
应用层、表示层、会话层、传输层、网络层、数据链据层、物理层
当发送消息时,需要按照上述从前往后的次序对发送的内容进行层层加包,最后发送出去
当接收消息时,需要按照上述相反的次序对发送的内容进行层层拆包,最后解析出来
常见协议
协议:就是一种约定/规则,是通信双方需要遵循的一种机制
http:超文本传输协议,浏览网站时使用该协议
ftp:文件传输协议,上传下载文件时候使用该协议
tcp:传输控制协议,是一种面向连接的协议
udp:用户数据报协议,是一种非面向连接的协议
ip:互联网协议,是上述协议的底层协议
端口号
IP地址可以定位到具体的一台设备
端口号可以定位到设备上具体的进程
在网络编程中需要提供:IP地址,端口号
端口号:本质上是由16位二进制组成的整数,范围是:0~65535,其中0~1024之间的端口以及被系统占用,因此编程端口号从1025开始
Socket
Socket套接字:
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字
Socket原理机制:
通信的两端都有Socket
网络通信其实就是Socket间的通信
数据在两个Socket间通过IO传输
InetAddress
在JDK中提供了一个与IP地址相关的InetAddress类,该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法
其中,前两个方法用于获得该类的实例对象,第一个方法用于获得表示指定主机的InetAddress对象,第2个方法用于获得表示本地的InetAddress对象。通过InetAddress对象便可获取指定主机名、IP地址等。
接下来通过一个案例来演示InetAddress常用方法的使用
import java.net.InetAddress; public class Example01 { public static void main(String[] args) throws Exception { InetAddress host = InetAddress.getLocalHost(); InetAddress byName = InetAddress.getByName("www.baidu.com"); System.out.println("本机的IP地址:"+host.getHostAddress()); System.out.println("百度的IP地址:"+byName.getHostAddress()); System.out.println("3秒内是否可达:"+byName.isReachable(3000)); System.out.println("本机的主机名是:"+host.getHostName()); System.out.println("百度的主机名是:"+byName.getHostName()); } }
TCP通信
面向连接、安全可靠,效率稍低,通过三次握手建立连接
在JDK中提供了两个用于实现TCP程序的类,一个是ServerSocket类,用于表示服务器端,一个Socket类,用于表示客户端。通信时,首先要创建代表服务器端ServerSocket对象,创建该对象相当于开启了一个服务,此服务会等待客户端的连接;然后创建代表客户端的Socket对象,使用该对象向服务器端发出连接请求,服务器端响应请求后,两者才建立连接,开始通信。
ServerSocket(int port)
使用该构造方法在创建Serversocket对象时,可以将其绑定到一个指定的端口号上(参数port就是端口号)。端口号可以指定为0,此时系统就会分配一个还没被其他网络程序所使用的的端口号。由于客户端需要指定的端口号来访问服务器端程序,因此端口号随机分配的情况并不常用,通常都会让服务器端程序监听一个指定的端口号。
ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求时,accept()方法才会返回一个Socket对象,用于和客户端实现通信
Socket(InetAddress address,int port)
参数address用于接收一个InetAddress 类型的对象,该对象用于封装一个IP地址
Socket类的常用方法,其中,getInputStream()和getOutputStream()方法分别用于获取输入流和输出流。当客户端和服务端建立连接后,数据是以IO流的形式进行交互的,从而实现通信。
接下来通过一张图来描述服务器端和客户端的数据传输
简单的TCP网络程序
通过前面两个小节了解到ServerSocket、Socket类的基本用法。为了让初学者更好地掌握这两个类的使用,接下来通过一个TCP通信的案例来进一步学习这两个类的用法
要实现TCP通信需要创建一个服务器端程序和一个客户端程序,为了保证数据传输的安全,首先需要实现服务端程序。
建立TCP服务端的思路
1. 创建ServerSocket类的想,并提供端口号
2. 等待客户端连接,使用accept()方法
3. 等Socket对象,并使用输入输出流进行通信
4. 关闭相互资源
public class Server { private static final int PORT=8888; //定义一个端口号 public static void main(String[] args) throws Exception { //创建ServerSocket对象 ServerSocket ss = new ServerSocket(PORT); //调用accpet()等待客户端的连接 Socket s = ss.accept(); //获取客户端的输出流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); System.out.println("开始与客户端交互数据"); //当客户端接入后,向客户端输出数据 bw.write("我是服务器,你好客户端!"); //模拟置信其他功能占用的时间 Thread.sleep(5000); System.out.println("结束与客户端交互数据"); bw.flush(); s.close(); bw.close(); } }
在创建ServerSocket对象时指定了端口号,并调用该对象的accept()方法。从运行结果可以看出,控制台中的光标一直在闪动,这是因为accept()方法发生阻塞,程序暂时停止运行,直到客户端来访问时才会结束这种阻塞状态。这时该方法会返回一个Socket类型的对象用于表示客户端,通过该对象获取与客户端关联的输出流,并向客户端发送消息。最后调用Socket对象的close()方法将通信关闭。
建立TCP客户端的思路
1. 创建Socket类型的对象,并制定服务器的IP地址和端口号
2. 使用输入输出流进行通信
3. 关闭相互资源
接下来编写与客户端的程序。
public class Client { private static final int PORT=8888; //定义一个端口号 public static void main(String[] args) throws Exception { //创建一个Socket并连接到给出的地址和端口号的计算机 Socket s = new Socket("192.168.96.1", PORT); //得到接收数据的流 BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); //读取数据 String r = br.readLine(); //打印读取到的数据 System.out.println(r); //关闭资源 br.close(); } }
在客户端创建Socket对象与服务器端建立连接后,通过Socket对象获得输入流读取服务端发来的数据,并打印结果,结束本次通信。
多线程的TCP网络程序
实际上,很多服务器端程序都是允许被多个应用程序访问的。例如门户网站可以被多个用户访问,因此服务器都是多线程的。下面通过一个图例来表示多个用户访问同一个服务器。
图中代表的是多个客户端访问同一个服务器端,服务器端为每个客户端创建一个对象的Socket,并且开启一个新的线程使两个Socket建立专线进行通信,接下来对TCP通信的服务端进行改进。
public class Server { private static final int PORT=8888; //定义一个端口号 public static void main(String[] args) throws Exception{ //创建ServerSocket对象 ServerSocket ss = new ServerSocket(PORT); //使用while()循环不停的接收客户端发送的请求 while(true) { //调用accpet()等待客户端的连接 Socket s = ss.accept(); //下面用代码开启一个新线程 new Thread() { public void run() { try { //获取客户端的输出流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); System.out.println("开始与客户端交互数据"); //当客户端接入后,向客户端输出数据 bw.write("我是服务器,你好客户端!"); //模拟置信其他功能占用的时间 Thread.sleep(5000); System.out.println("结束与客户端交互数据"); bw.flush(); s.close(); bw.close(); } catch (Exception e) { e.printStackTrace(); } } }.start(); } } }
使用多线程的方式创建了一个服务器端程序。通过在while循环中调用accept()方法,不停地接收客户端发送的请求,而主线程仍然处于等待状态。