网络编程 ———— Java Socket(UDP/TCP 套接字)
Posted 爱敲代码的三毛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程 ———— Java Socket(UDP/TCP 套接字)相关的知识,希望对你有一定的参考价值。
文章目录
Socket是什么?
想知道Socket是什么就先得了解一下什么是网络编程
网络编程,通过代码来控制两个主机的进程之间能够进行数据交互。
操作系统就把网络编程的一些相关操作,封装起来了,提供了一组API供程序员使用。操作系统提供的功能,访问网络核心的硬件设备,网卡。网卡也是归操作系统来管理的
操作系统提供的socket api 是C语言风格的接口,在Java中是不能直接使用的。JDK其实也针对C语言这里的 socket API 进行了封装,在标准库中有一组类,这组类就能够让我们完成网络编程,这组类本质上仍然是调用的操作系统提供的socketAPI
操作系统,提供的 socket API主要有两类(实际上不止两类),它属于传输层
TCP/UDP
TCP和UDP这里只是简单说一下它们的特点,便于理解Socket编程,详细的会在后面的博客中写到
TCP
- 有连接
- 可靠传输
- 面向字节流
- 全双工
UDP
- 无连接
- 不可靠传输
- 面向数据报
- 全双工
有连接:类似于微信视频,需要接通才能说话
无连接:类似于发微信消息,直接发就好了
可靠传输:发送方能知道对方是否收到消息
不可靠传输:发送方不知道是不是收到了消息
注意:可靠性 != 安全性
面向字节流:
假设发送数据为1000个字节,可以一次性发10个字节重复发100次,也可以一次发100个字节,重复发送10次,可以非常灵活的完成这里的发送,接收也是同理
TCP的文件读写都是面向字节流的
面向数据报:
以一个一个的数据报为基本单位(每个数据报多大,不同的协议里面是有不同的约定的)
发送的时候,一次至少发送一个数据报,如果尝试发送一个半,实际可能只能发出去一个
接收的时候,一次至少接收一个数据,如果尝试接收半个,剩下半个就没了
全双工:双向通信,A和B可以同时向对方发送数据
半双工:单向通信,要么A给B发,要么B给A发,不能同时发
就类似于两根水管和一根水管的区别
套接字
一个服务器的核心流程
1. 读取请求并解析
2. 根据请求计算响应
3. 把响应写回客户端
一个客户端的核心流程
1. 根据用户输入,构造请求
2. 发送请求给服务器
3. 读取服务器的响应
4. 解析响应并显示
UDP套接字
DatagramSocket API
DatagramSocket API 是UDP Socket,用于发送和接收UDP数据报
DatagramSocket构造方法
方法名 | 说明 |
---|---|
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个端口号(一般用于客户端) |
DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端) |
DatagramSocket 方法
方法名 | 说明 |
---|---|
void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
void send(DatagramPacket p) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。
InetSocketAddress API
InetSocketAddress ( SocketAddress 的子类 )构造方法
方法名 | 说明 |
---|---|
InetSocketAddress(InetAddress addr, int port) | 创建一个Socket地址,包含IP地址和端口号 |
基于UDP实现回显服务器
回显服务器就是客户端发送什么请求服务器就返回什么请求,UDP是不需要建立连接的
服务器代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer
private DatagramSocket socket;
public UdpEchoServer(int port) throws SocketException
this.socket = new DatagramSocket(port);
private void start() throws IOException
System.out.println("服务器启动成功");
while (true)
// 1.读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
this.socket.receive(requestPacket);
String request = new String(requestPacket.getData());
// 2.根据请求计算响应
String response = process(request);
// 3.把响应返回给客户端
DatagramPacket responsePacket = new DatagramPacket(request.getBytes(),0,request.getBytes().length,
requestPacket.getSocketAddress());
this.socket.send(responsePacket);
// 4.打印日志
String log = String.format("[%s:%d] request: %s response: %s",requestPacket.getAddress().toString(),requestPacket.getPort(),
request,response);
System.out.println(log);
/**
* 这是一个回显服务器
* @param request
*/
private String process(String request)
//发送什么请求就返回什么响应
return request;
public static void main(String[] args) throws IOException
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
udpEchoServer.start();
客户端代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class UdpEchoClient
private DatagramSocket socket;
private int serverPort;
private String serverIp;
private InetSocketAddress inetSocketAddress;
public UdpEchoClient(int port,String ip) throws SocketException
//客户端的IP和端口号由操作系统自动分配
this.socket = new DatagramSocket();
this.serverPort = port;
this.serverIp = ip;
this.inetSocketAddress = new InetSocketAddress(this.serverIp, this.serverPort);
public void start() throws IOException
System.out.println("客户端启动成功");
Scanner sc = new Scanner(System.in);
while (true)
// 1.从键盘输入请求并构造
System.out.print("-> ");
String request = sc.nextLine();
if ("exit".equals(request))
String log = String.format("客户端退出[%s:%d]",this.serverIp,this.socket.getPort());
System.out.println(log);
break;
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,this.inetSocketAddress);
// 2.把请求发送给服务器
this.socket.send(requestPacket);
// 3.从服务器获取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
this.socket.receive(responsePacket);
String response = new String(responsePacket.getData());
// 4.打印日志
String log = String.format("[%s:%d] request: %s response: %s",this.serverIp,responsePacket.getPort(),request,response);
System.out.println(log);
public static void main(String[] args) throws IOException
UdpEchoClient udpEchoClient = new UdpEchoClient(9090,"127.0.0.1");
udpEchoClient.start();
运行结果
TCP套接字
TCP的套接字API和UDP是完全 不同的
ServerSocket API
ServerSocket 是创建TCP服务端Soket的API
ServerSocket构造方法
方法名 | 说明 |
---|---|
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
ServerSocket方法
方法名 | 方法说明 |
---|---|
Socket accept() | 开始监听指定端口(创建时绑定的端口),有客户端连接后,放回一个服务端Socket对象,并基于该Socket建立于客户端的连接,否则阻塞等待 |
void close() | 关闭该套接字,防止内存泄露 |
Socket API
Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,放回的服务端Socket
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来和对方收发数据的
Socket 构造方法
方法名 | 说明 |
---|---|
Socket(String host, int port) | 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接 |
Socket方法
方法名 | 说明 |
---|---|
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 放回此套接字的输入流 |
OutputStream getOutputStream() | 放回此套接字的输入流 |
TCP中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接
短连接: 每次收到数据并返回响应后,都关闭
长连接: 不关闭连接,一直保持连接状态,双方不停的收发数据,就是长连接,也就是说,长连接可以多次收发数据
对比长短 连接,两者区别如下
-
建立连接、关闭连接的耗时
短连接每次请求、响应都需要建立连接,关闭连接。而长连接至需要第一次连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是耗时的,长连接效率更高
-
主动发送请求不同
短连接一般是客户端主动向服务器发送请求,而长连接可以是客户端主动发送请求,也可以是服务端主动发
-
两者的使用场景不同
短连接适用于客户端请求频率不高的场景,入浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室、实时游戏等
基于TCP实现回显服务器
服务器代码
- 创建ServerSocket 对象指定端口号
- TCP套接字双方要先建立连接
- 使用Thread处理多个客户端的情况
- 每次一个Socket使用完后一定要关闭
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoServer
private ServerSocket listenSocket;
public TcpEchoServer(int port) throws IOException
this.listenSocket = new ServerSocket(port);
private void start() throws IOException
System.out.println("服务器启动成功");
while (true)
// TCP套接字先要建立连接
Socket socket = this.listenSocket.accept();
//用Thread来处理多个客户端的情况
Thread thread = new Thread(new Runnable()
@Override
public void run()
try
connectionProcess(socket);
catch (IOException e)
e.printStackTrace();
);
thread.start();
private void connectionProcess(Socket socket) throws IOException
try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream())
Scanner sc = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
String log = String.format("[%s:%d] 客户端上线",socket.getInetAddress(),socket.getPort());
System.out.println(log);
while (true)
// 1.读取请求并解析
if (!sc.hasNext())
log = String.format("[%s:%d] 客户端下线",socket.getInetAddress(),socket.getPort());
System.out.println(log);
break;
String request = sc.nextLine();
// 2.根据请求计算响应
String response = process(request);
// 3.把响应发给客户端
printWriter.println(response);
//加上flush刷新缓冲区
printWriter.flush();
// 4.打印日志
log = String.format("[%s:%d] request: %s response: %s",socket.getInetAddress(),socket.getPort(),
request,response);
System.out.println(log);
sc.close();
printWriter.close();
catch (IOException e)
e.printStackTrace();
finally
//使用后关闭,防止内存泄露
socket.close();
/**
* 回显服务器直接返回请求
* @param request
* @return
*/
private String process(String request)
return request;
public static void main(String[] args) throws IOException
TcpEchoServer server = new TcpEchoServer(9090);
server.start();
客户端代码
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TcpEchoClient
private Socket clientSocket;
private String serverIp;
private int serverPort;
public TcpEchoClient(int serverPort,String serverIp) throws IOException
this.clientSocket = new Socket(serverIp,serverPort);
this.serverPort = serverPort;
this.serverIp = serverIp;
public void start()
System.out.println("客户端启动成功");
Scanner sc = new Scanner(System.in);
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream())
Scanner responseSc = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true)
// 1.从键盘输入请求
System.out.print("-> ");
String request = sc.nextLine();
if ("exit".equals(request))
break;
// 2.发送请求给服务器
printWriter.println(request);
//加上flush刷新缓冲区
printWriter.flush();
// 3.从服务器获取响应
String response = responseSc.nextLine();
// 4.打印日志
String log = String.format("[%s:%d] request: %s response: %s",this.serverIp,this.serverPort
,request,response);
System.out.println(log);
responseSc.close();
printWriter.close();
catch (IOException e)
e.printStackTrace();
public static void main(String[] args) throws IOException
TcpEchoClient client = new TcpEchoClient(9090,"127.0.0.1");
client.start();
运行结果
下一篇 ————《UDP首部格式》
《新程序员》:云原生和全面数字化实践 50位技术专家共同创作,文字、视频、音频交互阅读以上是关于网络编程 ———— Java Socket(UDP/TCP 套接字)的主要内容,如果未能解决你的问题,请参考以下文章