一、OSI网络体系结构
OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
物理层
物理层处于OSI的最底层,是整个开放系统的基础。物理层涉及通信信道上传输的原始比特流(bits),它的功能主要是为数据端设备提供传送数据的通路以及传输数据。
数据链路层
数据链路层的主要任务是实现计算机网络中相邻节点之间的可靠传输,把原始的、有差错的物理传输线路加上数据链路协议以后,构成逻辑上可靠的数据链路。需要完成的功能有链路管理、成帧、差错控制以及流量控制等。其中成帧是对物理层的原始比特流进行界定,数据链路层也能够对帧的丢失进行处理。
网络层
网络层涉及源主机节点到目的主机节点之间可靠的网络传输,它需要完成的功能主要包括路由选择、网络寻址、流量控制、拥塞控制、网络互连等。
传输层
传输层起着承上启下的作用,涉及源端节点到目的端节点之间可靠的信息传输。传输层需要解决跨越网络连接的建立和释放,对底层不可靠的网络,建立连接时需要三次握手,释放连接时需要四次挥手。
会话层和表示层
会话层的主要功能是负责应用程序之间建立、维持和中断会话,同时也提供对设备和结点之间的会话控制,协调系统和服务之间的交流,并通过提供单工、半双工和全双工3种不同的通信方式,使系统和服务之间有序地进行通信。
表示层关心所传输数据信息的格式定义,其主要功能是把应用层提供的信息变换为能够共同理解的形式,提供字符代码、数据格式、控制信息格式、加密等的统一表示。
应用层
应用层为OSI的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。
二、TCP/IP参考模型
重点掌握其传输层:
传输层 ( TCP层 )
TCP层负责在应用进程之间建立端到端的连接和可靠通信,它只存在与端节点中。TCP层涉及两个协议,TCP和UDP。其中,TCP协议提供面向连接的服务,提供按字节流的有序、可靠传输,可以实现连接管理、差错控制、流量控制、拥塞控制等。UDP协议提供无连接的服务,用于不需要或无法实现面向连接的网络应用中。
1.三次握手与四次挥手
TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为“三次握手”。
三次握手过程如下:
1.客户端向服务器发送请求
2.服务器回复客户端收到
3.客户端回复服务器我收到了你收到了这条消息
四次挥手过程如下:
1.客户端发送消息给服务器,我要关闭了
2.服务器收到消息,并表示还要发送点消息给你
3.服务器发送可以关闭的消息给客户端
4.客户端收到消息,并发收到,后而双方关闭
三、Socket编程
1.socket通信模型
2.基于TCP的简单Socket编程程序
服务器端:
public static void main(String[] args) throws IOException {
//创建客户端口6666的一个ServerSocket的监听器
ServerSocket server = new ServerSocket(6666);
//
while(true) {
//监听器监听得到一个socket对象,
Socket socket = server.accept();
//通过这个socket对象得到输入流来读取客户端发来的消息。
DataInputStream in = new DataInputStream(socket.getInputStream());
String readUTF = in.readUTF();
System.out.println(readUTF);
//关闭流和socket
in.close();
socket.close();
}
}
客户端:
public static void main(String[] args) throws UnknownHostException, IOException {
//创建一个面向于这个"192.168.3.151",6666地址的socket对象
Socket socket = new Socket("192.168.3.151",6666);
//通过socket的输出流写数据到这个端口去
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeUTF("你好,客户端");
out.close();
socket.close();
}
注意:
(1)先开启服务器,再打开客户端。
3.基于UDP的简单Socket编程
发送端:
public static void main(String[] args) throws IOException {
//发送端创建一个socket,并指定本机的端口号:如6556
DatagramSocket ds = new DatagramSocket(6556);
//将想发送的数据得到其byte[]存储形式
String s = new String("爱吃肉的小周");
byte[] b = s.getBytes();
/**
* 创建一个数据包,DatagramPacket类
* 参数:1.发送数据的 byte[]
* 2.byte[] 的长度`在这里插入代码片`
* 3.一个网络的地址对象 new InetSocketAddress(要发送目的的IP,端口号)
*/
DatagramPacket dp = new DatagramPacket(b, b.length, new InetSocketAddress("192.168.3.151",1689));
//发送数据包
ds.send(dp);
//关闭socket
ds.close();
}
接收端:
public static void main(String[] args) throws IOException {
//创建一个字节数组
byte[] b = new byte[1024];
//创建一个socket,端口号为1689,目的是为了接收数据包
DatagramSocket ds = new DatagramSocket(1689);
//创建一个数据包,用byte[]装,长度为b.length
DatagramPacket dp = new DatagramPacket(b, b.length);
//发送数据包
ds.receive(dp);
System.out.println(new String(b,0,dp.getLength()));
//关闭socket
ds.close();
}
注意:
先开启接收端,再开启发送端。
四、多客户端连接服务器
1.服务器:
public static void main(String[] args) throws IOException {
//创建一个socket的监听器
ServerSocket sever = new ServerSocket(6699);
System.out.println("服务器已开启,等待客户端连接...");
int num = 0;
while(true) {
//得到socket对象
Socket socket = sever.accept();
num++;
System.out.println(num+"号客户端已连接");
//创建线程启动自动读写,num为几号客户端
new Thread(new ServerRead$Write(socket, num)).start();
}
}
2.服务器的读写线程:
public class ServerRead$Write implements Runnable{
private Socket socket;
private int num;
//构造一个客户端的读写线程来处理读写操作。
public ServerRead$Write(Socket socket,int num) {
//构造获得其socket对象
this.socket = socket;
//记录客户端号
this.num = num;
}
@Override
public void run() {
try {
//创建socket得来的输入,输出流
PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);
BufferedReader read =new BufferedReader(new InputStreamReader(socket.getInputStream()));
while(true) {
//读取从客户端发送过来的消息
String readLine = read.readLine();
System.out.println("服务器收到"+num+"号客户端"+"发送的消息:"+readLine);
//如果发送的消息是“bye”就会跳出循环,关闭流与socket
if("bye".equals(readLine)) {
System.out.println(num+"号服务器已断开连接!");
writer.println("BYE");
Thread.sleep(200);
break;
}else {
//转为大写,发送过去
writer.println(readLine.toUpperCase());
}
}
writer.close();
read.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
3.客户端:
public static void main(String[] args) throws UnknownHostException, IOException {
//创建一个socket
Socket socket = new Socket("localhost",6699);
//开启读的线程
new Thread(new ClientRead(socket)).start();
//开启写的线程
new Thread(new ClientWrite(socket)).start();
}
4.客户端的读线程:
public class ClientRead implements Runnable {
Socket socket;
public ClientRead(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//通过socket得到输入流
BufferedReader read =new BufferedReader(new InputStreamReader(socket.getInputStream()));
while(true) {
//得到输入流
String readLine = read.readLine();
System.out.println("服务器发送:"+readLine);
if(readLine.equals("BYE")) {
break;
}
}
read.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.客户端的写线程:
public class ClientWrite implements Runnable {
Socket socket;
public ClientWrite(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//通过socket得到输出流
PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);
//系统的输入流
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while(true) {
System.out.println("请输入:");
String readLine = reader.readLine();
writer.println(readLine);
if(readLine.equals("bye")) {
break;
}
}
writer.close();
reader.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
总结:
1.使用打印流时,不要用flush(),会造成堵塞,得不到数据
2.客户端的socket :1个输入流 对应 服务器的一个输出流: