【计算机网络】Linux环境中的网络套接字编程

news2024/11/28 7:34:14

文章目录

  • 前言
  • 一、预备知识
    • 理解源IP地址和目的IP地址
    • 认识端口号
    • 认识UDP协议和TCP协议
    • 了解网络字节序
  • 二、socket 套接字
    • socket 常见API
    • sockaddr 和 sockaddr_in
  • 三、UDP Socket编程
    • 封装UdpSocket
    • 实现UDP通用服务器
    • 实现英译汉服务器
    • 实现UDP通用客户端
    • 实现英译汉客户端
  • 四、地址转换函数
    • 字符串转in_addr的函数
    • in_addr转字符串的函数


前言

本编文章是博主学习了网络套接字编程后对相关知识的总结,阅读本文可对网络编程有基本的了解。文章内容包括认识IP地址和端口号的作用、了解网络字节序等网络编程中的基本概念、掌握Socket API的基本用法,从而能够实现简单的UDP客户端/服务器和TCP客户端/服务器,并且理解TCP服务器建立连接、发送数据、断开连接的基本流程。

一、预备知识

理解源IP地址和目的IP地址

IP地址被用来给Internet上的电脑一个编号。大家日常见到的情况是每台联网的PC上都需要有IP地址,才能正常通信。如果一台电脑上的数据要传输给另一台电脑,那么该电脑对应的IP地址就是源IP地址,而对于的目的电脑的IP地址就是目的IP地址。如果一台电脑与目的电脑进行通信,就必须知道目的IP地址,才能将信息发送给正确的对象。

认识端口号

知道了目的IP并不能建立双方之间的通信,两台主机通信的目的不是为了将数据发送给对方,而是为了访问目的主机的某一个访问。而目的主机上通常不仅仅只有一个服务,因此就引入了端口号进行区分。

所谓的端口,就好像是门牌号一样,客户端可以通过IP地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。

端口号的作用

端口号的主要作用是表示一台计算机中的特定进程所提供的服务。网络中的计算机是通过IP地址来代表其身份的,它只能表示某台特定的计算机,但是一台计算机上可以同时提供很多个服务,如数据库服务、FTP服务、Web服务等,我们就通过端口号来区别相同计算机所提供的这些不同的服务,如常见的端口号21表示的是FTP服务,端口号23表示的是Telnet服务端口号25指的是SMTP服务等。端口号一般习惯为4位整数,在同一台计算机上端口号不能重复,否则,就会产生端口号冲突这样的例外。

端口号的使用规则

TCP与UDP段结构中端口地址都是16比特,可以有在 0 ~ 65535 范围内的端口号。对于这65536个端口号有以下的使用规定:

  1. 端口号小于256的定义为常用端口,服务器一般都是通过常用端口号来识别的。任何TCP/IP实现所提供的服务都用1 ~ 1023之间的端口号,是由ICANN来管理的;端口号从1024—49151是被注册的端口,也成为“用户端口”,被IANA指定为特殊服务使用;
  2. 客户端只需保证该端口号在本机上是唯一的就可以了。客户端端口号因存在时间很短暂又称临时端口号;
  3. 大多数TCP/IP实现给临时端口号分配1024—5000之间的端口号。大于5000的端口号是为其他服务器预留的。

认识UDP协议和TCP协议

在TCP/IP网络体系结构中,TCP(传输控制协议,Transport Control Protocol)、UDP(用户数据报协议,User Data Protocol)是传输层最重要的两种协议,为上层用户提供级别的通信可靠性。

传输控制协议(TCP)
TCP(传输控制协议)定义了两台计算机之间进行可靠的传输而交换的数据和确认信息的格式,以及计算机为了确保数据的正确到达而采取的措施。协议规定了TCP软件怎样识别给定计算机上的多个目的进程如何对分组重复这类差错进行恢复。协议还规定了两台计算机如何初始化一个TCP数据流传输以及如何结束这一传输。TCP最大的特点就是提供的是面向连接、可靠的字节流服务

