首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

网络编程(TCP/UDP)

  • 25-02-21 19:41
  • 2589
  • 9958
blog.csdn.net

什么是网络编程

网络编程指的是,网络上的主机,不同的进程,通过网络的方式实现通信。同一个主机但是不同进程之间通信也是网络编程,但是我们主要编程对象是不同主机至今啊的通信。

网络编程实际上就是把传输层和应用层进行封装,然后利用java提供的api进行通过,代码的形式交给传输层然后进行通信。

网络编程的基本概念

客户端和服务器

  1. 客户端:发起通信的一方,实际上就是我们平时的应用之类的。
  2. 服务器:接受数据的一方,接收数据并进行处理。

客户端服务器的定义实际上是谁发起了通信,谁接受了数据。

请求和响应

请求:request,客户端给服务器发送的数据。

响应:response,服务器返回给客户端的数据。

Socket关键字

Socket是系统提供的方式用于网络通信,网络通信常常基于Socket关键字。

TCP和UDP是传输层的两个重要的协议。

TCP的特点

  • 有连接(必须要双方都接通了才能进行通信,需要三次握手四次挥手)。
  • 可靠传输(可以知道对方是否接收到了数据)。
  • 面向字节流(网络中的传输数据是字节模式,以字节为单位)。
  • 全双工(可以双向通信)。

UDP的特点

  • 无连接(就是类似于QQ直接发出去,无需等待对方建立连接)。
  • 不可靠传输(对方就算对方没有接收到,发送端也不知道有没有对方是否接收到)。
  • 面向数据报(单位是数据报)。
  • 全双工(可以双向通信)。

UDP编程:

1.DatagramSocket

Datagramsocket是UDP Socket的关键方法,用来发送和接受UDP数据。

构造方法:

重要方法:

2.DatagramPacket

DatagramPacket是UDP Socket发送数据的数据报(每次接收和发送数据的基本单位就是 数据报)。

构造方法:

3.UDP回显服务器

服务器和客户端都要指定一个端口号,但是一般服务器的端口号要显式指定,客户端不能显式指定,系统会自动分配。服务器需要把端口号明确下来,需要让别人找到。客户端的端口号不能指定,因为有可能被别人占用了(避免端口号冲突),交给系统分配。

服务器的端口号在程序员手里,服务器的哪些端口号被使用了,程序员都知道的。客户端在客户上面。一个服务器程序需要长时间运行。

new DatagramPacket()用来承载从网卡这边读到的数据,读到数据需要指定一个内存空间来保存这个数据。socket(网卡)读取数据,,并且保存到requestpacket里面。

receive会阻塞,直到客户端发送数据。

  1. package network;
  2. import java.io.IOException;
  3. import java.net.DatagramPacket;
  4. import java.net.DatagramSocket;
  5. import java.net.SocketException;
  6. public class UdpEchoServer {
  7. //服务器代码,创建一个DatagramSocket,后续操作网卡
  8. private DatagramSocket socket=null;
  9. public UdpEchoServer(int port) throws SocketException {
  10. this.socket = new DatagramSocket(port);
  11. //socket=new DatagramSocket();这是让系统分配的方法。
  12. }
  13. public void start() throws IOException {
  14. while (true){
  15. //读取请求并且解析
  16. DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
  17. //从网卡中读取数据,并且存储在packet中,有数据才会接受不然就会阻塞
  18. socket.receive(requestPacket);
  19. //拿到数据,并且放入到String中,取区间内的字节,构造成String,这里的getlength实际上不是4096,是收到的数据的真实长度
  20. String request=new String(requestPacket.getData(),0, requestPacket.getLength());
  21. //根据请求计算响应!!!!一般服务器最重要的
  22. String response=process(request);
  23. //将响应写回去
  24. //UDP是无连接的,每次都要指定数据要发给谁
  25. //构造数据报,需要指定数据内容,也要指定发给谁
  26. //不能直接getlength,获取字符为单位的如果都是英文单词,那字符字节一样,中午不一样,网络传输都是字节为单位
  27. DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
  28. socket.send(responsePacket);
  29. System.out.printf("[%s:%d] req=%s resp=%s",requestPacket.getAddress().toString(),responsePacket.getPort(),request,response);
  30. }
  31. }
  32. private String process(String request) {
  33. return request;
  34. }
  35. public static void main(String[] args) throws IOException {
  36. UdpEchoServer udpEchoServer=new UdpEchoServer(9090);
  37. udpEchoServer.start();
  38. }
  39. }

对于客户端,服务器的端口号可以由系统随机分配,但需要知道服务器的IP地址及端口号,不然就不知道发送数据给谁。

  1. 客户端发送数据
  2. 构造数据报通过socket发送给服务器
  3. 服务器进行读取并且返回给客户端
  4. 客户端输出发送的响应
  1. package network;
  2. import java.io.IOException;
  3. import java.net.*;
  4. import java.util.Scanner;
  5. public class UdpEchoClient {
  6. private DatagramSocket socket;
  7. private String address;
  8. private int port;
  9. public UdpEchoClient(String address, int port) throws SocketException {
  10. this.address = address;
  11. this.port = port;
  12. socket=new DatagramSocket();//这里表示服务器的随机端口创建
  13. }
  14. public void start() throws IOException {
  15. System.out.println("客户端启动");
  16. Scanner input=new Scanner(System.in);
  17. while (true){
  18. //没有输入数据的时候就跳出循环
  19. if(!input.hasNext()){
  20. break;
  21. }
  22. //读取所有的数据
  23. String request=input.next();
  24. //将内容构造成datagrampacket发送出去,并且需要找到对方的ip地址和端口号
  25. DatagramPacket datagramPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(address),port);
  26. //通过网卡进行发送数据报
  27. socket.send(datagramPacket);
  28. //读取服务器内容并且显示在客户端。
  29. DatagramPacket responseDatagramPacket=new DatagramPacket(new byte[4096],4096);
  30. socket.receive(responseDatagramPacket);
  31. String response=new String(responseDatagramPacket.getData(),0,responseDatagramPacket.getLength());
  32. System.out.println(response);
  33. }
  34. }
  35. public static void main(String[] args) throws IOException {
  36. UdpEchoClient udpEchoClient=new UdpEchoClient("127.0.0.1",9090);
  37. udpEchoClient.start();
  38. }
  39. }

但是实际上这个程序不能跨主机通信,如果想要实现跨主机通信,就要把程序部署到云服务器上面。

4.UDP翻译回显服务器

基于上述回显服务器,还可以实现出一些其他带有一点业务逻辑的服务器。

进行业务逻辑的修改实际上就是进行对回显服务器的继承,再实现更多的细节和代码。

上述的操作是在process的代码中实现的,我们只要进行继承然后重写方法就可以达到汉译英的效果了。

  1. package network;
  2. import java.io.IOException;
  3. import java.net.SocketException;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. public class UdpDireServer extends UdpEchoServer{
  7. private Map map;
  8. public UdpDireServer(int port) throws SocketException {
  9. super(port);
  10. map = new HashMap<>();
  11. map.put("cat", "小猫");
  12. map.put("bear", "小熊");
  13. }
  14. @Override
  15. public String process(String request) {
  16. if(map.get(request)!=null){
  17. return map.get(request);
  18. }
  19. return request;
  20. }
  21. public static void main(String[] args) throws IOException {
  22. UdpDireServer udpDireServer=new UdpDireServer(9090);
  23. udpDireServer.start();
  24. }
  25. }

观察一下运行结果,发现没有问题。

以上就是UDP的回显服务器的开发和运行了。

TCP编程:

1.ServerSocket

ServerSocket是创建TCP服务端Socket的API(只能给服务器使用)。

构造方法:

重要方法:

2.Socket

Socket 类用于创建客户端 Socket,或服务器端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket. (服务器端和客户端都能使用)

构造方法:

重要方法:

3.TCP回显服务器程序

TCP和UDP的区别就是TCP是有连接的,就和打电话一样需要一方接通另外一方才能进行通话。所以要等待客户端发起请求后,服务器确认接通之后,才可以进行通信,TCP的首要任务就是建立连接。

和UDP回显服务器一样,对于这里的服务器,同样需要指定端口号创建TCP服务器端Socket,即ServerSocket。

  1. 服务器启动之后,需要通过accept方法来监听当前端口。
  2. 成功建立连接之后,就可以返回一个Socket方法,这个对象保存了对端的信息,即客户端信息,可以用来接收和发送请求等(TCP是面向字节流),可以通过该方法来发送和接收数据。

后续流程和UCP回显服务器一致。此处由于每有一个客户端连接,就会有一个clientSocket,这里消耗的Socket会越来越多,因此每当一个客户端连接结束,就需要释放这个clientSocket。

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. import java.time.temporal.IsoFields;
  8. import java.util.Scanner;
  9. import java.util.concurrent.ExecutorService;
  10. import java.util.concurrent.Executors;
  11. public class TcpEchoServer {
  12. private ServerSocket serverSocket;
  13. public TcpEchoServer(int port) throws IOException {
  14. serverSocket=new ServerSocket(port);
  15. }
  16. public void start() throws IOException {
  17. System.out.println("服务器启动");
  18. //ExecutorService threadPool = Executors.newCachedThreadPool();
  19. while (true){
  20. while (true) {
  21. //监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息
  22. Socket clientSocket = serverSocket.accept();
  23. processConnection(clientSocket);
  24. }
  25. }
  26. }
  27. private void processConnection(Socket clientSocket) throws IOException {
  28. //返回ip地址和对应的端口
  29. System.out.printf("[%s:%d] 客户端上线\n", clientSocket.getInetAddress(), clientSocket.getPort());
  30. try(OutputStream outputStream=clientSocket.getOutputStream();
  31. InputStream inputStream=clientSocket.getInputStream()) {
  32. //不断读取输入的数据
  33. while (true){
  34. Scanner scanner=new Scanner(inputStream);
  35. if (!scanner.hasNext()) {
  36. System.out.printf("[%s:%d] 客户端下线\n",
  37. clientSocket.getInetAddress(), clientSocket.getPort());
  38. break;
  39. }
  40. //next是等到/n才结束,也就是用户输入换行(回车)
  41. String request=scanner.next();
  42. String response=process(request);
  43. PrintWriter printWriter=new PrintWriter(outputStream);
  44. printWriter.println(response);
  45. printWriter.flush();
  46. System.out.printf("[%s:%d] request:%s response:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);
  47. }
  48. }finally {
  49. clientSocket.close();
  50. }
  51. }
  52. private String process (String request) {
  53. return request;
  54. }
  55. public static void main(String[] args) throws IOException {
  56. TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
  57. tcpEchoServer.start();
  58. }
  59. }

对于客户端,需要指定服务器的IP和端口号建立连接。使用 Socket(String host, int port) 创建Socket的时候,就开始发起与对应服务器建立连接的请求了。

实际上TCP回显服务器和UDP很相似。,但是TCP是面向字节流。

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.InetAddress;
  6. import java.net.Socket;
  7. import java.net.UnknownHostException;
  8. import java.util.Scanner;
  9. public class TcpEchoClient {
  10. private Socket clientSocket;
  11. public TcpEchoClient(String serverAddress, int serverPort) throws IOException {
  12. clientSocket=new Socket(InetAddress.getByName(serverAddress),serverPort);
  13. }
  14. public void start() {
  15. try(InputStream inputStream=clientSocket.getInputStream();
  16. OutputStream outputStream=clientSocket.getOutputStream();
  17. Scanner scanner=new Scanner(System.in)) {
  18. while (true){
  19. System.out.print("->");
  20. String request=scanner.next();
  21. PrintWriter printWriter=new PrintWriter(outputStream);
  22. printWriter.println(request);
  23. printWriter.flush();
  24. //读取服务器发送的数据
  25. Scanner inputScanner=new Scanner(inputStream);
  26. String response=inputScanner.next();
  27. System.out.println(response);
  28. }
  29. } catch (IOException e) {
  30. throw new RuntimeException(e);
  31. }
  32. }
  33. public static void main(String[] args) throws IOException {
  34. TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9090);
  35. tcpEchoClient.start();
  36. }
  37. }

