基础概念
客户端: 主动发起请求的一方,客户端给服务器发送的数据“请求”(Request)
服务器: 被动接受请求的一方,服务器给客户端发回的数据“响应”(Response)
服务器不知道什么时候会受到客户端请求,服务器只能一直准备等待7-24小时
通信中的五个概念
源IP: 发件人地址
32位的整数,用三个.分成四个部分,每一个部分一个字节(0-255)
源端口: 发件人姓名
0-65535之间,(占两个字节的整数),一个服务器需要关联一个固定的端口号
目的IP: 收件人地址
目的端口:收件人姓名
协议类型
IP是用来决定互联网上的某个主机的位置. 端口是决定数据交给这个主机.上的哪个程序.
Socket API
socket对象本质上是一个文件,文件是网卡的抽象
Java标准库中提供了两种风格
1、【UDP】DatagramSocket
面向数据报(发送接收数据,必须以一定的数据包为单位进行传输)
最简单的客户端服务器
客户端给服务器发送一个字符串, 服务器把这个字符串原封不动的返回. (回显服务器echo server)相当于服务器开发中的hello world
如果Packet用于receive,只指定缓冲区就可以,(地址是接收数据的时候由内核填充的)
如果这个Packet用户send,除了指定缓冲区,还需要指定发给谁(用户手动设定). 一种是直接设定InetAddress对象(里面同时包含了IP和port),还可以把IP和port分开设置.
2、【TCP】ServerSocket
面向字节流
服务器必须绑定端口
一个端口号通常情况下,只能被一个进程绑定
服务器绑定了端口之后,客户端才能访问. (买东西的时候需要填好你的收件人地址和收件人姓名,卖家才能发货)
客户端必须不绑定端口(由操作系统自动分配一个空闲端口)
客户端不能绑定的原因:如果客户端绑定了的话,一个主机上就只能启动一个客户端了
UDP协议
服务器
最简单的客户端服务器,客户端给服务器发送一个字符串,服务器原封不动返回(回显服务器 echoserver)
1、进行初始化,实例化Socket对象
2、进入主循环(死循环,一直接受请求)
a、读取数据并解析
b、根据请求计算响应
c、把结果写回到客户端
1、进行初始化,实例化Socket对象
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
//new这个Socket对象就会让当前的socket对象和一个ip地址以及一个端口号关联(绑定端口)
//未来客户端就按照这个ip+端口访问服务器
//如果在构造socket的时候没写ip默认是0.0.0.0(特殊ip会关联到这个主机的所有网卡)
}
new这个Socket对象就会让当前的socket对象和一个ip地址以及一个端口号关联(绑定端口),未来客户端就按照这个ip+端口访问服务器
如果在构造socket的时候没写ip默认是0.0.0.0(特殊ip会关联到这个主机的所有网卡)
2、进入主循环(死循环,一直接受请求)
a、读取数据并解析
b、根据请求计算响应
c、把结果写回到客户端
DatagramPacket这个类是udp发送接收数据基本单位
IP是用来决定互联网上的某个主机的位置. port是决定数据交给这个主机.上的哪个程序.
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//a. 读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//udp发送接收数据基本单位
//实例化中传入一个缓冲区
//socket对象本质上是一个文件,文件是网卡的抽象
socket.receive(requestPacket);//程序启动马上执行receive,读取从客户端收到的请求
//receive被堵塞,知道真的有数据来,receive就会把数据放入到刚刚new的缓冲区当中
//把收到的请求数据转成一个String(本来是一个byte[]),用trim()是为了去掉数组中的空白部分,提高效率
String request = new String(requestPacket.getData(),
0,requestPacket.getLength()).trim();
//b。根据请求计算响应
String response = process(request);
//c。把响应传回给客户端,传回的相应就是response变量,需要包装成Packet对象传回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length, requestPacket.getSocketAddress());
//利用String中的getBytes()方法把response构造成Packet对象,之后是数据的长度,
// 还有包要发送给谁,requestPacket.getSocketAddress(),获取客户端地址
//此处的地址就是客户端的地址和端口,这两个信息就包含在requePacket内部
//response.getBytes().length是字节数、response.getBytes().length()是字符数
socket.send(responsePacket);
//方便观察打印日志
System.out.printf("[%s:%d] 目的IP :%s; 目的端口号: %s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response );
}
}
public String process (String s){
//因为是echo server回显服务器,所以process方法只要接收然后直接返回就行,不需要其他操作
//如果更复杂,在这个方法里面可以包含更多的其他操作
return s;
}
服务器运行:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//1、进行初始化,实例化Socket对象
//2、进入主循环(死循环,一直接受请求)
//a、读取数据并解析
//b、根据请求计算响应
//c、把结果写回到客户端
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
//new这个Socket对象就会让当前的socket对象和一个ip地址以及一个端口号关联(绑定端口)
//未来客户端就按照这个ip+端口访问服务器
//如果在构造socket的时候没写ip默认是0.0.0.0(特殊ip会关联到这个主机的所有网卡)
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//a. 读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//udp发送接收数据基本单位
//实例化中传入一个缓冲区
//socket对象本质上是一个文件,文件是网卡的抽象
socket.receive(requestPacket);//程序启动马上执行receive,读取从客户端收到的请求
//receive被堵塞,知道真的有数据来,receive就会把数据放入到刚刚new的缓冲区当中
//把收到的请求数据转成一个String(本来是一个byte[]),用trim()是为了去掉数组中的空白部分,提高效率
String request = new String(requestPacket.getData(),
0,requestPacket.getLength()).trim();
//b。根据请求计算响应
String response = process(request);
//c。把响应传回给客户端,传回的相应就是response变量,需要包装成Packet对象传回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length, requestPacket.getSocketAddress());
//利用String中的getBytes()方法把response构造成Packet对象,之后是数据的长度,
// 还有包要发送给谁,requestPacket.getSocketAddress(),获取客户端地址
//此处的地址就是客户端的地址和端口,这两个信息就包含在requePacket内部
//response.getBytes().length是字节数、response.getBytes().length()是字符数
socket.send(responsePacket);
//方便观察打印日志
System.out.printf("[%s:%d] 目的IP :%s; 目的端口号: %s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response );
}
}
public String process (String s){
//因为是echo server回显服务器,所以process方法只要接收然后直接返回就行,不需要其他操作
//如果更复杂,在这个方法里面可以包含更多的其他操作
return s;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer = new UdpEchoServer(9090);//指定一个端口号
udpEchoServer.start();
}
}
服务器启动
服务器一直在等待客户端的请求
客户端
站在客户端角度理解此处通信的五元组
协议类型: UDP
源IP: 客户端的IP (客户端所在的主机IP)
源端口: 客户端的端口(操作系统自动分配的端口)
目的IP: 服务器的IP (服务器和客户端在同一个主机上. IP就是127.0.0.1)
目的端口: 9090 (服务器启动的时候绑定的端口)
构造,构造的时候需要传入要连接的服务器ip和端口
客户端的执行分四步
1、从用户读取输入数据
2、构造请求发送给服务器
3、从服务器读取响应
4、把响应写回给客户端
构造,构造的时候需要传入要连接的服务器ip和端口
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
//构造的时候需要传入要连接的服务器ip和端口
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();//构建客户端socket时不用绑定端口号
//服务器必须绑定端口,客户端必须不绑定端口(由操作系统自动分配一个空闲端口)
//如果客服端绑定,一个主机上就只能启动一个客户端了
}
客户端的执行分四步
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
//1、读取用户输入的数据
System.out.println("输入数据:");
String request = scanner.nextLine();
if(request.equals("退出")){
break;
}
//2、构造请求发送给服务器
//里面传入缓冲区,还有缓冲区长度,还有服务器ip 服务器端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);//发送请求
//3、从服务器读取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//response中放入请求
String response = new String(responsePacket.getData(),0,
responsePacket.getLength());
//4、显示响应数据
System.out.println(response);
}
客户端总代码
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
//构造,构造的时候需要传入要连接的服务器ip和端口
//客户端的执行分四步
//1、从用户读取输入数据
//2、构造请求发送给服务器
//3、从服务器读取响应
//4、把响应写回给客户端
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
//构造的时候需要传入要连接的服务器ip和端口
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();//构建客户端socket时不用绑定端口号
//服务器必须绑定端口,客户端必须不绑定端口(由操作系统自动分配一个空闲端口)
//如果客服端绑定,一个主机上就只能启动一个客户端了
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
//1、读取用户输入的数据
System.out.println("输入数据:");
String request = scanner.nextLine();
if(request.equals("退出")){
break;
}
//2、构造请求发送给服务器
//里面传入缓冲区,还有缓冲区长度,还有服务器ip 服务器端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);//发送请求
//3、从服务器读取响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
//response中放入请求
String response = new String(responsePacket.getData(),0,
responsePacket.getLength());
//4、显示响应数据
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
//127.0.0.1表示环回ip,自己访问自己,刚刚所写的服务器和客户端都是在一个主机上,所以使用这个ip
//如果不在同一个主机上,此处的ip就要写成要访问的服务器ip,9090就是上面写的服务器的端口号
udpEchoClient.start();
}
}