用户数据报协议(UDP):
UDP(用户数据报协议)是一个简单的面向数据报的传输层协议。不提供可靠性,也不提供报文到达确认、排序以及流量控制等功能。它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。因此报文可能会丢失、重复以及乱序等。但由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。UDP最大的特点就是提供的是非面向连接的、不可靠的数据流传输

了解网络字节序

通过以前对C语言的学习,我们知道了内存中的多字节数据相对于内存地址有大端和小端之分(大端和小端),磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。当然,网络数据流中也同样有大小端之分。

以下是对网络数据流的地址定义

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机从网络中接收到的数据依次保存到缓冲区中,也同样按照内存地址从低到高的顺序保存;
  • 由此可以保证网络数据流的地址都是:先发出的数据在低地址处,后发出的数据在高地址处;
  • TCP/IP协议规定,网络数据流采用大端字节序,即低地址高字节;
  • 不管当前主机是大端机还是小端机,都会按照TCP/IP协议所规定的网络字节序进行接收就发送数据;
  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可。

数据采用大小端写入内存的区别:

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转
换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  • 这些函数名很好记,h表示host;n表示network;l表示32位长整数;s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

二、socket 套接字

socket 常见API

#include <sys/socket.h>

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX Domain Socket等。然而,各种网络协议的地址格式并不相同。

sockaddr 和 sockaddr_in

struct sockaddrstruct sockaddr_in 这两个结构体用来处理网络通信的地址。

1、sockaddr

sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了。其结构如下:

/* POSIX.1g specifies this type name for the `sa_family' member.  */
typedef unsigned short int sa_family_t;
 
/* This macro is used to declare the initial common members
   of the data types used for socket addresses, `struct sockaddr',
   `struct sockaddr_in', `struct sockaddr_un', etc.  */
 
#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family
  
 
/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
    char sa_data[14];		/* Address data.  */
  };
struct sockaddr {  
     sa_family_t sin_family;//地址族
    char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
   }; 

2、sockadd_in
sockaddr_in在头文件#include<netinet/in.h>#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把portaddr 分开储存在两个变量中,其结构定义如下:

/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */
 
    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr)
			   - __SOCKADDR_COMMON_SIZE
			   - sizeof (in_port_t)
			   - sizeof (struct in_addr)];
  };

sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

虽然socket API中的接口是sockaddr,但是我们真正在基于IPv4编程时,使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型、端口号、IP地址。

3、sockaddr 和 sockaddr_in的结构

  • IPv4和IPv6的地址格式定义在头文件netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址。
  • IPv4、IPv6地址类型分别定义为常数AF_INETAF_INET6, 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
  • socket API 可以都用struct sockaddr *类型表示,在使用的时候需要强制转化成sockaddr_in, 这样的好处是程序的通用性,既可以接收IPv4、IPv6、也可以接收UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

4、总结

二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr

sockaddr常用于bindconnectrecvfromsendto等函数的参数,指明地址信息,是一种通用的套接字地址。
sockaddr_ininternet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作,使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数。即sockaddr_in用于socket定义和赋值;而sockaddr用于函数参数。

用法举例:

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
int main(int argc,char **argv)
{
    int sockfd = 0;
    struct sockaddr_in addr_in;
    struct sockaddr * addr;
 
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  //获得fd
    bzero(&addr_in,sizeof(addr_in));  // 初始化结构体
    /*
     8008的主机字节序  小端字节序 0001 1111 0100 1000 = 8008
     8008的网络字节序  大端字节序 0100 1000 0001 1111 = 18463
    */
    addr_in.sin_port = htons(8008);
    addr_in.sin_family = AF_INET;  // 设置地址家族
    addr_in.sin_addr.s_addr = inet_addr("192.168.3.30");  //设置地址
    printf("sockaddr_in.sin_addr.s_addr = %d \n", addr_in.sin_addr.s_addr);
    printf("addr = %s \n", inet_ntoa(addr_in.sin_addr));
//    addr_in.sin_addr.s_addr = htonl(INADDR_ANY);  //设置地址
 
    printf("struct sockaddr size = %ld \n", sizeof (addr));
    printf("struct sockaddr_in size = %ld \n", sizeof (addr_in));
    addr = (struct sockaddr *)&addr_in;
//    bind(sockfd, (struct sockaddr *)&addr_in, sizeof(struct sockaddr));  /* bind的时候进行转化 */
    bind(sockfd, addr, sizeof(struct sockaddr));
    
    ... ...
    
    return 0;
}

