提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 1.分类
- 1.流套接字
- 2.数据报套接字
- 3.原始套接字
- 2.Socket通信模型
- 3.UDP套接字编程
- 1. DatagramSocket API
- 1.构造方法
- 1.DatagramSocket()
- 2.DatagramSocket(int port)
- 2.常用方法
- 1.receive(DatagramPacket p)
- 2.send(DatagramPacket p)
- 2. DatagramPacket API
- 1.构造方法
- 1.DatagramPacket(byte[] buf,int length)
- 2.DatagramPacket(byte[] buf,int offset,int length,SocketAddress address)
- 2. 常用方法
- 1. getAddress()
- 2.getPort()
- 3.getData()
- 3.InetSocketAddress API
- 1.构造函数
- 2.可用方法
- 4.TCP套接字
- 1.ServerSocket API
- 1.构造方法
- 2.常用方法
- 1. accept()
- 2. close()
- 2.Socket API
- 1.构造方法
- 2.常用方法
- 1.getInetAddress()
- 2.getInputStream()
- 3.getOutputStream()
- 5.UDP示例(实现回显服务器和词典功能)
- 1.服务器端的示例
- 2. 客户端的示例
- 6.TCP示例(实现词典功能)
- 1.服务器示例
- 2.客户端示例
- 7.结果执行展示
前言
`Socket套接字,是系统提供的用于网络通信的技术,是基于TCP/IP协议的网络通信的基本单元,基于Socket套接字的网络程序开发就是网络编程,Sevrlet是socket的一种应用
1.分类
Socket套接字分为三类
1.流套接字
传输层使用TCP协议,对于字节流来说,传输流是基于IO流
流式数据的特征在流没有关闭的情况下,可以多次发送,切没有边界,也可以分开发送
具体TCP的特征可以看我前面的文章
2.数据报套接字
传输层使用UDP协议
对于数据来说,可以理解成是发送数据和接受数据都必须一块一块的接受和发送,切不能分次
3.原始套接字
原始套接字用于自定义传输层的协议,用于读写内核没有处理的IP协议数据
2.Socket通信模型
这张图可以很好的展现出由TCP建立连接的通信模型
3.UDP套接字编程
UDP套接字编程,我们的java给我们提供了一个API叫做DatagramSocket
1. DatagramSocket API
1.构造方法
这个API有5个构造方法,但是我们只需要学习2个
我们可以去Java的官方文件中查看
这里只需要学习划红框的两个即可
1.DatagramSocket()
这个是将我们的数据包套接字绑定到我们本地的随机端口,一般用于客户端
2.DatagramSocket(int port)
这个是将我们的数据包套接字绑定到我们本地的指定端口,一般用于服务器
这里可能优点抽象,不过没关系,我们根据下文中的示例来理解,就会清晰很多
2.常用方法
这里我们DatagramSocket的方法有很多,但是我们只需要知道三个常用的就行了
1.receive(DatagramPacket p)
这个方法是从套接字中接受数据包,括号内的参数就是套接字,结果会传回到参数中去
注意:如果没有等到数据包,那么该方法会阻塞等待
DatagramPacket API我们下面就会详解
2.send(DatagramPacket p)
这个方法和上面的方法其实结构式一样的上面的式接受数据,这个是从参数中的这个套接字发送数据
注意:这个方法不会阻塞等待,直接就发送
2. DatagramPacket API
DatagramPacket就是UDP发送的数据报
1.构造方法
这里还是一样,我们只知道这两个就行了
1.DatagramPacket(byte[] buf,int length)
这里是构造一个DatagramPacket用来接受长度为length的数据包,这里的buf是用来存放接受到的数据的
2.DatagramPacket(byte[] buf,int offset,int length,SocketAddress address)
这里是构造一个DatagramPacket用来发送长度为length的数据包,buf用来存放发送的数据,这里的offset是偏移量的意思,即从哪里开始,长度为length,address是指定目的主机的端口号的
这里我们构造数据包的时候,需要SocketAddress,这个对象可以通过我们下面介绍的InetSocketAddress来创建
2. 常用方法
它的方法也有很多,但是我们只挑我们需要的
1. getAddress()
这个方法是从获取到的数据包中,获取发送端主机的IP地址,或者从发送的数据报中,获取接收端的主机IP地址,并且打包到InetAddress中
2.getPort()
这个方法是从接收的数据包中获取到发送端主机的端口号,或者从发送的数据包中,获取接收端的主机端口号
3.getData()
这个方法,其实就是获取数据包中的数据
3.InetSocketAddress API
1.构造函数
这个构造函数我们只需要了解一个就行了
2.可用方法
现阶段,我们用这个API来构造一i个Socket地址即可,先不了解方法
4.TCP套接字
1.ServerSocket API
ServerSocket是创建TCP服务端的Socket API
1.构造方法
这里比UDP的简单些,只需要学一个构造方法即可
创建一个服务端流套接字,并且绑定到指定的端口上面去
2.常用方法
这里我们常用两个方法
1. accept()
这个方法是让我们服务端开始监听窗口,在创建的时候要先绑定端口号,有客户端连接之后,返回一个Socket对象,然后客户端基于这个对象,建立连接,否则就阻塞等待
2. close()
这个方法,会让我们的套接字进行关闭操作
2.Socket API
1.构造方法
这个构造方法我们也只需要学一个即可
这里的host是对应主机的IP地址,这里的port是对应主机的端口号
2.常用方法
1.getInetAddress()
这个方法就像图片上面的一样,简单易懂
2.getInputStream()
3.getOutputStream()
5.UDP示例(实现回显服务器和词典功能)
1.服务器端的示例
代码里面是逐行注释了,不存在看不懂,静下心去看
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 王久实
* Date: 2022-10-17
* Time: 20:05
*/
//这里继承UdpEchoServer,UdpEchoServer是我们自己写的回显服务器,在下面这里为了方便,直接就继承下面写好的,只需要对process进行修改就行了
public class UdpTranslateServer extends UdpEchoServer{
// 创建一个Map用来对应单词和释意
private Map<String,String> map = new HashMap<>();
// 构造方法,传入端口号,然后用super进行绑定
public UdpTranslateServer(int port) throws SocketException {
super(port);
map.put("add","添加");
map.put("delete","删除");
map.put("update","修改");
}
//字典
//本身实现的无意义的回显服务器,现在只需要修改其中的process就可以了
@Override
public String process(String str) {
return map.getOrDefault(str,"没有找到该词");
}
public static void main(String[] args) throws IOException {
// 创建对象,并且绑定端口号,这个类是上面我们自己定义的
UdpTranslateServer UdpTranslateServer = new UdpTranslateServer(1231);
UdpTranslateServer.start();
}
}
/**
* UDP实现回显服务器
*/
class UdpEchoServer {
// 这里是懒汉模式,只有当我们创建实例的时候,才进行对象的分配
private DatagramSocket socket = null;
//通过构造方法绑定端口
public UdpEchoServer(int port) throws SocketException {
// 这里就是我们前面讲的DatagramSocket对象的应用
socket = new DatagramSocket(port);
}
//启动服务器
public void start() throws IOException {
System.out.println("服务器已经启动了");
//读取socket发来的请求并解析
//构造方法(构造一个DatagramPacket来接收数据报,接收的数据保存到传入的数组中去)
while (true){
// 这里的数组大小我是随便指定的
// DatagramPacket就是我们前面说的数据包,
DatagramPacket requestPacket = new DatagramPacket(new byte[6666],6666);
//receive方法是从requestPacket这个套接字中接收数据报,并且写回到我们传入的参数中,然后没有结果就阻塞等待,也是前面说过的
socket.receive(requestPacket);
//String的构造方法:通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String
String str = new String(requestPacket.getData(),0,requestPacket.getLength());
//给数据执行对应的操作
// process是我们自己写的方法
String response = process(str);
//把响应写回给客户端
// 这句语句信息量较多,首先,第一个参数是数组,也就是响应数据,第二个是数据长度,第三个就是我们的SocketAddress
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
// 这里进行发送数据
socket.send(responsePacket);
//4.打印日志
System.out.printf("%s:%d req: %s;resp: %s \n",requestPacket.getAddress(),requestPacket.getPort(),str,response);
}
}
// 这里就是响应的具体实现,因为是回显服务器,就把传过来的字符串,原封不动的返回即可
public String process(String str){
return str;
}
}
2. 客户端的示例
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 王久实
* Date: 2022-10-15
* Time: 20:41
*/
//客户端方面就只需要发送数据即可,所以不管是回显,还是字典,客户端都是使用的同一段代码
public class UdpTranslateClient {
// 依旧是懒汉模式
private DatagramSocket socket = null;
// 服务器的IP
private String serverIP;
// 服务器的端口号
private int serverPort;
//构造方法,初始化,这个我们前面的文章中讲过,这里注意,端口号必须和服务器中的端口号对应
public UdpTranslateClient(String serverIP,int serverPort) throws SocketException {
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner sc = new Scanner(System.in);
// 这里用while的原因是因为要客户端可以多次发送请求
while(true){
// 这里是我们输入的请求
String request = sc.next();
// 这个还是构造需要发送的数据包
// 这里的InetAddress.getByName是把域名变成可访问的IP地址,比如"www.baidu.com"这种就会转换成对应的IP地址
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(this.serverIP)
,this.serverPort);
//发送数据
socket.send(requestPacket);
//接收响应并且解析
DatagramPacket responsePacket = new DatagramPacket(new byte[6666],6666);
// 这个是从这个套接字里面接收数据,并且返回到参数中去
socket.receive(responsePacket);
//解码
// 这里是把接收到的数据进行解码操作
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpTranslateClient client = new UdpTranslateClient("127.0.0.1",1231);
client.start();
}
}
6.TCP示例(实现词典功能)
1.服务器示例
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 王久实
* Date: 2022-10-18
* Time: 8:47
*/
public class TcpTranslateServer {
// 依旧是懒汉模式创建Socket
private ServerSocket listenSocket = null;
Map<String, String> map = new HashMap<>();
public TcpTranslateServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
map.put("cat", "猫咪");
map.put("dog", "小狗");
map.put("fish", "鱼");
}
public void start() throws IOException {
System.out.println("服务器已经启动");
// 创建一个service
ExecutorService service = Executors.newCachedThreadPool();
while (true) {
// 这个方法是监听窗口开始监听,前面也有提到,这里的端口号绑定是在上面的构造函数中已经绑定好的
Socket clientSocket = listenSocket.accept();
// 这里我们采用多线程的方式进行执行操作,因为我们的服务器不可能只同时给一台客户端服务,UDP不一样,UDP是不建立连接的,TCP要建立连接,所以
// 要处理多台设备同时连接的情况
// ExecutorService的submit方法是将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行
service.submit(new Runnable() {
@Override
public void run() {
// 这里就是调用字典获取响应的方法了
processConnection(clientSocket);
}
});
}
}
private void processConnection(Socket clientSocket) {
System.out.println("客户端上线");
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()) {
while (true) {
Scanner sc = new Scanner(inputStream);
// 说明客户端没有再输入了
if (!sc.hasNext()) {
System.out.println("客户端下线");
break;
}
// 这个是接收请求
String request = sc.next();
// 这里是通过下面的函数处理request请求,然后将返回的响应复制给response
String response = process(request);
// 这里就是建立输出流对象,将得到的response进行输出
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
// 这里是刷新缓存区
printWriter.flush();
System.out.printf("[%s:%d] req: %s res: %s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 处理发送过来的请求,并返回响应
public String process(String request) {
return map.getOrDefault(request, "没有找到该单词");
}
public static void main(String[] args) throws IOException {
TcpTranslateServer tcpTranslateServer = new TcpTranslateServer(1234);
tcpTranslateServer.start();
}
}
2.客户端示例
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 王久实
* Date: 2022-10-18
* Time: 8:48
*/
public class TcpTranslateClint {
// 懒汉模式
private Socket socket = null;
// 构造方法,传入服务器IP和服务器端口号,并且创建socket对象
public TcpTranslateClint(String serverIP,int serverPort) throws IOException {
socket = new Socket(serverIP,serverPort);
}
public void start(){
Scanner scanner = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
while(true){
// 输入请求
String request = scanner.next();
// 将请求放进IO输出流对象
PrintWriter printWriter = new PrintWriter(outputStream);
// 将请求通过IO对象输出
printWriter.println(request);
// 刷新缓存区
printWriter.flush();
// 创建接收结果的输入流
Scanner responseScanner = new Scanner(inputStream);
// 通过输入流将数据写入到结果集
String response = responseScanner.next();
// 打印结果集
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpTranslateClint tcpTranslateClint = new TcpTranslateClint("127.0.0.1",1234);
tcpTranslateClint.start();
}
}
7.结果执行展示
这里的两个结果是一样的,我这里就执行TCP的拿来做展示
注意这里需要先运行服务端,再执行客户端