IP地址(源IP,目的IP)
用来识别互联网上一台主机的位置
端口号(源端口,目的端口)
用来区分一台主机上的哪个应用程序
(占两个字节的整数)
五元组:
源IP,
目的IP
源端口,
目的端口
协议类型
通过一个五元组表示一个唯一的通信
Socket API(本质是一个文件,网卡的抽象)
Java标准库中提供了两种风格:
1.UDP DatagramSocket:面向数据报(发送接收数据,必须以一定的数据包为单位进行传输)
2.TCP ServerSocket:面向字节流
这两个是传输层中最重要的两个协议。
UDP服务器
现在以回显服务器为例(echo server)
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//对于一个服务器,核心流程为两步
//1.初始化操作(实例化Socket对象)
//2.进入主循环,接受并处理请求
//读取数据并解析
//根据请求计算响应
//吧响应结果返回到客户端
private DatagramSocket socket = null;
//绑定端口号
//构造Socket时如果没写IP,默认是0.0.0.0(特殊IP)会关联到这个主机的所有网卡IP
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true) {
//UDP socket发送接受数据的基本单位
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
//当客户端没发任何数据,receive就会阻塞
socket.receive(requestPacket);
//当有客户端数据过来了,receive就会把数据放到DatagramPacket对象的缓冲区
//吧数据转成String
//用户实际发送的数据可能远小于4096,而此处得到的长度就是4096,
//可以通过trim就可以去掉不必要的空白字符
String request = new String(requestPacket.getData(),
0,requestPacket.getLength()).trim();
String response = process(request);
//requestPacket.getSocketAddress()就是客户端的地址和端口
//response.getBytes().length得到的是字节数
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length,requestPacket.getSocketAddress());
socket.send(requestPacket);
System.out.printf("[%s:%d] req: %s;resp:%s\n",requestPacket.getAddress().toString(),
requestPacket.getPort(),request,response);
}
}
public String process(String request) {
//此处是回显服务器,请求啥响应内容就是啥
//如果是更复杂的服务器,此处就包含很多业务逻辑
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
//客户端分四步
//1.从用户这里读取数据
//2.构造请求发给服务器
//3.从服务器读取响应
//4.吧响应写回给客户端
private DatagramSocket socket = null;
private String serverIp;
private int serverPort;
//需要在启动客户端是来指定需要连接那个服务器
public UdpEchoClient(String serverIp,int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
//不需要绑定端口号,由操作系统自动分配一个空闲端口
//一个端口号通常只能被一个进程绑定
//服务器绑定了端口之后,客户端才能访问
//客户端如果绑定端口,一个主机只能启动一个客户端(通常不允许)
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("->");
String request = scanner.nextLine();
if (request.equals("exit")) {
break;
}
//构造请求
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String response = new String(requestPacket.getData(),0,requestPacket.getLength()).trim();
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
//这里的IP是特殊IP,环回IP,由于这里服务器和客户端在同一主机
//如果不在同一主机,要写成服务器IP
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
客户端服务器是为了跨主机通信
要把服务器部署到云服务器上
部署主要是两个步骤:
1.把代码打成jar包
2.把jar包拷贝到服务器上运行
用idea打jar包:
现在写一个翻译服务器来体会服务器里面process的业务逻辑,一般比较复杂的服务器,process里面的代码往往很复杂。
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
public class UdpDictServer extends UdpEchoServer {
private Map<String,String> dict = new HashMap<>();
public UdpDictServer(int port) throws SocketException {
super(port);
dict.put("cat","小猫");
dict.put("dog","小狗");
dict.put("bird","小鸟");
}
@Override
public String process(String request) {
return dict.getOrDefault(request,"这超出了我的知识范围");
}
public static void main(String[] args) throws IOException {
UdpDictServer server = new UdpDictServer(9090);
server.start();
}
}