其中inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr的初始化。inet_ntoa()作用是将一个sin_addr结构体输出成IP字符串(network to ascii)。

三、UDP Socket编程

这里我们使用UDP套接字编程实现一个简单的英译汉服务器。

封装UdpSocket

udpSocket.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class udpSocket
{
public:
    udpSocket() : _fd(-1) {}

    ~udpSocket(){}

public:
    // 创建套接字
    bool Socket()
    {
        _fd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET表示采用IPv4, SOCK_DGRAM表示采用udp协议
        if (_fd < 0)
        {
            std::cerr << "create socket error!" << std::endl;
            return false;
        }

        return true;
    }

    // 关闭套接字
    bool Close()
    {
        close(_fd);
        return true;
    }

    // Bind套接字相关信息
    bool Bind(const std::string &ip, uint16_t port)
    {
        sockaddr_in addr;
        // 填充协议家族
        addr.sin_family = AF_INET;
        // 填写端口号,port会通过网络发送给客户端
        addr.sin_port = htons(port);

        // 服务器必须有IP地址,“xx.yy.zz.aaa”,字符串风格的点分十进制 -》4字节IP -> uint32_t IP
        // INADDR_ANY(0): 不关心会bind到哪个IP,bind任意IP,服务器一般的做法
        // inet_addr指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n
        addr.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());

        // bind 网络信息
        // struct sockaddr_in填充信息, struct sockaddr充当函数参数
        if (bind(_fd, (const sockaddr *)&addr, sizeof addr) == -1)
        {
            std::cerr << "bind error!" << std::endl;
            return false;
        }
        return true;
    }

    // 接收数据
    bool RecvFrom(std::string *buf, std::string *ip = nullptr, uint16_t *port = nullptr)
    {
        char inbuf[1024 * 10];
        sockaddr_in peer;
        socklen_t len = sizeof(peer);

        // 将接收的数据读入到inbuf缓冲区中,并且获取客户端的sockaddr_in信息
        ssize_t read_size = recvfrom(_fd, inbuf, sizeof(inbuf) - 1, 0, (sockaddr *)&peer, &len);
        if (read_size < 0)
        {
            std::cerr << "recvfrom error!" << std::endl;
            return false;
        }

        // 将缓冲区中的内容放到输出型参数中
        buf->assign(inbuf, read_size);
        if (ip != nullptr)
        {
            *ip = inet_ntoa(peer.sin_addr);
        }
        if (port != nullptr)
        {
            *port = ntohs(peer.sin_port);
        }
        return true;
    }

    // 发送数据
    bool SendTo(const std::string &buf, const std::string &ip, uint16_t port)
    {
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());

        //发送数据给客户端
        ssize_t write_size = sendto(_fd, buf.c_str(), buf.size(), 0, (const sockaddr *)&addr, sizeof(addr));
        if (write_size < 0)
        {
            std::cerr << "sendto error!" << std::endl;
            return false;
        }
        return true;
    }

private:
    int _fd;
};

实现UDP通用服务器

udpServer.hpp

#pragma once
#include "udpSocket.hpp"

// C++11 能够兼容函数指针、仿函数和Lambda表达式
#include <functional>
typedef std::function<void (const std::string &, std::string* resp)> Handler;

