目录
基本概念
发送端与接收端
请求与响应
编辑客户端与服务器
Socket套接字
分类
数据报套接字
流套接字传输模型
UDP数据报套接字编程
DatagramSocket API
DatagramPacket API
InetSocketAddress API
示例一:
示例二:
TCP流数据报套接字编程
ServerSocket API
Socket API
示例一:
网络编程指的是,网络上的主机的不同进程通过编程的方式实现网络通信.同一主机下只要满足不同进程间的通信就可以成为"网络通信"
在网络通信中:
作为发送数据的进程称为"发送端",发送端主机即网络通信中的"源主机"
作为接收数据的进程称为"接收端",接收端主机即网络通信中的"目的主机"
注意:网络通信中的发送端与接收端都是相对的.
一般来说,一次网络通信中设计到两次数据传输:
服务器:在网络通信下,提供服务的一端.(服务可以指:响应一定的要求)
客户端:获取服务的一端
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元.基于Socket套接字的网络程序开发就是网络编程.
套接字根据传输层协议主要分成:
数据报固定每次传输的字节,更像是写信,有来有回的.
面对的是字节流.
打电话一般,接通后就可以无节制的传输.
构造方法
方法签名 | 方法说明 |
DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口 (一般用于客户端) |
DatagramSocket(int port) |
创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用 于服务端) |
常用方法
方法 | 方法说明 |
void receive(DatagramPacket p) |
从此套接字接收数据报(如果没有接收到数据报,该方法会阻 塞等待) |
void send(DatagramPacket p) |
从此套接字发送数据报包(不会阻塞等待,直接发送) |
void close() | 关闭此数据报套接字 |
构造方法
方法签名 | 方法说明 |
DatagramPacket(byte[] buf, int length) |
构造一个DatagramPacket以用来接收数据报,接收的数据保存在 字节数组(第一个参数buf)中,接收指定长度(第二个参数 length) |
DatagramPacket(byte[] buf, int length, SocketAddress address) |
构造一个DatagramPacket以用来发送数据报,发送的数据为字节 数组(第一个参数buf)中,从0到指定长度(第二个参数 length)。address指定目的主机的IP和端口号 |
常用方法
方法签名 | 方法说明 |
InetAddress getAddress() |
从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取 接收端主机IP地址 |
int getPort() | 从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获 取接收端主机端口号 |
byte[] getData() | 获取数据报中的数据 |
InetSocketAddress是ScketAddress的一个子类,用来包装IP与端口号
方法 | 方法说明 |
InetSocketAddress(InetAddress addr, int port) | 创建一个Socket地址,包含IP地址和端口号 |
客户端像服务器发出请求,但服务器无响应版本
服务器:
public class UdpServer {
private DatagramSocket socket= null;
public UdpServer(int port) throws SocketException {//构造方法
this.socket = new DatagramSocket(port);
}
public void start() throws IOException {//作为启动服务器的方法
while(true){//因为不知道什么时候客户端会发送请求
//作为服务器,需要不停的接收客户端的请求
//创建packet
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);//用bytes作为接收,使用的长度为bytes的长度
System.out.println("等待接收数据中...");
socket.receive(packet);//还没收到之前会进行阻塞等待
//此处的版本没有作出响应
//我们可以打印出收到的packet中的数据看看有什么东西
System.out.println("IP: " + packet.getAddress().getHostAddress());
System.out.println("端口号: " + packet.getPort());
System.out.printf("文本数据为: " + new String(packet.getData()));
System.out.println("原始数据为: " + Arrays.toString(packet.getData()));
}
}
public static void main(String[] args) throws IOException {
UdpServer udpServer = new UdpServer(1024);
udpServer.start();
}
}
客户端:
方法一:
public class UdpClient {
public static void main(String[] args) throws IOException {
//创建Socket
DatagramSocket socket = new DatagramSocket();//创建一个socket,端口号为系统随机分配
//构建Packet
byte[] bytes = "Hello World".getBytes();//字符串转换成byte再塞进数组
SocketAddress address = new InetSocketAddress("localhost",1024);//目的IP为本地地址,端口号为1024
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,address);//构建packet
socket.send(packet);//发送
System.out.println("发送完成");
}
}
方法二:
public class UdpClient {
private DatagramSocket socket = null;//socket
private String serverIp;
private int serverPort;
public UdpClient(String serverIp,int serverPort) throws SocketException {
this.socket = new DatagramSocket();
this.serverIp = serverIp;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("输入:");
String text = scanner.next();
if(text.equals("exit")){
System.out.println("再见");
break;
}
//需要用InetAddress将字符串钟的IP转换成地址形式
//SocketAddress address = new InetSocketAddress("localhost",1024);//也可以创建一个实例进行包装IP与端口号
//此处的长度是字节的长度噢,注意单位
DatagramPacket packet = new DatagramPacket(text.getBytes(),text.getBytes().length,InetAddress.getByName(serverIp),serverPort);
socket.send(packet);
System.out.println("发送成功");
}
}
public static void main(String[] args) throws IOException {
UdpClient client = new UdpClient("127.0.0.1",1024);
client.start();
}
先启动服务器后启动客户端发送.
记得打开IDEA可以同时运行两个进程的选项噢!
服务器接收到的信息为:
做一个服务器对客户端有响应的版本
简单的英汉翻译
服务器:
public class UdpServerResponse{
private DatagramSocket socket= null;
public UdpServerResponse(int port) throws SocketException {//构造方法
this.socket = new DatagramSocket(port);
}
public void start() throws IOException {//启动服务器
while(true){
byte[] bytes = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes,bytes.length);//创建包来接收
System.out.println("等待接收数据中...");
socket.receive(receivePacket);//接收包
String request = new String(receivePacket.getData(),0,receivePacket.getLength());//根据接收到的包转换成字符串
String response = process(request);//对请求进行分析
//记得是getSocketAddress噢里面通常包含了IP与端口号
DatagramPacket sendPacket = new DatagramPacket(response.getBytes(),response.getBytes().length,receivePacket.getSocketAddress());
socket.send(sendPacket);//对客户端作出响应
System.out.println("客户端IP: " + receivePacket.getAddress());
System.out.println("客户端端口号: " + receivePacket.getPort());
System.out.println("收到的文本: " + request);
System.out.println("返回的文本: " + response);
}
}
public String process(String request){//解析请求,看看要做什么
//这里就做一个英汉翻译吧
HashMap map = new HashMap<>();
map.put("人","human");
map.put("猫","cat");
map.put("狗","dog");
return map.getOrDefault(request,"查阅失败");
}
public static void main(String[] args) throws IOException {
UdpServerResponse udpServerResponse = new UdpServerResponse(1024);
udpServerResponse.start();
}
}
客户端:
public class UdpClientResponse {
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
public UdpClientResponse(String serverIp,int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();
}
public void start() throws IOException {
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("输入: ");
String request = scanner.next();
if(request.equals("exit")){
System.out.println("再见!");
break;
}
//根据请求创建包
DatagramPacket sendPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName("127.0.0.1"),1024);
socket.send(sendPacket);
System.out.println("发送成功");
DatagramPacket receivePacket = new DatagramPacket(new byte[1024],1024);//创建接收包
socket.receive(receivePacket);
String receive = new String(receivePacket.getData(),0,receivePacket.getLength());
System.out.println(receive);
}
}
public static void main(String[] args) throws IOException {
UdpClientResponse udpClientResponse = new UdpClientResponse("127.0.0.1",1024);
udpClientResponse.start();
}
}
服务器的打印
客户端的打印
创建TCP服务端的API
构造方法
构造方法 | 方法说明 |
ServerSocket(int port) | 创建一个服务端流套接字Socket,并绑定到指定端口 |
方法
方法签 名 |
方法说明 |
Socket accept() |
开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待 |
void close() |
关闭此套接字 |
用来建立链接后保存对方的信息
构造方法:
方法 | 方法说明 |
Socket(String host, int port) |
创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接 |
常用方法
方法签名 | 方法说明 |
InetAddress getInetAddress() | 返回套接字所连接的地址 |
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
在TCP协议中的连接还分为长连接与短链接.
一请求一响应
此处为长连接(把代码里的while(true)去掉就是短连接啦!只进行一次请求响应)
服务器:
对于服务器来说,每次与客户端连接后会创建一个socket来暂时存储客户端的信息数据
断开连接后,记得要将这个存储客户端数据的socket进行close释放掉
在服务器进程中,一个客户端socket会占用文件描述符的一个位置,一个服务器可能会要与成千上万个客户端进行通信,不释放就会将文件描述符的位置沾满造成泄露.
而服务器的serverSocket的生命周期与整个进程相当,且只有一个.所以可以不进行释放
使用线程池,用多线程的方式来运行服务器达到同时与多个客户端进行通信的功能.
public class TcpServer {
private ServerSocket socket = null;
public TcpServer(int port) throws IOException {
socket = new ServerSocket(port);
}
public void start() throws IOException {
//尝试链接
ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个线程池,一个线程对应一个客户端进行通信
while (true) {
Socket clientSocket = socket.accept();//会阻塞等待接受
threadPool.submit(() -> {//向线程提供任务
try {
processConnect(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
public void processConnect(Socket clientSocket) throws IOException {
System.out.println("已与客户端进行链接-" + clientSocket.getInetAddress() + clientSocket.getPort());
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
while(true){
Scanner scanner = new Scanner(inputStream);//读
PrintWriter printWriter = new PrintWriter(outputStream);//写
if(!scanner.hasNext()){//客户端不再传输数据就断开链接
System.out.println("结束");
break;
}
String request = scanner.next();//接收请求
String response = process(request);//处理请求
printWriter.println(response);//向客户端写回响应
printWriter.flush();//记得写回后进行刷新缓冲区
System.out.println("响应:" + clientSocket.getInetAddress() + clientSocket.getPort() + "文本: "+ response);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
clientSocket.close();//记得要关闭
}
}
public String process(String request){
HashMap map = new HashMap<>();
map.put("人","human");
map.put("猫","cat");
map.put("狗","dog");
return map.getOrDefault(request,"查阅失败");
}
public static void main(String[] args) throws IOException {
TcpServer tcpServer = new TcpServer(1024);
tcpServer.start();
}
}
客户端:
public class TcpClient {
private Socket socket = null;
private String serverIp;
private int serverPort;
public TcpClient(String serverIp,int serverPort) throws IOException {
socket = new Socket(serverIp,serverPort);//客户端随机分配端口号
this.serverIp = serverIp;
this.serverPort = serverPort;
}
public void start(){
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
//创建流对象进行写与读
while(true){
Scanner scanner = new Scanner(System.in);//用来写入
PrintWriter printWriter = new PrintWriter(outputStream);//包装output流对象
String request = scanner.next();//写请求
if(request.equals("exit")){
System.out.println("结束与服务器连接");
break;
}
//把请求放到流对象中写出去
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 {
TcpClient tcpClient = new TcpClient("127.0.0.1",1024);
tcpClient.start();
}
}
服务器打印:
客户端打印: