• 【计算机网络】—网络编程(socket)02


    目录

    一、网络编程的概念

    二、UDP数据报套接字编程

    2.1 回显服务器代码

     2.2 翻译程序(英译汉)

    三、TCP数据报套接字编程

    3.1回显服务器

    3.2 翻译服务器


    关于synchronized使用和单例模式的原理和代码介绍(线程安全)

    一、网络编程的概念

    网络编程:指网络上的主机,通过不同的进程,以编程的方式实现网络通信(网络数据传输)

    发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念

    Socket套接字:由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。操作系统给应用程序提供的一组API。

    TCP的特点:

    • 有连接:需要连接成功才能发送数据(类似打电话)
    • 可靠传输:发送方知道接收方有没有收到数据(显示已读未读)
    • 面向字节流:以字节为单位传输(类似文件操作中的字节流)
    • 有接收缓冲区,也有发送缓冲区:
    • 全双工:一条通道,双向通信
    • 大小不限

    UDP的特点:

    • 无连接 :不需要接通,直接发数据(类似发微信)
    • 不可靠传输:发送方不知道数据是否已被对方接收到
    • 面向数据报:以数据报为单位传输()
    • 有接收缓冲区,无发送缓冲区:
    • 大小受限:一次最多传输64k
    • 全双工:一条通道,双向通信

    二、UDP数据报套接字编程

    UDP Socket 主要涉及两个类:DatagramSocket、DatagramPacket

    DatagramSocket对象,对应到操作系统的一个socket文件(文件除了普通文件,还包括硬件设备 / 软件资源)

    socket文件对应“网卡”这种硬件设备,从socket文件读写数据,就是读写网卡中的信息


    2.1 回显服务器代码

    构造一个回显服务器(请求和响应相同):包括客户端和服务器的代码

    (1)服务器端代码:

    构造函数中关于port的指定:服务器需要手动指定端口号port(客户端需要根据端口号访问服务器),而客户端会自动指定端口(不知道客户端已经被占用了哪些端口),由于客户端为主动发起请求的一方,因此需要知道服务器的地址+端口

    1. 启动服务器: start函数

    2.读取客户端发来的请求,并将其解析为一个字符串

    3.根据请求计算响应(是最复杂的过程):String response = process(request)

    4.把响应写回到客户端(需要先把数据构造成一个数据报) 

    1. import java.io.IOException;
    2. import java.net.DatagramPacket;
    3. import java.net.DatagramSocket;
    4. import java.net.SocketException;
    5. /*回显服务端的代码:
    6. 1.先指定端口号接收,便于指定进程处理该信息
    7. 2.启动服务器:读取请求(若读到,则需要返回)----->根据请求计算响应
    8. */
    9. public class UdpEchoServer {
    10. private DatagramSocket socket = null; //创建实例socket,进行网络编程的前提
    11. public UdpEchoServer(int port) throws SocketException { //构造方法传入端口进行绑定
    12. socket = new DatagramSocket(port);
    13. }
    14. // 1. 启动服务器
    15. public void start() throws IOException {
    16. System.out.println("启动服务器!");
    17. while (true) {
    18. //DatagramPacket把一个字节数组(byte[])进行包装
    19. // 2.读取客户端发来的请求,并将其解析为一个字符串
    20. DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);
    21. socket.receive(requestPacket); //接收数据(一个UDP数据报)
    22. String request = new String(requestPacket.getData(),0,requestPacket.getLength(),"UTF-8");
    23. //3. 根据请求计算相应(是最复杂的过程)
    24. String response = process(request);
    25. //4.把响应写回到客户端(需要先把响应 数据构造成一个数据报),同时指定IP和端口
    26. // response.getBytes()获取响应字节数组,response.getBytes().length获取字节数组长度;requestPacket.getSocketAddress():指定发回去的地址(也即是客户端)
    27. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
    28. requestPacket.getSocketAddress());
    29. socket.send(responsePacket);
    30. //打印日志 %s:打印字符串 %d:打印有符号位十进制整数
    31. String log = String.format("[%s:%d] req: %s; resp: %s",
    32. requestPacket.getSocketAddress().toString(),
    33. requestPacket.getPort(),
    34. request,response);
    35. System.out.println(log);
    36. }
    37. }
    38. //由于是回响服务器,响应和请求是一样的
    39. public String process(String request) {
    40. return request; //返回请求值
    41. }
    42. public static void main(String[] args) throws IOException {
    43. UdpEchoServer server = new UdpEchoServer(9090);
    44. server.start();//启动服务器
    45. }
    46. }

    (2)客户端代码:

    1.从控制台读取用户输入的信息

    2. 把读取的内容构造成一个UDP请求,并发送

    3.从服务器读取响应数据,并解析

    4.把响应结果显示到控制台

    1. import javax.sound.sampled.Port;
    2. import java.io.IOException;
    3. import java.net.*;
    4. import java.util.Scanner;
    5. /*服务器需要手动指定端口号,而客户端会自动指定端口,无需手动执行
    6. * */
    7. public class UdpEchoClient {
    8. private DatagramSocket socket = null;
    9. private String serverIP;
    10. private int serverPort;
    11. public UdpEchoClient(String ip,int port) throws SocketException { //不需要指定端口port
    12. socket = new DatagramSocket();
    13. serverIP = ip;
    14. serverPort = port;
    15. }
    16. public void start() throws IOException {
    17. Scanner in = new Scanner((System.in));
    18. while (true) {
    19. // 1.从控制台读取用户输入的信息
    20. System.out.println("->");
    21. String request = in.next();
    22. // 2. 把读取的内容构造成一个UDP请求(String数据内容+目的地服务器地址),并发送
    23. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
    24. InetAddress.getByName(serverIP),serverPort); //等价于InetSocketAddress,获取地址和IP
    25. socket.send(requestPacket);
    26. // 3.从服务器读取响应数据,并解析
    27. DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
    28. socket.receive(responsePacket);
    29. String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"utf-8");
    30. // 4.把响应结果显示到控制台
    31. System.out.printf("req: %s, resp: %s\n", request, response);
    32. }
    33. }
    34. public static void main(String[] args) throws IOException {
    35. UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090); //因为此处服务器在本机,所以指定的IP为本机环回IP
    36. client.start();
    37. }
    38. }

     开启多个客户端发送数据,按照下图进行配置

    服务器输出结果:

     

     2.2 翻译程序(英译汉)

     (1)客户端程序不变,如上

    (2)只需调整服务器代码,只要是修改process方法(根据请求处理响应)

    1. import java.io.IOException;
    2. import java.net.SocketException;
    3. import java.util.HashMap;
    4. public class UdpDictServer extends UdpEchoServer{
    5. private HashMap dict = new HashMap();
    6. public UdpDictServer(int port) throws SocketException { //插入构造方法
    7. super(port);
    8. dict.put("cat","小猫");
    9. dict.put("dog","小狗");
    10. }
    11. // 重写process
    12. @Override //注意重写的UdpEchoServer中的process方法不能是私有的
    13. public String process(String request) {
    14. return dict.getOrDefault(request,"该词无法被查询到");
    15. }
    16. public static void main(String[] args) throws IOException {
    17. UdpDictServer server = new UdpDictServer(9090);
    18. server.start();
    19. }
    20. }

    三、TCP数据报套接字编程

    • ServerSocket专门给TCP服务器使用
    • Socket给服务器和客户端使用

    3.1回显服务器

    1.读取请求

    2.根据请求计算响应

    3.把响应返回客户端

    (1)服务器代码

    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.util.Scanner;
    8. public class TcpEpochServer {
    9. private ServerSocket serverSocket = null; 定义一个变量表示服务器 listen 原意为监听
    10. public TcpEpochServer(int port) throws IOException { //服务器需要指定端口号
    11. serverSocket = new ServerSocket(port); //给服务器指定端口号
    12. }
    13. public void start() throws IOException {
    14. System.out.println("启动服务器!!");
    15. while (true) {
    16. //1.由于tcp是有连接的,需要先进行连接才能读数据 若无客户端与其建立连接,accept则会阻塞
    17. Socket clientSocket = serverSocket.accept(); //clientSocket完成后续操作
    18. processConnection(clientSocket);
    19. }
    20. }
    21. private void processConnection(Socket clientSocket) {
    22. System.out.printf("[%s:%d] 客户端建立连接", clientSocket.getInetAddress().toString(),clientSocket.getPort());
    23. try (InputStream inputStream = clientSocket.getInputStream()){
    24. try (OutputStream outputStream = clientSocket.getOutputStream()) {
    25. Scanner in = new Scanner(System.in);
    26. while (true) { //循环处理每个请求
    27. //1.读取请求
    28. if (!in.hasNext()) {
    29. System.out.printf("[%s:%d] 客户端建立连接", clientSocket.getInetAddress().toString(),clientSocket.getPort());
    30. break;
    31. }
    32. String request = in.next();
    33. //2.根据请求计算响应
    34. String response = process(request);
    35. //3.把响应返回客户端
    36. PrintWriter printWriter = new PrintWriter(outputStream);
    37. printWriter.println(response);
    38. printWriter.flush(); //刷新缓冲区
    39. System.out.printf("[%s:%d] req: %s,resp:%s\n",clientSocket.getInetAddress().toString(),
    40. clientSocket.getPort(),request,response);
    41. }
    42. }
    43. }catch (IOException e) {
    44. e.printStackTrace();
    45. }finally {
    46. try {
    47. clientSocket.close();
    48. }catch (IOException e) {
    49. e.printStackTrace();
    50. }
    51. }
    52. }
    53. public String process(String request) {
    54. return request;
    55. }
    56. public static void main(String[] args) throws IOException {
    57. TcpEpochServer server = =new TcpEpochServer(9090);
    58. server.start();
    59. }
    60. }

    (2)客户端代码(单个连接)

    1.从控制台读取字符串数据

    2.根据读取的数据构造请求,并把请求发送给服务器

    3.从服务器读取响应,并解析

    4.把结果显示到控制台上

    1. import java.io.IOException;
    2. import java.io.InputStream;
    3. import java.io.OutputStream;
    4. import java.io.PrintWriter;
    5. import java.net.Socket;
    6. import java.util.Scanner;
    7. public class TcpEpochClient {
    8. private Socket socket = null; //客户端不用指定IP,由系统自动分配即可
    9. public TcpEpochClient(String serverIP,int serverPort) throws IOException {
    10. socket = new Socket(serverIP,serverPort); //此处的端口号和IP表示的是服务器所有的,客户端通过超找到IP和端口进行连接
    11. }
    12. public void start() throws IOException {
    13. System.out.println("和服务器连接成功");
    14. Scanner in = new Scanner(System.in);
    15. try (InputStream inputStream = socket.getInputStream()) {
    16. try (OutputStream outputStream = socket.getOutputStream()) {
    17. while (true) {
    18. // 1.从控制台读取字符串数据
    19. System.out.print("- >");
    20. String request = in.next(); //请求为控制台输入内容
    21. // 2.根据读取的数据构造请求,并把请求发送给服务器
    22. PrintWriter printWriter = new PrintWriter(outputStream);//用·PrintWriter包装outputStream
    23. printWriter.println(request); //打印请求
    24. printWriter.flush(); //写入之后就需要刷新,否则服务器不能及时显示
    25. // 3.从服务器读取响应,并解析
    26. Scanner respScanner = new Scanner(inputStream); //读取输入请求的数据流
    27. String response = respScanner.next();
    28. // 4.把结果显示到控制台上
    29. System.out.printf("req: %s,resp: %s\n",request,response);
    30. }
    31. }
    32. }catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. public static void main(String[] args) throws IOException {
    37. TcpEpochClient client = new TcpEpochClient("127.0.0.1",9090);
    38. client.start();
    39. }
    40. }

    (2)客户端代码(建立多个连接)

    由于服务器部分while处包含嵌套循环,导致内部循环未结束便不能进行外部循环,导致服务器不能与多个客户端建立连接 。

    解决:主线程循环调用accept,有客户端连接时,则主线程创建一个新线程负责对客户端的若干请求(while循环处理请求)。由于多线程是并发执行的(宏观上是同时执行),由我们观察处理时就感觉到是各个线程独立执行,互不干扰,便就实现了多客户端连接。对如下代码进行改进即可。

    改进1:每次accept成功,就创建一个线程,由其负责执行客户端的请求

    1. public void start() throws IOException {
    2. System.out.println("服务器启动!!");
    3. while (true) { //此处包含两个循环(processConnection方法内部有一个循环)
    4. //1.由于tcp是有连接的,需要先进行连接才能读数据 若无客户端与其建立连接,accept则会阻塞
    5. Socket clientSocket = serverSocket.accept(); //clientSocket完成后续与客户端之间的沟通
    6. Thread t = new Thread(() ->{ //创建线程,使processConnection在线程内部执行
    7. processConnection(clientSocket);
    8. });
    9. t.start();
    10. }
    11. }

    改进2:每次accept成功,就创建线程池,由其负责执行客户端的请求

    1. public void start() throws IOException {
    2. System.out.println("服务器启动!!");
    3. ExecutorService pool = Executors.newCachedThreadPool(); //创建线程池
    4. while (true) { //此处包含两个循环(processConnection方法内部有一个循环)
    5. //1.由于tcp是有连接的,需要先进行连接才能读数据 若无客户端与其建立连接,accept则会阻塞
    6. Socket clientSocket = serverSocket.accept(); //clientSocket完成后续与客户端之间的沟通
    7. pool.submit(new Runnable() {
    8. @Override
    9. public void run() {
    10. processConnection(clientSocket);
    11. }
    12. });
    13. }
    14. }

     完整代码:

    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.util.Scanner;
    8. public class TcpThreadEpochServer {
    9. private ServerSocket serverSocket = null; //定义一个变量表示服务器
    10. public TcpThreadEpochServer(int port) throws IOException { //服务器需要指定端口号
    11. serverSocket = new ServerSocket(port); //给服务器指定端口号
    12. }
    13. public void start() throws IOException {
    14. System.out.println("服务器启动!!");
    15. while (true) { //此处包含两个循环(processConnection方法内部有一个循环)
    16. //1.由于tcp是有连接的,需要先进行连接才能读数据 若无客户端与其建立连接,accept则会阻塞
    17. Socket clientSocket = serverSocket.accept(); //clientSocket完成后续与客户端之间的沟通
    18. Thread t = new Thread(() ->{ //创建线程,使processConnection在线程内部执行
    19. processConnection(clientSocket);
    20. });
    21. t.start();
    22. }
    23. }
    24. private void processConnection(Socket clientSocket) {
    25. System.out.printf("[%s:%d] 客户端建立连接\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
    26. try (InputStream inputStream = clientSocket.getInputStream()){
    27. try (OutputStream outputStream = clientSocket.getOutputStream()) {
    28. Scanner in = new Scanner(System.in);
    29. while (true) { //循环处理每个请求
    30. if (!in.hasNext()) { //1.读取请求
    31. System.out.printf("[%s:%d] 客户端断开连接\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
    32. break;
    33. }
    34. String request = in.next();
    35. String response = process(request); //2.根据请求计算响应
    36. PrintWriter printWriter = new PrintWriter(outputStream); //3.把响应返回客户端
    37. printWriter.println(response);
    38. printWriter.flush(); //刷新缓冲区,可能导致客户端不能第一时间看到响应
    39. System.out.printf("[%s:%d] req: %s,resp:%s\n",clientSocket.getInetAddress().toString(),
    40. clientSocket.getPort(),request,response);
    41. }
    42. }
    43. }catch (IOException e) {
    44. e.printStackTrace();
    45. }finally {
    46. try {
    47. clientSocket.close(); //关闭操作
    48. }catch (IOException e) {
    49. e.printStackTrace();
    50. }
    51. }
    52. }
    53. public String process(String request) {
    54. return request;
    55. }
    56. public static void main(String[] args) throws IOException {
    57. TcpThreadEpochServer server = new TcpThreadEpochServer(9090);
    58. server.start();
    59. }
    60. }

    3.2 翻译服务器

    客户端内容不做改动,只需对服务器中的process方法改动即可(实现翻译的逻辑),让翻译客户端继承自上面的客户端即可。

    1. import java.io.IOException;
    2. import java.util.HashMap;
    3. public class TcpDictServer extends TcpEpochServer{ //继承之后重写process方法
    4. private HashMap map = new HashMap<>(); //创建一个HashMap用于接收翻译之间的映射关系
    5. public TcpDictServer(int port) throws IOException {
    6. super(port);
    7. map.put("cat","猫咪");
    8. map.put("dog","小狗");
    9. map.put("pig","小猪");
    10. map.put("apple","苹果");
    11. map.put("banana","香蕉");
    12. }
    13. @Override
    14. public String process(String request) {
    15. return map.getOrDefault(request,"当前语料库不足以翻译此词条");
    16. }
    17. public static void main(String[] args) throws IOException {
    18. TcpDictServer server = new TcpDictServer(9090);
    19. server.start();
    20. }
    21. }

  • 相关阅读:
    超级浏览器对跨境亚马逊防关联有用吗?
    目标检测YOLO实战应用案例100讲-高速铁路供电安全检测监测系统图像智能识别
    【UVA 101】 区块世界 The Blocks Problem or【POJ No.1208】
    uniapp H5预览PDF支持手势缩放、分页、添加水印、懒加载、PDF下载
    基于Ray的分布式版本的决策树与随机森林
    Alibaba 最终版 Java 面试小抄 + 架构师系统进阶指南
    智能家居涉及到的12个物联网传感器!
    三、静态路由实验
    EdrawMax思维导图,EdrawMax组织结构图
    深入剖析:如何使用Pulsar和Arthas高效排查消息队列延迟问题
  • 原文地址:https://blog.csdn.net/qq_45864756/article/details/127795311