class udpServer
{
public:
    udpServer()
    {
        assert(_sock.Socket());
    }
    ~udpServer()
    {
        assert(_sock.Close());
    }

public:
    bool Start(const std::string &ip, uint16_t port, Handler handler)
    {
        // 绑定IP 和 端口号

        if (!_sock.Bind(ip, port))
        {
            return false;
        }

        while (true)
        {
            // 尝试读取请求
            std::string req;
            std::string client_ip;
            uint16_t client_port;
            if (!_sock.RecvFrom(&req, &client_ip, &client_port))
            {
                continue;
            }

            std::string resq;
            //根据请求获得响应
            handler(req, &resq);

            //返回相应给客户端
            _sock.SendTo(resq, client_ip, client_port);
            //debug
            printf("[%s:%d] req: %s, resq: %s\n", client_ip.c_str(), client_port, req.c_str(), resq.c_str());
        }
        _sock.Close();
        return true;
    }

private:
    udpSocket _sock;
};

实现英译汉服务器

dict_server.cpp

#include <iostream>
#include <string>
#include <unordered_map>
#include "udpServer.hpp"

std::unordered_map<std::string, std::string> g_dict;

void Translate(const std::string &req, std::string *resp)
{
    auto it = g_dict.find(req);
    if (it == g_dict.end())
    {
        std::cout << "未查找到!" << std::endl;
        return;
    }
    *resp = it->second;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("Usage: ./dict_server [ip] [port]\n");
        return 1;
    }

    // 数据初始化
    g_dict.insert(std::make_pair("hello", "你好"));
    g_dict.insert(std::make_pair("world", "世界"));
    g_dict.insert(std::make_pair("c++", "最好的编程语言"));
    g_dict.insert(std::make_pair("byte", "字节"));

    // 启动服务器
    udpServer().Start(argv[1], atoi(argv[2]), Translate);

    return 0;
}

实现UDP通用客户端

udpClient.hpp

#pragma once

#include "udpSocket.hpp"

class udpClient
{
public:
    udpClient(const std::string &ip, uint16_t port)
        : _ip(ip), _port(port)
    {
        assert(_sock.Socket());
    }
    ~udpClient() { _sock.Close(); }
public:
    bool RecvFrom(std::string* buf)
    {
        return _sock.RecvFrom(buf);
    }

    bool SendTo(const std::string& buf)
    {
        return _sock.SendTo(buf, _ip, _port);
    }
private:
    udpSocket _sock;
    // 服务器的IP和端口号
    std::string _ip;
    uint16_t _port;
};

实现英译汉客户端

client.cpp

#include <iostream>
#include <string>
#include "udpClient.hpp"

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("Usage: ./dict_server [ip] [port]\n");
        return 1;
    }

    udpClient client(argv[1], atoi(argv[2]));
    while(true)
    {
        std::string word;
        std::cout << "请输入您要查找的单词:";
        if(!(std::cin >> word))
        {
            std::cout << "end..." << std::endl;
            break;
        }

        client.SendTo(word);

        std::string res;
        client.RecvFrom(&res);
        std::cout << word << "的意思是:" << res << std::endl;
    }

    return 0;
}

运行结果:

四、地址转换函数

sockaddr_in中的成员struct in_addr sin_addr表示32位的IP地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换。

字符串转in_addr的函数

#include <arpa/inet.h>

int inet_aton(const char* strptr, struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family, const char* strptr, void* addrptr);

in_addr转字符串的函数

char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);

其中inet_ptoninet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void* addrptr

代码样例:

#include <iostream>
#include <cstdio>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
    sockaddr_in addr;
    inet_aton("127.0.0.1", &addr.sin_addr);
    
    uint32_t* ptr = (uint32_t*)(&addr.sin_addr);
    printf("addr: %x\n", *ptr);
    
    printf("addr_str: %s\n", inet_ntoa(addr.sin_addr));
    
    return 0;
}

运行结果:
在这里插入图片描述

关于inet_ntoa
inet_ntoa这个函数返回了一个char*类型的指针,很显然是这个函数自己在内部为我们申请了一块内存来保存IP的结果。那么是
否需要调用者手动释放呢?答案是不需要。

