【Socket学习】1. TCP与UDP
网络架构模型
OSI参考模型(Open System Interconnect)
开放式系统互连
OSI定义了网络互连得七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层、应用层)
- 第七层 应用层
- 第六层 表示层
- 第五层 会话层
- 第四层 传输层
- 第三层 网络层
- 第二层 数据链路层
- 第一层 物理层
TCP/IP五层模型
- 应用层
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理层
网络编程中的问题
** Q: 怎么定位网络上的一台主机或多台主机?**
**A:网络层中主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机**
**Q:定位后是如何进行数据传输的?**
**A:传输层提供面向应用的可靠(TCP)的或者非可靠(UDP)的数据传输机制**
TCP协议与UDP协议
1.TCP
<u>TCP(Transmission Control Protocol)传输控制协议。</u>是一种面向连接的保证可靠传输的协议,通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方成对的两个socket之间必须建立连接,当一个socket(Server Socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,他们就可以进行双向数据传输,双方都可以进行发送和接受操作
三次握手
建立起一个TCP连接需要经过“三次握手”
-
第一次握手
客户端发送SYN包(Synchronize Sequence Numbers,同步序列编号)到服务器(syn=j),并进入SYN_SEND状态,等待服务器确认。 -
第二次握手
服务器收到SYN包,必须确认服务器的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包(Acknowledge Character,确认字符),此时服务器进入SYN_RECV状态。 -
第三次握手
客户端收到服务器的SYN+ACK包,向服务器发送确认包(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手握手过程中传送得包中不含数据,三次握手完毕后,客户端与服务器踩正式开始传送数据。
理想状态下TCP连接一旦建立,在通信双方中得任何一方主动关闭连接之前,TCP连接都将一直保持下去。断开连接时,服务器何客户端都可以主动发起断开TCP连接的请求
TCP应用
TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要补丁长度的数据被可靠地传输。但可靠的传输是要付出代价的,对数据内容真千古恶心的检验必然占用计算机的处理时间和网络的带宽,因此TCP的传输效率不如UDP高。
2. UDP
<u>UDP(User Datagram Protocol)用户数据包协议。</u>
这是一种无连接的协议,每个数据包都是一个独立的信息,包括完整的源地址或目的地址,它在网络上可以以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都不能保证
UDP应用
UDP操作简单,而且仅需要较少的监护,因此通常用于局域网、高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下使用UDP更合理
Socket
Socket英文译为“孔;插座”。在网络编程中,网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个scoket。
Socket包含了进行网络通信必须的五种信息:
- 连接使用的协议
- 本地主机的IP地址
- 本地进程的协议端口
- 远地主机的IP地址
- 远地主机的协议端口
Socket的本质是编程接口(API),对TCP./IP的封装,TCP/IP提供可供程序员做网络开发所用的接口
Socket原理
socket(套接字)之间的连接过程可分为三个步骤:**服务器监听**、**客户端请求**、**连接确认**
- 服务器监听
服务器的socket不会去定位某个具体客户端的socket,而是处于等待连接的状态,实时监控网络状态,等待客户端的socket对其发出连接请求。 - 客户端请求
客户端的socket发起连接请求,指定要连接目标(服务器端)的socket。为此客户端的socket必须先描述他要链接的服务器的socket,指出服务器端的套接字的地址和端口号然后向服务器提出连接请求. - 连接确认
是指当服务器端socket监听到客户端socket的连接请求,他就响应客户端socket的请求,建立一个新的线程,把服务端socket的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器socket继续处于监听状态,继续监听接受其他客户端socket的连接请求
Java代码实现
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含一下四个步骤
- 创建Socket
- 打开连接到Socket的I/O流
- 按照一定的协议对Socket进行读写操作
- 关闭Socket
基于TCP的Socket实现
客户端SocketClient.java
package TcpSocketDemo;
import java.io.*;
import java.net.Socket;
/**
* @author Mo
* @createTime 2022/1/8 12:00
* @descripiton
*/
public class SocketClient {
public static void main(String[] args) {
try {
// 创建Socket,指明连接服务器的地址和端口
Socket socket = new Socket("localhost", 8080);
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("海子哥牛逼");
pw.write("啊吧啊吧");
pw.flush();
socket.shutdownOutput();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while( (info = br.readLine() ) !=null) {
System.out.println("我是客户端, 服务器返回信息: " + info);
}
br.close();
is.close();
os.close();
pw.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器SocketServer.java
package TcpSocketDemo;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author Mo
* @createTime 2022/1/8 16:56
* @descripiton
*/
public class SocketServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket;
while (true) {
socket = serverSocket.accept();
ServerThread serverThread = new ServerThread(socket);
serverThread.start();
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端的IP: " + address.getHostAddress() + " 端口号为: " + socket.getPort());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器线程ServerThread.java
package TcpSocketDemo;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author Mo
* @createTime 2022/1/8 17:00
* @descripiton
*/
public class ServerThread extends Thread {
private Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
InputStream is=null;
InputStreamReader isr=null;
BufferedReader br=null;
OutputStream os=null;
PrintWriter pw=null;
try {
is = socket.getInputStream();
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info = null;
while((info=br.readLine())!=null){
System.out.println("我是服务器,客户端说:"+info);
}
socket.shutdownInput();
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("服务器欢迎你");
pw.flush();
} catch (Exception e) {
// TODO: handle exception
} finally{
//关闭资源
try {
if(pw!=null)
pw.close();
if(os!=null)
os.close();
if(br!=null)
br.close();
if(isr!=null)
isr.close();
if(is!=null)
is.close();
if(socket!=null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端返回数据
服务器返回数据
基于UDP的Socket实现
SocketServer.java
package UdpSocketDemo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author Mo
* @createTime 2022/1/8 20:09
* @descripiton
*/
public class SocketServer {
public static void main(String[] args) {
try {
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 创建Socket,指定端口
DatagramSocket socket = new DatagramSocket(8088);
while (true) {
// 接受socket客户端发送的数据,如果未收到会一直阻塞
socket.receive(packet);
String receiveMessage = new String(packet.getData(), 0, packet.getLength());
System.out.println("字节长度: " + packet.getLength());
System.out.println("接受数据: " + receiveMessage);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SocketClient.java
package UdpSocketDemo;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
/**
* @author Mo
* @createTime 2022/1/8 20:08
* @descripiton
*/
public class SocketClient {
public static void main(String[] args) {
try {
String sendMessage = "Hello World";
InetAddress address = InetAddress.getByName("localhost");
// 封装要发送的数据包
DatagramPacket packet = new DatagramPacket(sendMessage.getBytes(), sendMessage.length(), address, 8088);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}