在讲协议之前,我们需要先了解Socket(套接字),就是为网络服务提供的一种机制,通信的两端都有Socket,网络通信其实就是Socket之间的通信,数据在两个Socket之间通过IO传输。可以将Socket理解为“插座”,也就是每一台主机都必须得有
UDP
DatagramSocket(); 发送和接收数据包的套接字
DatagramPacket(); 数据报包,实现无连接包投递服务
建立UDP发送端
思路:1.建立udp的socket服务 2.明确具体发送的数据 3.通过socket服务将具体的数据发送出去 4.关闭服务
代码实现
package UDP;
import java.net.*;
public class UdpSend {
public static void main(String[] args) throws Exception {
System.out.println("udp发送端启动");
//建立udp服务
DatagramSocket ds = new DatagramSocket();
//明确数据
String str = "UDP来了!";
//发送数据,将数据封装到数据包中,数据包会明确地址和端口
byte[] buf = str.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName("localhost"), 12345);
//发送
ds.send(dp);
//关闭服务
ds.close();
}
}
此时,发送端就完成发送任务啦~
建立UDP接收端
思路:1.创建socket服务,明确一个端口 (该端口应与发送端的端口保持一致) 2.接收数据(以数据包的形式) 3.将需要的数据取出来 4.关闭资源
代码实现
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpReceive {
public static void main(String[] args) throws IOException {
System.out.println("udp接收端启动");
//创建接收端服务
DatagramSocket ds = new DatagramSocket(12345); //端口对应发送端的端口
//接收数据
//接收数据将会是接收所有的数据。而我们需要将这些数据存储在数据包中,
//然后通过数据包对象的方法对收到的数据进行解析
//创建数据包
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//接收数据
ds.receive(dp);
//对收到的数据进行解析,使用数据包的方法
String id = dp.getAddress().getHostAddress();
int port = dp.getPort();
//获取文字数据
String str= new String(dp.getData(), 0, dp.getLength());
System.out.println(id + "---" + port + "---" + str);
//关闭资源
ds.close();
}
}
需要注意:在运行程序时,会出现执行到receive()方法时,程序不再反应了,有可能出现了这两种问题
(1).发送端和接收端执行的先后顺序搞错。因为UDP是不可靠的协议,它不会考虑是否有接受者的问题,先启动发送端,它没有找到相应的接收者,于是数据包就丢失了,正确顺序是:先执行接收端连接发送端端口,再执行发送端
(2).关闭防火墙,可能是数据包被拦截了,receive这边没收到,关掉防火墙就可以了
用UDP实现聊天室
思路架构:
聊天室其实就是在一个用户上既可以发送数据,又可以接收数据,那么,就需要两个类分别代表发送和接收,并且这两个要能够同时进行,此时就需要多线程。
发送:用UDP发送数据,那么就需要通过DatagramSocket类帮助发送,也就是任务对象一建立,就需要socket对象,那么我们可以将DatagramSocket作为成员变量并进行封装。
然后线程中的run()如何实现呢?既然是发送端,那么核心任务就是发送数据了:1.发送的数据是通过键盘进行录入的 2.将数据封装到数据包中 3.将数据包发送出去
接收:和发送一样,接收也同样需要通过DatagramSocket类,创建socket对象
接收数据如何实现?其实原理就和上面的UDP接收端是一样的:1.接收具体的数据内容 2.创建数据包对象 3.将受到的数据存储到数据包中 4.获取数据
但是,这样的话,接收和发送的数据就都只是一次性的,要让它变为不停的来回发送和接收,那么只需要给条件加一个循环就可以了,以下是代码实现:
package UDP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UdpChat {
public static void main(String[] args) throws SocketException {
DatagramSocket send = new DatagramSocket(8888);
DatagramSocket receive = new DatagramSocket(12345);
Thread t1 = new Thread(new Send(send));
Thread t2 = new Thread(new Receive(receive));
t1.start();
t2.start();
}
}
//发送端
class Send implements Runnable {
private DatagramSocket ds;
public Send(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
//键盘录入数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//读取数据
String line = null;
try {
while((line = br.readLine()) != null) {
if("over".equals(line)) {
break;
}
//将数据变成字节数组,封装到数据包中
byte[] buf = line.getBytes();
DatagramPacket dp = new DatagramPacket(buf, buf.length,
InetAddress.getByName("localhost"), 12345);
//发送数据
ds.send(dp);
}
//不在录入数据,关闭服务
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//接收端
class Receive implements Runnable {
private DatagramSocket ds;
public Receive(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
while (true) {
//创建字节数组,因为使用数据包必须要有字节数组
byte[] buf = new byte[1024];
//创建数据包对象
DatagramPacket dp = new DatagramPacket(buf, buf.length);
//接收数据
try {
ds.receive(dp);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据
InetAddress id = dp.getAddress();
String data = new String(buf, 0, buf.length);
if("over".equals(data)) {
System.out.println(id + "离开聊天室...");
}
System.out.println(id + ": " + data);
}
}
}
执行结果:
TCP
和UDP需要发送端和接收端也一样,TCP也要建立客户端和服务端,但是TCP是面向连接的,只有连接成为通路,才会在客户端和服务端之间进行数据的传输,而这个连接过程就被成为三次握手,简易图解如下
更深入了解三次握手和四次挥手可以看看转载的这篇文章:https://www.cnblogs.com/laowz/p/6947539.html
建立TCP客户端:
Socket(); 客户端的套接字
ServerSocket(); 服务端的套接字
思路:
1.建立tcp客户服务
1.1因为是面向连接,必须有连接才可以进行通信
1.2在创建客户端时,就必须明确目的地址和端口
2.一旦建立连接,就有了传输数据的通道,就可以在通道中进行数据传输,而这个传输就是通过流实现的,该流就是socket io流
3.只要获取socket io中的写动作就可以将数据写到socket流中发给服务端
4.关闭资源
代码实现:
package TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) throws IOException {
System.out.println("客户端启动...");
//创建客户端,明确地址和端口
Socket socket = new Socket("localhost", 10234);
//获取socket中的输出流,将数据发送给服务端
OutputStream out = socket.getOutputStream();
//通过流写数据
out.write("注意,tcp来啦!".getBytes());
//关闭资源
socket.close(); //关闭的时候连同流一起关闭
}
}
注意:此时运行程序不会出现结果,并且会报错,如图
原因很简单,因为TCP是面向连接的,没有接收端就不能建立传输通道,自然会报错
建立TCP服务端:
思路:
1.创建socket服务端服务,服务端为了让客户端可以连接上,必须提供端口,佳宁一个端口
2.获取客户端对象,通过客户端的socket流对应的客户端进行通信
3.获取客户端的socket流的读取流
4.读取并显示在服务器端
5.关闭资源
代码实现:
package TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) throws IOException {
System.out.println("客户端启动...");
//创建客户端,明确地址和端口
Socket socket = new Socket("localhost", 10234);
//获取socket中的输出流,将数据发送给服务端
OutputStream out = socket.getOutputStream();
//通过流写数据
out.write("注意,tcp来啦!".getBytes());
//关闭资源
socket.close(); //关闭的时候连同流一起关闭
}
}
运行结果:先启动服务端建立连接,在启动客户端发送数据
TCP客户端服务端互访
上面的例子只是客户端向服务端发送数据,服务端显示,我们觉得不太过瘾,希望实现客户端非服务端发送,服务端也可以给客户端进行回应,下面是代码实现
客户端
package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient2 {
public static void main(String[] args) throws IOException {
System.out.println("客户端2启动...");
//建立客户端socket
Socket s = new Socket("localhost", 12345);
//获取输出流,将数据发送给服务端
OutputStream out = s.getOutputStream();
//写数据
out.write("注意,tcp2来啦!".getBytes());
//接收服务端的回馈
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String str = new String(buf, 0, len);
System.out.println(str);
s.close();
}
}
服务端
package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer2 {
public static void main(String[] args) throws IOException {
System.out.println("服务端2启动...");
//创建服务端socket
ServerSocket ss = new ServerSocket(12345);
//接收客户端的对象
Socket s = ss.accept();
//通过对象获得socket流
InputStream in = s.getInputStream();
//读取并显示
String id = s.getInetAddress().getHostAddress();
byte[] buf = new byte[1024];
int len = in.read(buf);
String str = new String(buf, 0, len);
System.out.println(id + ": " + str);
//反馈给客户端
OutputStream out = s.getOutputStream();
out.write("好的,知道你来啦".getBytes());
s.close();
ss.close();
}
}
运行结果:
TCP上传文本客户端
需求:上传文本到客户端,读取本地文本数据,服务端接收完毕后,回馈“上传成功”字样
在这一块,需要用到大量的IO流知识,关于IO流可以参考文章:https://blog.csdn.net/szy2333/article/details/81531531
客户端
package TCP;
import java.io.*;
import java.net.Socket;
public class TcpUploadTextClient {
public static void main(String[] args) throws IOException {
System.out.println("上传文本客户端启动...");
/*
*上传文本的客户端。读取本地文本数据,发送给服务端,服务端接收完毕后,回馈“上传成功”字样
*/
//创建客户端socket
Socket s = new Socket("localhost", 1345);
//确定数据源,本地文件上传
BufferedReader bufr = new BufferedReader(new FileReader("client.txt"));
//确定目的。socket输出流
//BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//和被注释的上面一行代码的效果是一样的
PrintWriter out = new PrintWriter(s.getOutputStream(), true);
String line = null;
while((line = bufr.readLine()) != null) {
out.println(line);
}
//给服务端发送标记,使用socket的禁用输出流的方法
s.shutdownOutput();
//接收服务端的反馈
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str = bufIn.readLine();
System.out.println(str);
}
}
服务端
package TCP;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpUploadTextServer {
public static void main(String[] args) throws IOException {
System.out.println("上传文本服务端启动...");
//创建服务端
ServerSocket ss = new ServerSocket(1345);
//获取客户端
Socket s = ss.accept();
//获取读取流
BufferedReader bufIn = new BufferedReader(new InputStreamReader(s.getInputStream()));
//确定目的文件
PrintWriter pw = new PrintWriter(new FileWriter("server.txt", true));
//频繁读写
String line = null;
while ((line = bufIn.readLine()) != null) {
pw.println(line);
}
//给客户端返回信息
PrintWriter pwOut = new PrintWriter(s.getOutputStream(), true);
pwOut.println("上传成功");
//关闭各种资源
pw.close();
s.close();
ss.close();
}
}
最后结果就将client.txt文件中的内容上传至服务器,然后通过服务器保存在server.txt中了,两个文件的内容是一样的,所以本地上传文件的本质就是文件复制!