在man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区,所以这个时候不需要我们手动进行释放。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/331538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文详解Yolov5——基于Yolov5的火灾检测系统

✨原创不易&#xff0c;还希望各位大佬支持一下\textcolor{blue}{原创不易&#xff0c;还希望各位大佬支持一下}原创不易&#xff0c;还希望各位大佬支持一下 &#x1f44d; 点赞&#xff0c;你的认可是我创作的动力&#xff01;\textcolor{green}{点赞&#xff0c;你的认可是…

SAP ERP系统实施隐式增强中“声明“和“代码“的区别和用途介绍

SAP ERP系统在实施隐式增强的时候会跳出一个增强模式选择“声明”或者“代码”,这步骤应该如何选择对于刚接触这类增强的开发人员通常会感到疑惑&#xff0c;不知道应该选择哪个&#xff08;如下图)。 点击“信息”可以看到官方的英文的解释如下&#xff1a; 这两个选项有什…

通达信收费接口查询可申购新股c++源码分享

有很多股民在做股票交易时为了实现盈利会借助第三三方炒股工具帮助自己&#xff0c;那么通达信收费接口就是人们常用到的&#xff0c;今天小编来分享一下通达信收费接口查询可申购新股c源码&#xff1a; std::cout << " 查询可申购新股: category 12 \n"; c…

maven的学习与理解之 maven的下载与配置文件的修改

maven的学习之maven的下载与配置文件的修改 maven的下载 maven的下载地址&#xff1a; <http://us.mirrors.quenda.co/apache/maven/maven-3/3.5.4/binaries/maven的安装 zip文件下载之后 解压到没有中文的路径下 这个路径后面maven项目使用频繁 建议路径简单…

电商销量查询:鲸参谋2023年1月平板电视品牌销售数据

根据鲸参谋平台的电商数据显示&#xff0c;1月份在京东平台上各类大家电的销售中&#xff0c;平板电视销售量共计210万件左右&#xff0c;总销售量排名第一&#xff0c;环比增长了26.43%&#xff0c;领先于洗衣机、冰箱、冷柜和空调等热销大家电。 ​*数据源于鲸参谋-类目排行分…

C语言--数据的存储1

目录数据类型的介绍类型的意义类型的基本归类整形家族浮点型家族构造类型--自定义类型指针类型空类型整形在内存中的存储大小端大小端如何区分为什么会有大小端判断机器字节序从本章开始&#xff0c;我们将正式进入C语言的进阶学习中。本篇内容我们将学习 数据的存储数据类型的…

Python深度学习实战PyQt5基本控件使用解析

PyQt5 提供了丰富的输入输出控件。本文介绍通过 QtDesigner 工具栏创建常用的基本控件&#xff0c;包括各种按钮控件、文本输入控件和调节输入控件1. PyQt5 控件简介1.1 什么是控件控件也称控件对象&#xff0c;是 Qt用户界面上最基本的组件类型&#xff0c;也是构成用户界面的…

作为研发如何使用Github Api?

文章目录使用步骤账号创建进行开发者相关设置API操作演示Github API好处推荐的Github API&#x1f31f;个人主页: 个人主页 &#x1f6b5;‍♀️个人介绍:每天进步一点点&#xff0c;生活变得好一点点。 &#x1f4cc;作为一位开发&#xff0c;不管是非工作的还是工作中的人士&…

指针的进阶

指针的进阶一级目录二级目录三级目录先来回顾一下&#xff1a;字符指针指针数组数组指针数组指针的定义&数组名vs数组名数组指针的使用数组参数 指针参数一维数组传参二维数组传参一级指针传参二级指针传参函数指针函数指针数组指向函数指针数组的指针回调函数指针和数组笔…

图表控件TeeChart for .NET系列教程六:选择一个系列类型(使用系列)

TeeChart for .NET是优秀的工业4.0 WinForm图表控件&#xff0c;官方独家授权汉化&#xff0c;集功能全面、性能稳定、价格实惠等优势于一体。TeeChart for .NET 中文版还可让您在使用和学习上没有任何语言障碍&#xff0c;至少可以节省30%的开发时间。 TeeChart for .NET最新…

