Socket-UDP介绍
- 英语:User Datagram Protocol,简称UDP
- 支持一个无连接的传输协议,该协议称为用户数据报协议,也称为用户数据报文协议
- 是一个简单的面向数据报的传输层协议,正式规范为RFC 768
UDP特点
- 它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份
- UDP在IP数据报的头部仅仅加入复用和数据校验(字段)
- 发送端生产数据,接收端从网络获取数据
- 结构简单、无校验、速度快、容易丢包、可广播
UDP报文头结构
- 0-15是源端口
- 16-31是目的端口
- 上面是0这个32位的结构,下面0-32分别是字节长度和头部和数据校验
- UDP包最大的长度
1 、首先看图,我们发现一个存储长度信息是16位(如Source Port),一个字节8位,所以16位->2个字节
2、根据UDP协议,UDP的最大长度是2^16-1=65535个字节
3、根据1我们可以知道,UDP自身实际占了的是8个字节,且典型的 IPv4报头是20个字节,所以总共是65535-8-20=35507个字节
UDP的单播、多播、广播
- 单播:就是点对点进行传输数据,所以肯定需要知道对方的主机地址。如:你写情书给女神(如果你是博爱,请移步到广播,谢谢)
- 多播:对一组进行传输数据,可以称为组播。如小组委员收小组作业
- 广播:往这个地址发送的信息,在整个网段中的主机都能收到信息。可以理解为你在学校广播唱歌,不管是谁都会听到
- 广播分为直接广播地址和受限广播地址,受限广播地址就是255.255.255.255。直接广播的地址是主机位全为1,可以由主机的ip地址和掩码计算的得到
广播地址运算
- IP:192.168.1.111
- 子网掩码:255.255.255.0
- 网络地址:192.168.1.0
1、首先IP192开头所以属于c类地址
2、因为IP地址=网络地址+主机地址
所以网络地址=子网掩码&&IP地址
11000000.10101000.000000001.01100101 //ip地址
&&
1111111111.1111111111.111111111111.0000000 //子网掩码
=11000000.10101000.000000001.0000000 =192.168.1.0
第一个可用192.168.1.1,最后一个可用:192.168.1.254
广播是192.168.1.255
广播
标识段host ID 为全1 的IP 地址为广播地址。
点对点实现局域网搜索
接受数据
public class UdpProvider {
public static void main(String[] args) throws IOException {
System.out.println("Provider start .......");
//作为接受者,指定一个端口用于数据接受
DatagramSocket ds = new DatagramSocket(2000);
final byte[] buff = new byte[512];
DatagramPacket receiverPack = new DatagramPacket(buff, buff.length);
//接受
ds.receive(receiverPack);
//打印接受到的信息与发送者的信息
//发送者的IP地址
String ip = receiverPack.getAddress().getHostAddress();
int port = receiverPack.getPort();
int dataLen = receiverPack.getLength();
String data = new String(receiverPack.getData(), 0, dataLen);
System.out.println("UDPProvider receive from ip:" + ip
+ "\nport:" + port + "\ndata:" + data);
//回送数据
String responseData = "Receive data with lenth:" + dataLen;
byte[] responseDataByte = responseData.getBytes();
//直接根据发送者构建一份回收信息
DatagramPacket datagramPacket = new DatagramPacket(responseDataByte
, responseDataByte.length
, receiverPack.getAddress()
, receiverPack.getPort());
ds.send(datagramPacket);
System.out.println("UDPProvider finish");
ds.close();
}
}
搜索
public class UdpSearch {
public static void main(String[] args) throws IOException {
System.out.println("UdpSearch start .......");
//作为搜索方,让系统自动分配
DatagramSocket ds = new DatagramSocket();
//回送数据
String requestData = "Hello UDP";
byte[] requestDataByte = requestData.getBytes();
//直接根据发送者构建一份回收信息
DatagramPacket datagramPacket = new DatagramPacket(requestDataByte
, requestDataByte.length);
datagramPacket.setAddress(InetAddress.getLocalHost());
datagramPacket.setPort(2000);
ds.send(datagramPacket);
//接受
final byte[] buff = new byte[512];
DatagramPacket receiverPack = new DatagramPacket(buff, buff.length);
//接受
ds.receive(receiverPack);
//打印接受到的信息与发送者的信息
//发送者的IP地址
String ip = receiverPack.getAddress().getHostAddress();
int port = receiverPack.getPort();
int dataLen = receiverPack.getLength();
String data = new String(receiverPack.getData(), 0, dataLen);
System.out.println("UdpSearch receive from ip:" + ip
+ "\nport:" + port + "\ndata:" + data);
System.out.println("UdpSearch finish");
ds.close();
}
}
TCP介绍
- 英文:Transmission Control Protocol,简称TCP
- TCP是传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义
- 与UDP一样完成第四层传输层所指定的功能和指责
优缺点
- 可以实现聊天消息传输、推送
- 可以实现单人语音、视频聊天
- 限制:无法进行广播、多播
TCP机制
- 三次连接,四次挥手
三次连接
玩游戏开黑或者打电话的时候,我们都会:
1、喂、能听到我说话吗
2、我能听到,你能听到我说话吗
3、能能,来来,游戏搞起来
四次挥手
打完游戏,准备关电脑睡觉
1、不玩了,我要睡觉了
2、嗯,我也不玩了,886
3、他关了LOL
4、你关了LOL
- 具有校验机制,可靠,数据传输稳定
主要API
- socket(): 创建Socket
- bind(): 绑定一个Socket到一个本地地址和端口上
- connect(): 连接到远程套接字
- accept(): 接受一个进的连接
- write(): 将数据写入Socket输出流
- read(): 从Socket输入流读取数据
TCP流程
-
客户端流程
-
服务器流程
TCP传输数据
客户端
public class Client {
private static final int PORT = 2000;
private static final int LOCAL_PORT = 2002;
public static void main(String[] args) throws IOException {
//创建socket
Socket socket = createSocket();
//初始化socket
initSocket(socket);
//连接本地
socket.connect(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 3000);
System.out.println("客户端已发起服务器连接,等待服务器连接...........");
System.out.println("客户端信息" + socket.getLocalAddress() + "\t本地端口号Port:" + socket.getLocalPort());
System.out.println("服务器信息" + socket.getInetAddress() + "\t服务器端口号Port:" + socket.getPort());
//发送数据
todo(socket);
//释放资源
socket.close();
System.out.println("客户端已退出");
}
private static void initSocket(Socket socket) throws SocketException {
//设置读取超时时间
socket.setSoTimeout(2000);
//是否复用未完全关闭的socket地址,对于指定bind操作后的套接字有效
socket.setReuseAddress(true);
//是否开启Nagel算法
socket.setTcpNoDelay(true);
//是否需要在长时间无数据相应时才确认数据,时间大约2小时
socket.setKeepAlive(true);
//对于close操作进行处理,默认false,0
//表示关闭时最长阻塞20毫秒
socket.setSoLinger(true,20);
//是否让紧急数据内敛;紧急数据通过socket.sendUrgentData(1)发送
socket.setOOBInline(true);
//设置接受缓存器的大小
socket.setReceiveBufferSize(64*1024*1024);
socket.setSendBufferSize(64*1024*1024);
//设置性能参数:短连接时间,延迟,带宽的重要性 只表示三者之间的比重
socket.setPerformancePreferences(1,1,1);
}
private static Socket createSocket() throws IOException {
Socket socket = new Socket();
//绑定本地端口
socket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), LOCAL_PORT));
return socket;
}
private static void todo(Socket client) throws IOException {
//键盘输入流
InputStream in = System.in;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
//socket输出流,并转换为打印流,用于发送数据
OutputStream outputStream = client.getOutputStream();
PrintStream socketPrintStream = new PrintStream(outputStream);
//得到输入流
InputStream inputStream = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
boolean flag = true;
do {
//键盘读取一行
String str = bufferedReader.readLine();
//发送到服务器
socketPrintStream.println(str);
//服务器中读取一行
String s = reader.readLine();
if ("bye".equalsIgnoreCase(s)) {
flag = false;
} else {
System.out.println(s);
}
} while (flag);
//资源释放
reader.close();
socketPrintStream.close();
}
}
服务端
public class Server {
private static final int PORT = 2000;
public static void main(String[] args) throws IOException {
ServerSocket server = createServerSocket();
initServerSocket(server);
System.out.println("服务器准备就绪...........");
System.out.println("服务器信息" + server.getInetAddress() + "\t服务器端口号Port:" + server.getLocalPort());
//等待客户端连接
for (; ; ) {
//得到客户端
Socket client = server.accept();
ClientHandler clientHandler = new ClientHandler(client);
clientHandler.start();
}
}
private static void initServerSocket(ServerSocket server) throws SocketException {
//设置是否复用未完全关闭的地址端口
server.setReuseAddress(true);
//设置接受缓存器的大小
server.setReceiveBufferSize(60 * 1024 * 1024);
//设置性能参数:短连接时间,延迟,带宽的重要性 只表示三者之间的比重
server.setPerformancePreferences(1, 1, 1);
}
private static ServerSocket createServerSocket() throws IOException {
ServerSocket serverSocket = new ServerSocket();
//20代表我们允许等待连接数
serverSocket.bind(new InetSocketAddress(Inet4Address.getLocalHost(), PORT), 20);
return serverSocket;
}
private static class ClientHandler extends Thread {
private Socket socket;
private boolean isFlag = true;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
System.out.println("新客户端连接:" + socket.getInetAddress() + "客户端端口号:" + socket.getPort());
try {
//得到打印流,用于数据输出,服务器回收数据
PrintStream socketPrintStream = new PrintStream(socket.getOutputStream());
//得到输出流,用于接受数据
BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));
do {
//客户端得到一条数据
String str = socketInput.readLine();
if ("bye".equalsIgnoreCase(str)) {
isFlag = false;
//回送bye
socketPrintStream.println("bye");
} else {
//回送一条数据
System.out.println("接收到客户端发来的信息:" + str);
socketPrintStream.println("服务器得到内容是:" + str);
}
} while (isFlag);
socketInput.close();
socketPrintStream.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("连接异常断开了...");
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("客户端已退出:" + socket.getInetAddress() + ",客户端端口号:" + socket.getPort());
}
}
}
基础类型数据传输
- byte、char、short
- boolean、int、long
- float、double、string
byte数据传输
- 修改之前的client中的todo 方法
private static void todo(Socket client) throws IOException {
//socket输出流,并转换为打印流,用于发送数据
OutputStream outputStream = client.getOutputStream();
//得到输入流
InputStream inputStream = client.getInputStream();
byte[] buffer = new byte[512];
boolean flag = true;
//发送到服务器
outputStream.write(new byte[]{111});
//服务器中读取
int read = inputStream.read(buffer);
if (read > 0) {
System.out.println("收到数量:" + read + "\t数据:" + Array.getByte(buffer, 0));
} else {
System.out.println("收到数量:" + read);
}
//资源释放
outputStream.close();
inputStream.close();
}
修改服务器中的ClientHandler方法
private static class ClientHandler extends Thread {
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
System.out.println("新客户端连接:" + socket.getInetAddress() + "客户端端口号:" + socket.getPort());
try {
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] buffer=new byte[512];
int read = inputStream.read(buffer);
if(read>0){
System.out.println("收到数量:"+read+"\t数据:"+ Array.getByte(buffer, 0));
outputStream.write(buffer,0,read);
}else{
System.out.println("收到数量:"+read);
outputStream.write(new byte[]{0});
}
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
System.out.println("连接异常断开了...");
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("客户端已退出:" + socket.getInetAddress() + ",客户端端口号:" + socket.getPort());
}
}
其他基础数据类型
客户端
byte[] buffer = new byte[512];
ByteBuffer wrap = ByteBuffer.wrap(buffer);
wrap.put((byte) 126);
wrap.put((byte) 1234567);
boolean b=false;
wrap.put((byte) (b?1:0));
String str="I am peakmain";
wrap.put(str.getBytes());
//发送到服务器
outputStream.write(buffer,0,wrap.position()+1);
服务器获取数据
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] buffer=new byte[512];
int read = inputStream.read(buffer);
ByteBuffer wrap = ByteBuffer.wrap(buffer, 0, read);
//string
int position = wrap.position();
String s=new String(buffer,position,read-position-1);
int intValue = wrap.getInt();
boolean b=wrap.get()==1;
if(read>0){
System.out.println("收到数量:"+read+"\t数据:"+s+"\t"+intValue+"\t"+b);
outputStream.write(buffer,0,read);
}else{
System.out.println("收到数量:"+read);
outputStream.write(new byte[]{0});
}