在这里我们需要先运行服务器,再运行客户端,通常服务器都需要先启动,不然客户端会因为连接不上服务器而报错。

实际上这里还存在一个问题。这里的服务器只能给先获取连接的客户端提供服务,如果其他客户端想访问则会失败。

分析过程:

  1. 第一个客户端连上服务器之后,服务器就会从accept这里返回(解除阻塞),然后进入到processConnection方法中。
    .接下来服务器就会在processConnection循环处理客户端的请求,只有当客户端退出之后,连接结束,才会退出循环。
  2. 而服务器在循环处理客户端请求的时候,第二个客户端发起连接请求,而服务器这里并不能执行到accept。因此并不能成功连接,只有当客户端退出,才会执行回到accept进行连接。

第二个客户端之前发的请求为什么能被立即处理?

  1. 当前TCP在内核中,每个 socket 都是有缓冲区的。客户端发送的数据通过客户端代码,已经写入到服务器的缓冲区了,这里数据确实发送了,只不过数据在服务器的接收缓冲区中。
  2. 一旦第一个客户端退出,回到第一层循环,执行accept连接操作,后续processConnection方法里的 next 就能把之前缓冲区的内容给读出来。

实际上可以通过多线程来解决此问题,为每个访问的客户端都创建一个线程,使其可以通过单独的线程来进行访问服务器。

4. 服务器引入多线程

  1. //多线程
  2. public void start() throws IOException {
  3. System.out.println("服务器启动!");
  4. while (true) {
  5. //监听当前绑定的端口,等待客户端连接 连接后,返回一个socket,里面保存客户端(对端)信息
  6. Socket clientSocket = serverSocket.accept();
  7. Thread t = new Thread(() -> {
  8. try {
  9. processConnection(clientSocket);
  10. } catch (IOException e) {
  11. throw new RuntimeException(e);
  12. }
  13. });
  14. t.start();
  15. }
  16. }

但是实际上,像这样频繁创建和销毁线程对服务器来说是一个不小的开销。

  • 每有一个客户端连接,就会创建一个新的线程,每当这个客户端结束,就要销毁这个线程。
  • 如果客户端比较多,并且频繁连接、关闭,就会使服务器频繁创建和销毁线程

因此我们使用了线程池。

5.服务器引入线程池

  1. public void start() throws IOException {
  2. System.out.println("服务器启动!");
  3. ExecutorService threadPool = Executors.newCachedThreadPool();
  4. while (true) {
  5. Socket clientSocket = serverSocket.accept();
  6. threadPool.submit(new Runnable() {
  7. @Override
  8. public void run() {
  9. try {
  10. processConnection(clientSocket);
  11. } catch (IOException e) {
  12. throw new RuntimeException(e);
  13. }
  14. }
  15. });
  16. }
  17. }

6.TCP字典服务器

  1. import java.io.IOException;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class TcpDictServer extends TcpEchoServer{
  5. Map map=new HashMap<>();
  6. public TcpDictServer(int port) throws IOException {
  7. super(port);
  8. map.put("cat","小猫");
  9. }
  10. @Override
  11. public String process(String request) {
  12. return map.getOrDefault(request,"未查找到单词");
  13. }
  14. public static void main(String[] args) throws IOException {
  15. TcpDictServer tcpDictServer=new TcpDictServer(9090);
  16. tcpDictServer.start();
  17. }
  18. }

注:本文转载自blog.csdn.net的看到我请叫我去刷leetcode的文章"https://blog.csdn.net/syx050403/article/details/144519870"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

124
嵌入式
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top