草庐IT

【网络编程】网络编程概念,socket套接字,基于UDP和TCP的网络编程

良辰针不戳 2023-07-01 原文

前言:
大家好,我是良辰丫,今天我们一起来学习网络编程,网络编程的基本概念,认识套接字,UDP与TCP编程.💞💞💞

🧑个人主页:良辰针不戳
📖所属专栏:javaEE初阶
🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
💦期待大家三连,关注,点赞,收藏。
💌作者能力有限,可能也会出错,欢迎大家指正。
💞愿与君为伴,共探Java汪洋大海。

目录

1. 简述网络编程

1.1 关于网络编程

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

  • 进程A:编程来获取网络资源
  • 进程B:编程来提供网络资源

1.2 网络编程的相关概念

1.2.1 发送端与接收端

  • 发送端:数据的发送方的进程,称为发送端。发送端主机即网络通信中的源主机。
  • 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
  • 收发端:发送端和接收端两端,也简称为收发端。

1.2.2 客户端与服务器

接收服务的一方称为客户端.
提供服务的一方称为服务器.

服务器为客户端提供服务,客户端接收服务器的服务.

1.2.3 请求与响应

请求与响应是客户端与服务器交互的专业术语.

  • 客户端向服务器发起请求.
  • 服务器对客户端的请求做出响应.

2. socket套接字

  • Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程.
  • 程序员写程序主要编写的是应用程序,真正要发这个数据需要上层协议调用下层协议,应用层要调用传输层,传输层给应用层提供的一组api称为socket api.
  • 换句话说网络socket套接字相当于一组网络接口,在网络编程中,许多功能可以直接调用socket api来实现,非常的方便.

2.1 数据报套接字(UDP)

  • 数据报套接字是基于UDP协议的.
  • UDP协议全称为用户数据报协议.

UDP协议的特点

  • 无连接.使用UDP通信的双方,不需要刻意保存对端的信息.可以比作发短信,不用建立连接就能发送.
  • 不可靠传输.消息发了不一定收到,发短信,显示发了,但是对方不一定收到.
  • 面向数据报.以一个UDP数据报为基本单位.
  • 全双工.一条路径,双向通信.

2.2 流套接字(TCP)

  • 流套接字是基于TCP协议的.
  • TCP协议全称为传输控制协议.

TCP协议的特点

  • 有连接.使用TCP通信的双方,需要可以保存对方的相关信息.比如打电话需要互相建立连接,才能正常通信.
  • 可靠传输.建立连接之后才能通信,很大可能性确定对方收到自己的消息,比如打电话,一人一句,这就确保了可靠性.
  • 面向字节流.以字节为传输的基本单位,读写方式非常灵活.
  • 全双工.一条路径,双向通信.

3. 基于UDP的网络编程

3.1 网络编程的前提知识

  • Datagram就是"数据报",Socket表明自己是一个socket对象,这相当于对应到系统中一个特殊的文件(socket文件).
  • socket并非对应硬盘上某个数据存储空间,而是对应网卡这个硬件设备.

关于文件(操作系统的设计理念,一切皆文件) :

  • 广义上代指许多计算机中的软件/硬件资源.
  • 狭义上代指硬盘上的一块存储区域.

要想进行网络通信,就要有socket文件这样的对象,借助这个对象可以间接的操作网卡.

  • 往这个socket对象里面写数据,相当于通过网卡发送消息.
  • 在这个socket对象里面读数据,相当于通过网卡接收数据.

3.2 基于UDP的套接字

  • DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket 构造方法:

方法作用
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端)
DatagramSocket(intport)创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务端)

DatagramSocket 方法:

方法作用
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacketp)从此套接字发送数据报包(不会阻塞等待,直接发送)
void close()关闭此数据报套接字
  • DatagramPacket是UDP Socket发送和接收的数据报.
  • 构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

DatagramPacket 构造方法:

方法作用
DatagramPacket(byte[]buf, int length)构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacket(byte[]buf, int offset, int length,SocketAddress address )构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号.
  • 第一个版本,不需要设置地址,通常用来接收消息.
  • 第二个版本,需要显式的设置地址,通常用来发送消息.

DatagramPacket 方法:

方法作用
InetAddressgetAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

InetSocketAddress ( SocketAddress 的子类 )构造方法:

方法作用
InetSocketAddress(InetAddress addr, int port)创建一个Socket地址,包含IP地址和端口号
  • socket对象可以被客户端/服务器使用.
  • 服务器这边的socket需要关联一个端口号(不能变,因为要与客户端进行匹配)
  • 客户端这边不需要手动指定,系统自己分配就可以了.
  • socket也是文件,文件用完了需要关闭,要不然会资源泄露.

3.3 基于UDP的编程

一个最简单的回显服务器,客户端向服务器发了一个请求,服务器返回一个一毛一样的响应.

3.3.1 UDP服务器

  • 读取请求并解析
  • 根据请求计算响应(这块稍微比较复杂,实际工作中经常用到,但是咱们回显服务器非常简单)
  • 把响应返回到客户端.
package socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
//import java.nio.charset.StandardCharsets;

