JavaWebUDP/TCP 简单实现汉译英服务器与客户端
Posted Perceus
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaWebUDP/TCP 简单实现汉译英服务器与客户端相关的知识,希望对你有一定的参考价值。
@TOC
TCP 与 UDP 的 区别
有连接 与 无连接
可以怎么去理解?
有链接:像打电话
无连接:发微信
注:
- TCP,就是要求双发先建立连接,连接好了,才能进行传数据。
- UDP,直接传输数据,不需要双方建立连接
面向字节流 与 数据报
面向字节流
:数据是以字节为单位,进行传输
面向数据报
:以数据报为单位,进行传输
在代码中,这两者的区别是非常明显的!
通信五元组
- 源IP
- 源端口
- 目的IP
- 目的端口
- 协议类型
UDP Socket编程
TCP 和 UDP 协议中,只有 UDP 是面向数据报的。
那么 DatagramScoket 和 DatagramPacket 这两类,从名字就能看出来(Datagram-数据报),是关于UDP协议的类
UDP 主要接口
使用UDP实现汉译英 C/S
服务端
// 实现翻译回显C/S
public class MyUDPServer
// 对于一个服务器程序来说, 核心流程也是要分成两步.
// 1. 进行初始化操作 (实例化 Socket 对象)
// 2. 进入主循环, 接受并处理请求. (主循环就是一个 "死循环")
// a) 读取数据并解析
// b) 根据请求计算响应
// c) 把响应结果写回到客户端.
private DatagramSocket socket =null;
//map去存储我们的汉译英数据
private Map<String, String> map = new HashMap<>();
public MyUDPServer(int port) throws SocketException
//添加数据
initDates();
//服务器new socket对象的时候需要和一个ip地址和端口号绑定起来
//如果没有写ip 则默认时0.0.0.0 (一个特殊的ip会关联到这个主机的国有网卡的ip)
//socket对象本省就是一个文件
socket = new DatagramSocket(port);
// map初始化
private void initDates()
map.put("猫", "cat");
map.put("猪", "pig");
map.put("狗", "dog");
map.put("人", "people");
map.put("笔", "pen");
map.put("坐", "sit");
map.put("手", "hand");
map.put("腿", "leg");
//启动服务器
public void start() throws IOException
System.out.println("服务器启动");
// UDP 不用建立连接, 接受盛怒据即可
while(true)
//1. 接受客户端的请求
//2. 根据请求计算相应
//3. 把响应写回客户端
//这是一个接受数据的缓冲区 地址是接受数据的时候有内存填充
DatagramPacket datagramPacket= new DatagramPacket(new byte[4096],4096);
//程序启动会很快到达receive操作 如果客户端没有发送任何数据 此时receive操作会阻塞直到有客户端发送数据过来
//1.当整的有哭换端发送过来数据时 receive就会将数据保证到DategramPAcket对象的缓冲区里
socket.receive(datagramPacket);
//原本请求的数据时byte[]需要将其转换成String 并且如果发来的数据小于我们缓冲区的大小就会默认添加空格 我们得去掉无用空格
String request = new String(datagramPacket.getData(), 0, datagramPacket.getLength(),"UTF-8").trim();
//2.请求计算相应
String respond = process(request);
//把响应写回给客户端, 响应数据就是 response, 需要包装成一个 DatagramPacket 对象
//此时这个用于send 不仅需要指定缓冲区还不要忘记在Packet对象的最后加上请求数据包里的Socket地址
//填写ip和port还可以自己手动设置将ip和port分开写(如下面案例) 还可以直接定义InetAddress对象(里面包含ip和port)
DatagramPacket respondPacket = new DatagramPacket(respond.getBytes(),
respond.getBytes().length, datagramPacket.getSocketAddress());
// 3。发送数据
socket.send(respondPacket);
//打印请求访问日志
System.out.println(respondPacket.getAddress().toString() + " " + respondPacket.getPort() + " request: "
+ request + " respond: " + respond);
private String process(String request)
return map.getOrDefault(request, "未学习");
//一个主函数去设置该服务器的端口 并让其开始执行
public static void main(String[] args)
try
MyUDPServer myUDPServer = new MyUDPServer(9090);
try
myUDPServer.start();
catch (IOException e)
e.printStackTrace();
catch (SocketException e)
e.printStackTrace();
客户端
//客户端程序
public class MyUDPClient
//核心操作有俩步
//启动客户端的时候需要指定连接那台服务器
//执行任务主要流程分4步
// 1. 从用户这里读取输入的数据.
// 2. 构造请求发送给服务器
// 3. 从服务器读取响应
// 4. 把响应写回给客户端.
//需要客户端知道要发往哪台服务器的ip 和端口 还需要一个udp的连接对象
private String severIP = "127.0.0.1";
private int severPort = 9090;
private DatagramSocket socket = null;
//需要在启动客户端的时候来指定需要连接哪个服务器
public MyUDPClient(String severIP, int severPort) throws SocketException
this.severIP = severIP;
this.severPort = severPort;
//客户端在创建socket的时候不需要绑定端口号 但是服务器必须绑定端口号
//因为服务器绑定了端口号 客户端才能找到去访问它
//客户端不绑定是为了可以在一台主机上启动多个客户端
this.socket = new DatagramSocket();
public void start() throws IOException
Scanner scanner = new Scanner(System.in);
while (true)
//读取用户输入的消息
System.out.print("输入字符串->");
String request = scanner.nextLine();
if ("exit".equals(request))
break;
//发送请求
//注意ip和port要分开写并且前后位置要注意
DatagramPacket requstPacket = new DatagramPacket(request.getBytes(),
request.getBytes().length, InetAddress.getByName(this.severIP), this.severPort);
socket.send(requstPacket);
//接收服务器的响应
DatagramPacket respondPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(respondPacket);
String respond = new String(respondPacket.getData(), 0, respondPacket.getLength()).trim();
//显示响应
System.out.println(respond);
public static void main(String[] args)
try
//此时我们用于自己主机实验 127.0.0.1是一个特殊的ip(环回ip) 自己访问自己
//如果服务器和客户端在同一台主机上旧使用环回ip 如果不在同一台主机上就必须填写服务器的ip
//端口号必须与服务器的端口号一致
MyUDPClient client = new MyUDPClient("127.0.0.1", 9090);
try
client.start();
catch (IOException e)
e.printStackTrace();
catch (SocketException e)
e.printStackTrace();
效果:
TCP socket编程
ServerSocket 与 Socket
ServerSocket类
重要方法 accept()
- 三次握手完成后, 服务器调用accept()接受连接;
- 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
- Socket 是其返回值,代表网络的套接字
Socket类
简单的TCP服务器与客户端
服务端
public class TcpEchoServer
public ServerSocket serverSocket =null;
public TcpEchoServer (int port) throws IOException
serverSocket =new ServerSocket(port);
public void start() throws IOException
System.out.println("服务器启动");
while (true)
// 由于 TCP 是有连接的,不能一上来就读数据, 要先建立连接 (接电话)
// accept 就是在”接电话“,接电话的前提是,有人给你打~~, 如果没有客户端尝试建立连接, accept 就会阻塞
// accept 返回一个 socket 对象, 称为 clientSocket , 后续和客户端之间的沟通, 都是通过 clientSocket 完成的
Socket cilentSocket = serverSocket.accept();
processConnection(cilentSocket);
private void processConnection(Socket cilentSocket) throws IOException
//打印客户端信息
System.out.printf("[%s:%d] 客户端建立连接!!\\n",cilentSocket.getInetAddress().toString(),cilentSocket.getPort());
//处理请求和响应 全双工
try( InputStream inputStream = cilentSocket.getInputStream())
try(OutputStream outputStream = cilentSocket.getOutputStream())
//循环处理每个请求,返回响应
Scanner scanner =new Scanner(inputStream);
while(true)
//读取请求
if(!scanner.hasNext())
System.out.printf("[%s:%d] 客户端断开链接!!",cilentSocket.getInetAddress().toString(),cilentSocket.getPort());
break;
// 此处用Scanner 更方便
String request = scanner.next();
//根据请求计算响应
String response=process(request);
//为了方便使用 用 PrintWrite 把 OutputStream 包裹
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(response);
//刷新缓冲区,没有可能不能第一时间看见响应结果
printWriter.flush();
System.out.printf("[%s,%d] req:%s , resp:%s\\n",cilentSocket.getInetAddress().toString(),
cilentSocket.getPort(),request,response);
catch (IOException e)
e.printStackTrace();
private String process(String request)
return request;
public static void main(String[] args) throws IOException
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
客户端
public class TcpEchoClient
// 用普通的 Socket 即可,不用 ServerSocket 了
private Socket socket = null;
//此处也不用手动给客户端指定端口号,由系统自动分配(隐式)
public TcpEchoClient(String serverIP,int serverPort) throws IOException
// 其实这里是可以给定端口号的,但是这里给了之后,含义是不同的。
// 这里传入的 IP 与 端口号 的 含义: 表示的不是自己绑定,而是表示 和 这个IP 端口 建立连接
socket = new Socket(serverIP,serverPort);// 这里表示 与 IP 为serverIP的主机上的 端口为9090的程序,建立连接。
public void start()
System.out.println("和进服务器连接成功!");
Scanner sc = new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream())
try (OutputStream outputStream = socket.getOutputStream())
while(true)
//1、从控制台读取字符串
System.out.println("->");
String request = sc.next();
//2、根据读取的自妇产,构造请求,把请求发送给服务器
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(request);// 看似是一个输出语句,其实已经将数据写到服务器里面去了
printWriter.flush();// 记得 立即刷新缓冲区,确保 服务器 第一时间 感知到 请求。
//3、从服务器读取响应,并解析
Scanner scanner = new Scanner(inputStream);
String response = scanner.next();
//4、把结果显示到控制台上。
System.out.printf("request:%s,response:%s\\n ",request,response);
catch (IOException e)
e.printStackTrace();
public static void main(String[] args) throws IOException
TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
client.start();
效果
拓展:
多个客户端 与 服务器建立连接
虽然此时的 TCP代码已将跑起来了还是此处还存在一个很严重的问题!!!!
程池版本——TCP服务器
- 观察运行上述代码我们发现再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信.分析原因, 是因为我们accecpt了一个请求之后, 就在一直while循环尝试read, 没有继续调用到accecpt, 导致不能接受新的请求.我们当前的这个TCP, 只能处理一个连接, 这是不科学的
- 所以我们通过每个请求, 创建子进程的方式来支持多连接
- 但是还有问题当我们有很多连接的时候 线程就会疯狂的创建和销毁 所以结合前面所学我们可以使用线程池进行优化
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TCPThreadPoolServer
//去内核找连接
//处理这个连接对象
//获得socket的输入流
//处理输入流请求
//将响应写回到socket输出流
private ServerSocket serverSocket = null;
//构造函数需要给服务器绑定一个端口
public TCPThreadPoolServer(int port) throws IOException
this.serverSocket = new ServerSocket(port);
public void start() throws IOException
System.out.println("服务器启动");
//用一个线程池去解决多个多连接问题
ExecutorService executorService = Executors.newCachedThreadPool();
while (true)
Socket socket = serverSocket.accept();
executorService.execute(new Runnable()
@Override
public void run()
processSocket(socket);
private void processSocket(Socket socket)
System.out.printf("[%s : %d] 已上线\\n", socket.getInetAddress().toString(), socket.getPort());
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())))
while (true)
String get = bufferedReader.readLine();
String put = process(get);
bufferedWriter.write(put + "\\n");
//因为使用的是带缓冲区的buffer 所以一开始write是写入了缓冲区里 调用flush才可以将数据真正写到socket文件中
bufferedWriter.flush();
System.out.printf("[%s:%d] req: %s; resp: %s\\n", socket.getInetAddress().toString(),
socket.getPort(), get, put);
catch (Exception e)
System.out.printf("[%s : %d] 已下线\\n", socket.getInetAddress().toString(), socket.getPort());
private String process(String get)
return get.toUpperCase();
);
public static void main(String[] args)
try
TCPThreadPoolServer tcpThreadPoolServer = new TCPThreadPoolServer(9090);
tcpThreadPoolServer.start();
catch (IOException e)
e.printStackTrace();
以上是关于JavaWebUDP/TCP 简单实现汉译英服务器与客户端的主要内容,如果未能解决你的问题,请参考以下文章