UDP和TCP网络编程Socket
前言
- 网络编程中,服务器的构造方法都需要手动指定端口号! 服务器手动指定的端口号是程序员可控的! 如果不指定端口号,系统自动分配端口号,在服务器中凌乱且会出现问题. 而客户端的端口号由客户端自行分配,因为客户端的端口号程序员无法得知. 0~1023端口号被用作历史著名的服务器,避开这些端口号分配端口.
- 网络编程可分 UDP 和 TCP 两种形式,将网络传输称为socket套接字
UDP协议代码
服务器
UDP使用数据报的形式传送数据, java标准库通过DatagramSocket类表示用于发送和接收数据报包的套接字。
以DatagramPacket封装一个数据报包; 由DatagramSocket对象发送和接收.
public class UDPServer {
private DatagramSocket socket = null; //由socket接收或发送数据报包裹
public UDPServer(int port) throws SocketException {
this.socket = new DatagramSocket(port);//手动指定服务器端口号
}
}
↓给一个成员方法start用于调用启动服务器.这个服务器不断的循环在接收请求
通过receive()方法接收数据报包裹,receive方法没有返回值. java在设计时,没有将receive接收到的数据用 ‘=’ 赋给字节数组, 而是拿一个字节数据报包来装载,承当传递的单位,这复合了UDP特点,并且这样使得包裹可以得到重复利用
receive接收不到请求是会阻塞等待的.
DatagramPacket 的对象 可以通过request.getData()拿到字节数组, 随后我们可以将其转换成字符串读取请求
通过sent()回复请求, 需要特别注意的是,UDP 传输是无连接的, 作为回复的包裹DatagramPacket,就必须手动指定IP和端口号
- 在UDP通信中,当客户端发送一个数据包给服务器时,客户端会将自己的IP地址和端口号放入数据包的头部(源地址和源端口)。当服务器通过receive方法接收到这个数据包时,这些信息会被自动解析并存储在接收到的DatagramPacket对象中。
void start() throws IOException {
System.out.println("服务器启动");
while(true){
//构造一个包裹,给字节缓冲区,并且设置读取的最大总长度
DatagramPacket request = new DatagramPacket(new byte[8192],8192);
//通过循环不断的接收请求
socket.receive(request);//将接收的请求数据放入这个包裹中(缓冲)存放
//拿到字节数组,然后转换成String
String requestString = new String(request.getData(),0, request.getLength());
String responseString = process(requestString);//通过process方法来处理业务逻辑
//像接收一样,需要一个字节包裹送出回复
//如果你写成这样,这个是用来接收数据的,一定要指定客户端的IP和端口号
//DatagramPacket response = new DatagramPacket(responseString.getBytes(),responseString.getBytes().length);
//当receive接收之后,IP和端口号就存在这个缓冲区了
DatagramPacket response = new DatagramPacket(responseString.getBytes(),responseString.getBytes().length,request.getSocketAddress());
//将包裹回复
socket.send(response);
System.out.printf("客户端IP和端口号%s",request.getSocketAddress());
Date date = new Date();
System.out.println(" 时间:" + date);
}
}
//具体处理业务逻辑
private String process(String requestString) {
return requestString;
}
客户端
客户端和服务器都是同用DatagramSocket
发送的接收的都是相同的. 服务器在请求的包裹中可以拿到IP地址和端口, 而客户端是先发送的一方,需要手动指定服务器IP和端口
public class UDPClient {
private DatagramSocket socket = null;
private String IP;
private int Port;
//无连接需要指定发送目的地IP和端口号
// 此处 ip 使用的字符串, 点分十进制风格. "192.168.2.100"
public UDPClient(String IP,int Port) throws SocketException {//任意分配端口号
this.socket = new DatagramSocket();
this.IP = IP;//存一下IP
this.Port = Port;//存一下端口号
}
public void start() throws IOException {
while(true){
//构造请求
System.out.print("输入'退出'可退出 ->");
Scanner scanner = new Scanner(System.in);
String requestString = scanner.next();
if(requestString.equals("退出"))
break;
//将请求装进包裹里面
//DatagramPacket request = new DatagramPacket(requestString.getBytes(),requestString.getBytes().length,IP,Port);
//IP控制台写入是String,传出需要改类型
DatagramPacket request = new DatagramPacket(requestString.getBytes(),requestString.getBytes().length, InetAddress.getByName(IP), Port);
socket.send(request);//将装在包裹里面的请求发出
//等待响应回复
//创建一个包来装回复
DatagramPacket response = new DatagramPacket(new byte[8192],8192);
socket.receive(response);
//将包裹里的字节数据转换成String
//String responseString = new String(response.getData(),0,response.getData().length);
String responseString = new String(response.getData(),0,response.getLength());//获取有效长度
System.out.println(responseString);
}
}
}
TCP 协议代码
-
TCP 协议传输没有以什么为单位,而是以流的形式来传输,因此,可以使用IO流来进行网络传输
-
TCP需要分类给服务器使用
ServerSocket是专门给服务器接收用户的,就好比如:- 刚下高铁站时,有很多DD的揽客员在揽客,
ServerSocket不断在接收客户端请求 - 现在你有需求了,向他提出,他就专门叫了总部给你分配一个DD司机,这个DD司机就如
Socket对象,专门负责处理你的请求
- 刚下高铁站时,有很多DD的揽客员在揽客,
-
客户端只使用
Socket类发送请求和接收回复
服务器
serverSocket对象揽客,然后分配 socket ,进入具体的流程
public class TCPFirst {
private ServerSocket serverSocket = null;
public void TCPServer(int port) throws IOException {
this.serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
while(true){ //循环接收客户端
Socket socket = serverSocket.accept(); //没有就会阻塞等待
System.out.println(socket.getInetAddress() + "连接成功,正在接收数据");
processingLogic(socket);//分配司机之后处理逻辑流程
}
}
和之前说的一样,接收和发送都用IOStream方式
PrintWriter 调用println时先将内容放置到了缓冲区,等到内容足够多才一并发送,flush()可以手动刷新
public void processingLogic(Socket socket) throws IOException {
//以流的方式接收请求和发送回复
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
//通过Scanner读取
Scanner scanner = new Scanner(inputStream);
//假设一个用户一直发送请求,那就要一直响应
while(scanner.hasNext()){
String requestString = scanner.next();
String responseString = process(requestString);
//可以这样写,但是没有换行在最后面
//outputStream.write(responseString.getBytes(), 0, responseString.getBytes().length);
//这里以Scanner的相对应PrintWriter写回复
PrintWriter writer = new PrintWriter(outputStream);
writer.println(responseString);
writer.flush();//刷新缓冲区
}
System.out.printf("客户端%s关闭,port:%s\n",socket.getInetAddress(),socket.getPort());
}
}
这里会发现,一开始的揽客不是并发的,在学习中,使用多线程将揽客的操作改成并发执行的.
但是在实际中,一个客户一个线程显然是不行的,一旦用户占据着线程不松手,线程无法释放,就资源不足以支撑其他的客户端, 实际中的解决方案采用IO多路复用,通过IO流走多条路径回复多客户端.
public void start(){
System.out.println("服务器启动!");
ExecutorService poll = Executors.newFixedThreadPool(6);//实际中不应该用线程
// 肯定是一直在找客户
while (true) {
poll.submit(() -> {
try (Socket socket = serverSocket.accept()) { //分配一个DD司机
System.out.println(socket.getInetAddress() + "连接成功,正在接收数据");
processingLogic(socket);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
客户端
和服务器逻辑基本相同
public class TCPClient {
private Socket socketClient = null;
public TCPClient(String IP,int port) throws IOException {
this.socketClient = new Socket(IP,port); //需要访问的IP和端口
}
public void start() throws IOException {
System.out.println("客户端启动");
try(InputStream inputStream = socketClient.getInputStream();
OutputStream outputStream = socketClient.getOutputStream()){
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String request = scanner.next();
PrintWriter writer = new PrintWriter(outputStream);
writer.println(request); //发送
writer.flush(); //刷新缓冲区
Scanner scannerStream = new Scanner(inputStream); //创建网卡读入流
String response = scannerStream.next(); //读入
System.out.println(response);
}
}
}
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)