public class UdpEchoServer {
    // 需要先定义一个 socket 对象.
    // 通过网络通信, 必须要使用 socket 对象.
    private DatagramSocket socket = null;

    // 绑定一个端口, 不一定能成功!!
    // 如果某个端口已经被别的进程占用了, 此时这里的绑定操作就会出错.
    // 同一个主机上, 一个端口, 同一时刻, 只能被一个进程绑定.
    public UdpEchoServer(int port) throws SocketException {
        // 构造 socket 的同时, 指定要关联/绑定的端口.
        socket = new DatagramSocket(port);
    }

    // 启动服务器的主逻辑.
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            // 每次循环, 要做三件事情:
            // 1. 读取请求并解析
            //    构造空饭盒
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            //    食堂大妈给饭盒里盛饭. (饭从网卡上来的)
            socket.receive(requestPacket);
            //    为了方便处理这个请求, 把数据包转成 String
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 2. 根据请求计算响应(此处省略这个步骤)
            String response = process(request);
            // 3. 把响应结果写回到客户端
            //    根据 response 字符串, 构造一个 DatagramPacket .
            //    和请求 packet 不同, 此处构造响应的时候, 需要指定这个包要发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    // requestPacket 是从客户端这里收来的. getSocketAddress 就会得到客户端的 ip 和 端口
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    // 这个方法希望是根据请求计算响应.
    // 由于咱们写的是个 回显 程序. 请求是啥, 响应就是啥!!
    // 如果后续写个别的服务器, 不再回显了, 而是有具体的业务了, 就可以修改 process 方法,
    // 根据需要来重新构造响应.
    // 之所以单独列成一个方法, 就是想让同学们知道, 这是一个服务器中的关键环节!!!
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer udpEchoServer = new UdpEchoServer(9090);
        udpEchoServer.start();
    }
}

  • receive方法中,一般使用参数作为方法的"输入",用返回值作为方法的"输出",但有时候,使用参数也能作为返回值.
  • receive传一个空的packet对象,然后由方法内部把参数这个packet进行填充,这个时候packet就会有真的数据了,packet相当于一个购物车,填充东西后,购物车就不再空了.
  • 服务器启动后,调用start方法,立即执行到receive方法,如果这个时候,客户端没有发来数据,receive就会进入阻塞状态.
  • receive内部针对参数进行修改内容,外部也会生效.

3.3.2 UDP客户端

package socket;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIP;
    private int serverPort;

    // 客户端启动, 需要知道服务器在哪里!!
    public UdpEchoClient(String serverIP, int serverPort) throws SocketException {
        // 对于客户端来说, 不需要显示关联端口.
        // 不代表没有端口, 而是系统自动分配了个空闲的端口.
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.serverPort = serverPort;
    }

    public void start() throws IOException {
        // 通过这个客户端可以多次和服务器进行交互.
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 先从控制台, 读取一个字符串过来
            //    先打印一个提示符, 提示用户要输入内容
            System.out.print("-> ");
            String request = scanner.next();
            // 2. 把字符串构造成 UDP packet, 并进行发送.
            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);
            // 4. 把响应数据转换成 String 显示出来.
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.printf("req: %s, resp: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1", 9090);
        // UdpEchoClient udpEchoClient = new UdpEchoClient("42.192.83.143", 9090);
        udpEchoClient.start();
    }
}

服务器与客户端交互过程:

  • 服务器先启动,执行到receive进入阻塞状态.
  • 客户端执行后,从控制台读取数据,进行send.
  • 客户端在send之后,继续往下走,走到receive读取响应,会阻塞等待;服务器此时就从receive返回,读取请求数据(客户端发来的请求),走到process生成响应,再往下走到send,然后打印日志.
  • 客户端真正收到服务器send回来的数据后,就会解除阻塞,执行打印操作;服务器进入下一轮,再次阻塞在receive,等待客户端下一个请求.
  • 客户端继续进行下一轮循环,阻塞在控制台输入这里,等待用户输入新的数据.

看了上面过程,或许有点不清晰,那么咱们简化一下过程.

  • 客户端读取用户输入,然后构造请求并发送.
  • 服务器读取用户的请求,根据请求计算响应,把响应写回给客户端.
  • 客户端读取服务器的响应,把响应转化为字符串,然后打印出来.

注意:
系统自动给客户端分配空闲端口

3.3.3 基于UDP的字典服务器

package socket;

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("dog", "小狗");
        dict.put("cat", "小猫");
        dict.put("pig", "小猪");
    }

    @Override
    public String process(String request) {
        return dict.getOrDefault(request, "该单词没有查到!");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer udpDictServer = new UdpDictServer(9090);
        udpDictServer.start();
    }
}

4. 基于TCP的编程

4.1 前提知识

关于close.

  • close是一个释放资源的方法,当socket文件不再使用时,才会调用close释放资源.
  • 进程结束的时候就是资源释放的时候.

TCP两个核心的类

  • ServerSocket:给服务器使用
  • Socket:既会给服务器使用,也会给客户端使用.

4.1.1 ServerSocket API

ServerSocket 是创建TCP服务器的套接字的API.