python基于django的自媒体社区交流平台

自媒体社区平台采用python技术,基于django框架,mysql数据库进行开发,实现了以下功能&#xff1a; 本系统主要包括管理员,用户,商家和普通管理员四个角色组成,主要包括以下功能&#xff1a; 1;前台&#xff1a;首页、需求通告、优质案例、帮助中心、意见反馈、个人中心、后台管理…

区块链安全:从web3.0到数字货币

互联网发展的三个阶段 web1.0 静态页面&#xff0c;内容只能供用户去阅读&#xff0c;类似于在网络上读报纸或者看书。 web2.0 动态互联网&#xff0c;实现用户之间的互动&#xff0c;比如twitter&#xff0c;facebook&#xff0c;titok等。 web2.0中厂商用免费或极低的成…

基于UDP/TCP实现客户端服务器的网络通信程序

目录&#xff1a;前言基于UDP实现客户端服务器的网络通信程序基于TCP实现客户端服务器的网络通信程序前言网络编程的核心是Socket API&#xff0c;它是操作系统给应用程序提供的网络编程API&#xff0c;可以认为是socket api是和传输层密切相关的。在传输层里面&#xff0c;提供…

leetcode刷题---递归思想

leetcode刷题---递归思想&#xff09;1.1 递归介绍1.2 基本步骤1.3 代表题目1.3.1 入门题---青蛙跳1.3.2.1 初级题226.翻转二叉树112.路径总和1.3.3 中级题---汉诺塔问题1.3.4 进阶题---细胞分裂1.1 递归介绍 如果在函数中存在着调用函数本身的情况&#xff0c;这种现象就叫递…

java Resource

参看本文前 你要先了解 spring中的 Autowired和Qualifier 注解 如果之前没有接触过 可以查看我的文章 java spring 根据注解方式按(类型/名称)注入Bean 然后 创建一个java项目 引入spring注解方式 所需要的包 然后 在src下创建包 我们这里直接叫 Bean 在Bean下创建包 叫UserD…

【GIC】处理中断

目录 一、当中断变为pending时发生了什么&#xff1f; 二、中断响应 三、虚假的中断 四、运行优先级&抢占 五、结束中断 六、检查系统的当前状态 6.1最高优先级等待中断和运行优先级 6.2单个INTID的状态 一、当中断变为pending时发生了什么&#xff1f; 前面的文章…

0207 事件

事件监听事件监听版本事件类型事件概念事件在编程时系统内发生的动作或者发生的事情例子点击按钮鼠标经过拖拽鼠标事件监听&#xff08;注册事件&#xff0c;绑定事件&#xff09;让程序员检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应…

PHP立体安全:一网打尽攻击向量

PHP立体安全&#xff1a;一网打尽攻击向量 所谓攻击向量&#xff0c;就是指黑传递有效负载或恶意结果而可以访问计算机或网络服务器的路径或方法。 PHP的安全并不只有危险函数&#xff0c; 这只是冰山一角 。本文将介绍PHP从汇编层面到框架层面直到标准层面的所有攻击向量。 攻…

【H5】html实现微信授权登陆

html实现微信授权登陆前言网页授权的两种 scope 的区别开发指南第一步&#xff1a;用户同意授权&#xff0c;获取code第二步&#xff1a;通过 code 换取网页授权access_token第三步&#xff1a;拉取用户信息(需 scope 为 snsapi_userinfo)代码实现&#xff1a;效果图总结前言 …

用Python出了3000道数学题,外甥表示要正月剪头

人生苦短&#xff0c;快学Python&#xff01; 过年期间发现小外甥已经上小学了&#xff0c;我姐说老师今天给他们布置了寒假作业&#xff1a;每天坚持做乘法和加减法混合运算。 这我必须帮帮忙&#xff0c;用Python写了一段自动生成小学生计算题的代码&#xff0c;并支持导出…