ServerSocket 构造方法

方法作用
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

ServerSocket 方法:

方法作用
Socketaccept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭套接字

4.1.2 Socket API

  • Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
  • 不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket 构造方法:

方法作用
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket 方法:

方法作用
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回套接字的输入流
OutputStream getOutputStream()返回套接字的输出流

4.1.3 TCP的长短连接

TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
  • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

长短连接的区别:

  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等

4.2 TCP服务器

为了处理多个客户端,这里的accept方法采取多线程的方式.

  • 多线程.
  • 线程池.
package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    // serverSocket 就是外场拉客的小哥
    // clientSocket 就是内场服务的小姐姐.
    // serverSocket 只有一个. clientSocket 会给每个客户端都分配一个~
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        System.out.println("服务器启动!");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            // 如果直接调用, 该方法会影响这个循环的二次执行, 导致 accept 不及时了.
            // 创建新的线程, 用新线程来调用 processConnection
            // 每次来一个新的客户端都搞一个新的线程即可!!
//            Thread t = new Thread(() -> {
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
//            t.start();
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    // 通过这个方法来处理一个连接.
    // 读取请求
    // 根据请求计算响应
    // 把响应返回给客户端
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        // try () 这种写法, ( ) 中允许写多个流对象. 使用 ; 来分割
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 没有这个 scanner 和 printWriter, 完全可以!! 但是代价就是得一个字节一个字节扣, 找到哪个是请求的结束标记 \n
            // 不是不能做, 而是代码比较麻烦.
            // 为了简单, 把字节流包装秤了更方便的字符流~~
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                // 1. 读取请求
                if (!scanner.hasNext()) {
                    // 读取的流到了结尾了 (对端关闭了)
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                // 直接使用 scanner 读取一段字符串.
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端. 不要忘了, 响应里也是要带上换行的.
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            clientSocket.close();
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}

4.3 TCP客户端

package socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int port) throws IOException {
        // 这个操作相当于让客户端和服务器建立 tcp 连接.
        // 这里的连接连上了, 服务器的 accept 就会返回.
        socket = new Socket(serverIp, port);
    }

    public void start() {
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner scannerFromSocket = new Scanner(inputStream);
            while (true) {
                // 1. 从键盘上读取用户输入的内容.
                System.out.print("-> ");
                String request = scanner.next();
                // 2. 把读取的内容构造成请求, 发送给服务器.
                //    注意, 这里的发送, 是带有换行的!!
                //下面一行只是把数据写入内存的缓冲区中,等到缓冲区满了,才会           
                //真正写入网卡
                //因此在这里我们需要手动写入到网卡中
                printWriter.println(request);
                //手动刷新缓冲区
                printWriter.flush();
                // 3. 从服务器读取响应内容
                String response = scannerFromSocket.next();
                // 4. 把响应结果显示到控制台上.
                System.out.printf("req: %s; resp: %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过程:

  • 服务器启动,进入循环,执行到accept方法阻塞等待.
  • 客户端与服务器建立TCP连接,绑定ip和端口.
  • 客户端从控制端输入内容.(在此时,服务器也在监控者客户端,如果客户端下线就断开连接)
  • 客户端把读取的内容打包成请求,发给服务器.
  • 服务器读取请求,根据请求计算响应,把响应内容写回给客户端.
  • 客户端从服务器读取响应内容,然后打印在控制台.

小结TCP:

  • 服务器先运行start,执行到accept进行阻塞等待.
  • 客户端启动,会调用socket的构造方法,和服务器建立连接,连接成功后,服务器的accept就会返回.
  • 客户端往下执行控制台读取用户输入数据的时候也会阻塞等待.服务器就进入processConnection了.如果客户端没有发送请求的话,读取操作也会阻塞.
  • 当客户端输入内容的时候,客户端正常把请求发出去,这个时候,执行到读取服务器响应的时候再次进入阻塞状态.
  • 服务器收到客户端的请求之后,从next这里返回,执行到process与println,把响应写回给客户端.
  • 客户端收到服务器的响应,就可以把结果打印到控制台,然后再次进入循环,等待用户再次输入数据.
  • 服务器也会重新执行到循环位置,继续尝试读取请求,并进入阻塞状态.

有关【网络编程】网络编程概念,socket套接字,基于UDP和TCP的网络编程的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  4. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  7. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  8. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  9. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  10. ruby-on-rails - Ruby 的 'open_uri' 是否在读取或失败后可靠地关闭套接字? - 2

    一段时间以来,我一直在使用open_uri下拉ftp路径作为数据源,但突然发现我几乎连续不断地收到“530抱歉,允许的最大客户端数(95)已经连接。”我不确定我的代码是否有问题,或者是否是其他人在访问服务器,不幸的是,我无法真正确定谁有问题。本质上,我正在读取FTPURI:defself.read_uri(uri)beginuri=open(uri).readuri=="Error"?nil:urirescueOpenURI::HTTPErrornilendend我猜我需要在这里添加一些额外的错误处理代码...我想确保我采取一切预防措施来关闭所有连接,这样我的连接就不是问题所在,但是我

随机推荐