Linux C学习路线全概括及知识点笔记3-网络编程

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

系列文章目录
前言
一、pandas是什么?
二、使用步骤

1.引入库
2.读入数据

总结


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

1,TCP/IP 协议分层结构总览(了解功能即可)

TCP/IP 模型共分为四层,从上到下依次为:

层级名称 主要协议 功能概要
应用层 (Application Layer) HTTP, FTP, DNS, SMTP 等 提供用户应用服务,定义应用数据的格式和交换规则
传输层 (Transport Layer) TCP, UDP 提供端到端的传输控制和错误校验,保证数据可靠或快速传输
网络层 (Internet Layer) IP, ICMP, ARP 实现跨网络通信,负责逻辑寻址和路由选择
网络接口层(或链路层) (Link Layer) Ethernet, PPP, MAC等 管理物理连接,定义数据在本地网络中如何传输

1.1 各层功能详解

1.1.1 应用层(Application Layer)

        功能职责:

                (1)为用户提供网络应用服务

                (2)定义应用数据的格式、编码、会话控制等

        典型协议:

协议 功能说明
HTTP 网页浏览(超文本传输)
FTP 文件传输
SMTP 邮件发送
POP3/IMAP 邮件接收
DNS 域名解析(将域名转换为IP地址)
Telnet 远程登录

PS:你在浏览器输入网址并回车,实际使用的是HTTP协议与服务器通信。

1.1.2 传输层(Transport Layer)

功能职责:

        (1)端到端的通信控制

        (2)建立/管理/终止连接

        (3)数据分段、重组

        (4)流量控制、差错检测与纠正

协议典型:

协议 特性 适用场景
TCP 面向连接、可靠、顺序、校验 网页、文件传输、邮件等
UDP 无连接、不保证顺序或完整性 视频直播、DNS、语音等实时场景

PS:如果你下载文件,通常使用TCP;而如果在看直播,则往往用的是UDP。

1.1.3 网络层(Internet Layer)

功能职责:

        (1)实现IP地址之间的通信

        (2)路由寻址和转发

        (3)分片与重组

典型协议:

协议 功能说明
IP 逻辑寻址与分包转发
ICMP 网络错误/状态信息传递(如ping)
ARP 将 IP 地址解析为 MAC 地址

说明:

        IP协议是核心,提供无连接的传输服务(不保证可靠性)

        ICMP是网络层的“诊断助手”,如ping命令使用它

        ARP在局域网中用于解决“IP->MAC”的问题

1.1.4 网络接口层(Link Layer)

又成链路层、主机到网络层(Host-to-Network Layer)

功能职责:

        (1)实际的数据帧传输

        (2)硬件地址识别(如MAC)

        (3)帧校验、介质访问控制等

典型协议/技术:

技术或协议 功能说明
Ethernet 以太网,局域网最常用的链路协议
PPP 点对点协议,多用于拨号接入
WLAN/Wi-Fi 无线局域网
MAC 协议 媒体访问控制,控制谁可发数据

PS:数据包经过这一层,被封装成帧并在实际网线上(或无线)传输

1.2 整体数据传输过程示意

        以一个网页请求为例(如访问 https://www.baidu.com):

        (1)应用层(HTTP):构造网页请求

        (2)传输层(TCP):将请求封装成可靠的数据段

        (3)网络层(IP):决定如何将数据段送往目标服务器的IP地址

        (4)链路层(以太网/Wi-Fi):将数据帧通过物理媒介发送出去

服务器响应时则逆向传输回来。

1.3 总结与记忆建议

层级 关键词 功能一句话记忆
应用层 用户交互 用户与网络的接口(让程序会说话)
传输层 端到端通信 保证数据“准时准量”到对方(握手或快传)
网络层 寻址与路由 找到目的 IP 的路径(决定去哪儿)
网络接口层 局域链路传输 数据如何走“最后一公里”(怎么走、给谁送)

2,什么是Socket API?

        Socket(套接字)是应用层与传输层之间通信的编程接口,本质上是一个通信端点。Socket编程允许我们在用户空间使用TCP或UDP协议,进行网络通信。

        TCP/IP 协议栈中的 传输层(TCP/UDP)被Socket抽象为统一接口,因此不同协议的通信流程具有高度一致性,区别在于细节控制。

2.1 Socket 编程核心 API(UNIX/Linux平台)

下表是主要的 socket API 函数及其用途:

函数 说明 适用于
socket() 创建 socket 对象 TCP/UDP
bind() 绑定本地地址和端口 TCP/UDP
listen() 监听连接请求(TCP 专用) TCP
accept() 接受连接请求,返回新的 socket(TCP) TCP
connect() 主动连接服务器 TCP
send()/recv() 发送和接收数据 TCP
sendto()/recvfrom() 发送和接收数据(指定目标地址) UDP
close() 关闭 socket TCP/UDP

2.2 TCP Socket 编程流程

TCP服务器端流程图:

TCP客户端流程图:

2.3 TCP示例代码

2.3.1 TCP服务端(C语言示例)

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in sd = {0,};
sd.sin_family = AF_INET;
sd.sin_addr.s_addr = htnol(INADDR_ANY);
sd.sin_port = htons(12345);

bind(sockfd, (struct sockaddr *)&sd, sizeof(sd));
listen(sockfd, 5);

int connfd = accept(sockfd, NULL, NULL);
char buffer[1024] = {0};
recv(connfd, buffer, sizeof(buffer), 0);
send(connfd, "Hello from server", 17, 0);

close(connfd);
close(sockfd);

2.3.2 TCP客户端(C语言示例)

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in sd = {0, };
sd.sin_family = AF_INET;
sd.sin_port = htons(12345);

inet_pton(AF_INET, "1237.0.0.1", &sd.sin_addr);

connect(sockfd, (struct sockaddr *)&sd, sizeof(sd));

send(sockfd, "Hello from client", 17, 0);
char buffer[1024] = {0, };
recv(sockfd, buffer, sizeof(buffer), 0);

close(sockfd);

2.4 UDP socket编程流程

UDP服务器端流程:

UDP客户端流程:

2.5 UDP示例代码

2.5.1 UDP服务端

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

struct sockaddr_in serd = {0, }, clid = {0, };
skcklen_t len = sizeof(clid);

serd.sin_family = AF_INET;
serd.sin_addr.s_addr = htonl(INADDR_ANY);
serd.sin_port = hton(12345);

bind(sockfd, (struct sockaddr *)&serd, sizeof(serd));

char buffer[1024] = {0, };
recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&clid, &len);
sendto(sockfd, "UDP server reply", 16, 0, (struct sockaddr *)clid, &len);

close(sockfd);

2.5.2 UDP客户端

int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

structt sockaddr_in serd = {0, };
serd.sin_family = AF_INET;
serd.sin_prot = htons(12345);
inet_pton(AF_INET, "127.0.0.1", &serd.sin_addr);

sendto(sockfd, "UDP client hello", 17, 0, (sturct sockaddr *)&serd, sizeof(serd));
char buffer[1024] = {0, };
recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);

close(sockfd); 

2.6 TCP vs UDP 对比表格

特性 TCP UDP
是否连接 有连接(可靠) 无连接(不可靠)
数据传输单位 字节流(stream) 报文(datagram)
是否保证顺序
是否保证可靠性 是(有确认、重传、流控、拥塞控制)
适合场景 文件传输、网页、邮件等 实时语音、视频、DNS、NTP、广播等

2.7 Socket 编程核心函数原型与参数说明

1. int socket(int domain, int type, int protocol);
作用:创建一个套接字

参数:

domain:协议族,如 AF_INET(IPv4)或 AF_INET6

type:套接字类型,如 SOCK_STREAM(TCP),SOCK_DGRAM(UDP)

protocol:通常设为 0(自动选择),或指定 IPPROTO_TCP / IPPROTO_UDP

2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:将 socket 绑定到本地地址(IP + 端口)

参数:

sockfd:由 socket() 返回的描述符

addr:本地地址结构体指针,如 struct sockaddr_in

addrlen:地址结构体的长度(sizeof(struct sockaddr_in))

3. int listen(int sockfd, int backlog);
作用:监听指定 socket,使其成为被动连接等待状态(仅用于 TCP)

参数:

sockfd:由 socket() 返回的描述符

backlog:等待连接队列的最大长度

4. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
作用:从连接队列中取出一个连接(TCP 专用)

参数:

sockfd:监听 socket 的描述符

addr:保存客户端地址信息的结构体指针

addrlen:输入输出参数,指示地址结构体长度

5. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:客户端发起连接请求(TCP)

参数:

sockfd:由 socket() 返回的描述符

addr:服务器地址结构体指针

addrlen:地址结构体长度

6. ssize_t send(int sockfd, const void *buf, size_t len, int flags);
作用:向已连接的 socket 发送数据(TCP)

参数:

sockfd:连接 socket 的描述符

buf:指向发送数据的缓冲区

len:缓冲区长度

flags:通常为 0,可指定 MSG_DONTWAIT 等选项

7. ssize_t recv(int sockfd, void *buf, size_t len, int flags);
作用:接收来自已连接 socket 的数据(TCP)

参数:

sockfd:连接 socket 的描述符

buf:存储接收数据的缓冲区

len:缓冲区大小

flags:通常为 0,可设为非阻塞标志等

8. ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
作用:向指定目标发送数据(UDP)

参数:

sockfd:socket 描述符

buf:要发送的数据

len:数据长度

flags:发送选项

dest_addr:目标地址结构体指针

addrlen:地址结构体长度

9. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
作用:接收来自任意发送者的数据(UDP)

参数:

sockfd:socket 描述符

buf:接收缓冲区

len:缓冲区长度

flags:通常为 0

src_addr:保存发送方地址的结构体指针

addrlen:输入输出参数,表示地址结构体大小

10. int close(int fd);
作用:关闭 socket 文件描述符(释放资源)

参数:

fd:socket 描述符

11. int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
作用:设置 socket 选项(如复用地址、超时时间等)

参数:

sockfd:socket 描述符

level:选项级别,如 SOL_SOCKET

optname:选项名称,如 SO_REUSEADDR

optval:选项值指针

optlen:选项值的长度

12. int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
作用:获取 socket 当前设置的选项值

参数:同上

3, 常见套接字属性设置(使用 setsockopt();)

套接字选项可分为几个维度:

属性类型 常用选项名 设置作用
地址重用 SO_REUSEADDR, SO_REUSEPORT 允许端口复用,避免 bind 失败
缓冲区大小 SO_RCVBUF, SO_SNDBUF 设置接收、发送缓冲区大小
保活机制 SO_KEEPALIVE 检测连接是否断开
延迟控制 TCP_NODELAY(TCP专用) 禁用 Nagle 算法,提升实时性
超时设置 SO_RCVTIMEO, SO_SNDTIMEO 设置 I/O 操作超时时间
广播权限 SO_BROADCAST(UDP 专用) 启用 UDP 广播能力
Linger 机制 SO_LINGER 设置 close() 行为(是否阻塞等待发送)

3.1 setsockopt()原型

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd: 套接字描述符
level: 设置哪个协议层,如 SOL_SOCKET、IPPROTO_TCP
optname: 选项名称
optval: 选项值指针
optlen: 选项值长度

3.2 关键属性设置详解与示例

3.2.1 设置端口快速复用:SO_REUSEADDR

int prots = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

作用:允许多个socket在TIME_WAIT状态时立即重用同一端口
建议:服务端监听前调用,防止 “Address already in use” 报错

3.2.2 设置TCP禁用Nagle算法:TCP_NODELAY

int flag = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

作用: 提升小包发送实时性(适合游戏、音视频)
风险:过度禁用可能造成小包碎片增加

3.2.3 启用TCP保活机制:SO_KEEPALIVE

int keepalive = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepakive));

默认内核探测策略可通过如下参数设置(单位:秒)

#启动保活时间(无数据后多久开始探测)
echo 60 > /proc/sys/net/ipv4/tcp_keepalive_time

#探测间隔
echo 5 > /proc/sys/net/ipv4/tcp_keepalive_intvl

#探测失败次数上限
echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes

3.2.4 设置超时时间(避免recv()、send()阻塞)

struct timeval timeout = {3, 0}; // 3s
setsockopt(sockfd, SOL_SOCKET, SO_ECVITMEO, &timeout, sizeof(timeout));
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));

建议:UDP常用,或TCP需要保障应用层超时控制

3.2.5 设置发送和接收缓冲区大小

int size = 64 * 1024; // 64KB
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));

注意:
    内核可能对大小有最小或最大限制,可用 getsockopt(); 反查实际值

3.2.6 启用UDP广播权限:SO_BROADCAST

int broadcast = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

用于发送 192.168.1.255 的广播报文
没有此设置时UDP广播 sendto()会失败(Permission denied)

3.2.7 设置 SO_LINGER 控制 close() 行为

struct linger lin;
lin.l_onoff = 1; // 启用 linger
lin.l_linger = 5; // 最多等5s发送完

setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lin, sizeof(lin));

含义:
    l_onoff = 0; 立即关闭(默认行为)
    l_onoff = 1 && l_linger = 0; 强行关闭(发送RST)
    l_onoff = 1 && l_linger > 0; 阻塞等待发送数据完成

3.2.8 建议组合(实际经验推荐)

应用场景 推荐设置
高并发 TCP 服务器 SO_REUSEADDRSO_RCVBUF/SO_SNDBUFSO_LINGER(延迟关闭)
实时通信 TCP 客户端 TCP_NODELAYSO_KEEPALIVESO_RCVTIMEO
UDP 广播或组播 SO_BROADCASTSO_RCVBUF
长连接稳定性保障 SO_KEEPALIVE + 调整 /proc 中的相关参数

3.2.9 查看当前套接字属性

使用 getsockopt()查询:

int val = 0;
socklen_t len = szieof(val);
getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &val, &len);
printf("recv buf = %d
", val);

使用 ss 或 netstat 工具查看:

ss -tnp

netstat -anp

4,C/S架构定义与本质

C/S架构(Client/Server)是指系统由两个部分组成:

        1,Client(客户端):发起请求,负责用户交互和业务逻辑处理。

        2,Server(服务器):接收请求,处理并返回响应,集中处理数据、服务等。

4.1 核心特征

        1,角色分工明确(发起 vs 响应)

        2,通信基本标准协议(如 TCP/IP)

        3,逻辑分层部署(客户端注重表现,服务端注重计算和存储)

4.2 C/S架构的通信流程图(TCP为例)

客户端                                       服务端
   │                                             │
   │-------- socket() 创建套接字 ---------------▶│
   │                                             │
   │----------- connect() 发起连接 -------------▶│
   │                                             │
   │                                  socket() + bind() + listen()
   │                                             │
   │◀---------- accept() 接受连接 ---------------│
   │                                             │
   │----------- send()/write() 发送数据 --------▶│
   │                                             │
   │◀---------- recv()/read() 接收数据 ----------│
   │                                             │
   │              (通信过程循环)               │
   │                                             │
   │----------- close() 关闭连接 --------------▶│
   │                                             │

4.3 C/S架构的组成模块

组件 客户端 服务器端
网络层逻辑 创建 socket,连接服务端 创建 socket,绑定端口,监听连接
通信协议 通常使用 TCP 或 UDP 同上
应用逻辑 发起请求、处理响应、展示结果 接收请求、执行业务、返回响应
多路并发 一般为单连接、短时交互 需支持并发处理(多线程/多进程/epoll)
状态管理 连接状态由客户端主动维护 服务端可能维护连接会话信息

4.4 TCP与UDP的C/S架构对比

项目 TCP(面向连接) UDP(无连接)
连接建立 需三次握手 不建立连接,直接发送
数据可靠性 高,自动重传与校验 无保证,需应用层处理
编程复杂度 较高(需连接管理) 较低(一次性发送)
应用场景 HTTP、数据库、SSH DNS、视频流、语音、广播

4.5 最小可允许TCP/UDP、C/S模型,扩展多线程/epoll高并发

模型编号 通信类型 客户端实现方式 服务端实现方式
TCP 单连接 单连接
UDP 无连接 无连接
TCP 单连接 多线程并发处理
TCP 单连接 epoll 并发处理(Linux专用)

4.5.1 ① TCP 单连接 C/S 示例代码

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int mian(void)
{
    int sfd, cfd;
    struct sockaddr_in saddr, caddr;
    char buffer[BUFFER_SIZE];

    // 创建套接字
    sfd = socket(AF_INFT, SOCK_STREAM, 0);

    // 绑定地址
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(PORT);
    saddr.sin_addr.s_addr = INADDR_ANY;

    bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 监听
    listen(sfd, 5);
    printf("Server listening on port %d...
", PORT);

    socklen_t clen = sizeof(caddr);
    cfd = accept(sfd, (struct sockaddr *)&caddr, &clen);

    // 通信
    while(1) {
        memset(buffer, 0, BUFFER_SIZE);
        int len = recv(cfd, buffer, BUFFER_SIZE -1, 0);
        if (len <= 0) break;
        
        printf("Receivd from client:%s
", buffer);
        send(cfd, buffer, len, 0); // 回显
    }

    close(cfd);
    close(sfd);

    return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main(void)
{
    int sockfd;
    struct sockaddr_in saddr;
    char buffer[BUFFER_SIZE];

    // 创建socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    // 配置服务器地址
    saddr.sin_family = AF_INET;
    saddr.sin_port = hton(PORT);
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr);

    // 连续服务器
    connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));

    while(1) {
        printf("Input message: ");
        fgets(buffer, BUFFER_SZIE, stdin);
        send(sockfd, buffer, strlen(buffer), 0);
        memset(buffer, 0, BUFFER_SIZE);
        recv(sockfd, buffer, BUFFER_SZIE -1, 0);
        printf("Echo from server: %s
", buffer);
    }

    close(sockfd);
    return 0;
}

4.5.2 ② UDP 无连接 C/S 模型

        UDP是无连接的,客户端与服务端通过 sendto() 和 recvfrom() 直接收发数据,无需建立连接。

UDP服务端(udp_server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main(void)
{
    int sockfd;
    struct sockaddr_in saddr, caddr;
    char buffer[BUFFER_SIZE];
    socklen_t addr_len = sizeof(caddr);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    saddr.sin_faily = AF_INET;
    saddr.sin_port = htons(PORT);
    saddr.sin_addr.s_addr = INADDR_ANY;

    bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    printf("UDP Server listening on port %d...
", PORT);

    while(1) {
        memset(buffer, 0, BUFFER_SZIE);
        int len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0 
                            (struct sockaddr *)&caddr, &addr_len);
        
        if (len < 0) {
            printf("Recvfrom error.");
            continue;
        }

        printf("Received from %s:%d: %s
", inet_ntoa(caddr.sin_addr), 
             ntohs(caddr.sin_port), buffer);

        // 回显数据
        sendto(sockfd, buffer, len, 0, (struct sockaddr *)&caddr, addr_len);
    }

    close(spckfd);
    return 0;
}

UDP客户端(udp_client.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

int main(void)
{
    int sockfd;
    struct sockaddr_in saddr;
    char buffer[BUFFER_SIZE];
    socklen_t addr_len = sizeof(saddr);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(PORT);
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr);

    while(1) {
        printf("Input message: ");
        fgets(buffer. BUFFER_SZIE, stdin);
        
        snedto(sockfd, buffer, strlen(buffer), 0, 
                (struct sockaddr *)&saddr, addr_len);

        memset(buffer, 0, BUFFER_SIZE);
        int len = recvfrom(sockfd, buffer, BUFFER_SIZE -1, 0, 
                            NULL, NULL);
        if (len < 0) {
            printf("Recvfrom error!
");
            continue;
        }

        printf("Echo from server: %s
", buffer);
    }

    close(sockfd);
    return 0;
}

4.5.3 ③ TCP 多线程服务端模型

服务端为每个连接创建独立线程,适合中小规模并发,便于理解

多线程TCP服务端(tcp_thread_server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUFFER_SIZE 1024

void *client_handler(void *arg)
{
    int cfd = *(int *)arg;
    free(arg);
    
    char buffer[BUFFER_SIZE];
    while (1) {
        memset(buffer, 0, BUFFER_SIZE);
        int len = recv(cfd, buffer, BUFFER_SZIE - 1, 0);
        if (len <= 0) break;

        printf("Received: %s
", buffer);
        send(cfd, buffer, len, 0); // 回显
    }

    close(cfd);
    printf("Client disconnected
");

    return NULL;
}

int main(void) 
{
    int sfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(PORT);
    saddr.sin_addr.s_addr = INADDR_ANY;

    bind(sfd, (struct sockkaddr *)&saddr, sizeof(saddr));
    listen(sfd, 10);

    printf("Multi-thread TCP Server listening on port %d...
", PORT);

    while (1) {
        struct sockaddr_in caddr;
        socklen_t clen = sizeof(caddr);
        int *cfd = malloc(sizeof(int));
        
        *cfd = accept(sfd, (struct sockaddr *)&caddr, &clen);
        if (*cfd < 0) {
            printf("accept error
");
            free(cfd);
            continue;
        }
        
        pthread_t tid;
        pthread_create(&tid, NULL, client_handler, cfd);
        pthread_detach(tid); // 线程结束后自动回收资源

    }


    close(sfd);
    return 0;
}

TCP 多线程客户端可用之前的tcp_client.c代码,无需修改

4.5.4 ④ TCP epoll 高性能服务端模型(Linux 专用)

epoll允许单线程处理海量连接,适合高性能场景

epoll TCP 服务端(tcp_epool_server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define PORT 8888
#define MAX_EVENTS 1000
#define BUFFER_SIZE 1024

// 设置文件描述符为非阻塞
int set_nonblocking(int fd)
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags = -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main(void)
{
    int s_fd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(PORT);
    saddr.sin_addr.s_addr = INADDR_ANY;

    bind(s_fd, (struct sockaddr *)&saddr, sizeof(saddr));
    listen(s_fd, 128);

    int epoll_fd = epoll_create1(0);
    struct epoll_event ev, events[MAX_EVENTS];

    set_nonblocking(s_fd);
    
    ev.events = EPOLLIN;
    ev.data.fd = s_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, s_fd, &ev);

    printf("Epoll TCP Server listening on port %d...
", PORT);

    while (1) {
        int i;
        int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for (i = 0; i < nfds; i++) {
                if (events[i].data.fd == s_fd) {
                    // 新连接
                    struct sockaddr_in caddr;
                    socklen_t c_len = sizeof(caddr);
                    int c_fd = accept(s_fd, (struct sockaddr *)&caddr, &c_len);
                    if (c_fd < 0) continue;
            
                    set_nonblocking(c_fd);

                    ev.events = EPOLLIN | EPOLLET; // 边缘触发
                    ev.data.fd = c_fd;
                    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, c_fd, &ev);

                    printf("New client connected: %s:%d
", 
                            inet_ntoa(caddr.sin_addr), 
                            ntohs(caddr.sin_port);
                } else {
                    // 处理客户端数据
                    int c_fd = events[i].data.fd;
                    char buffer[BUFFER_SIZE];
                    int done = 0;

                    while(1) {
                        ssize_t count = read(c_fd. buffer, sizeof(buffer));
                        switch count{
                            case -1:
                                // 表示数据读完
                                break;
                            case 0:
                                // 表示连接关闭
                                done = 1;
                                break;
                            default: 
                                done = 0;
                                break;
                        }

                        // 回显
                        write(c_fd, buffer, count);
                    }
                    
                    if (done) {
                        printf("Client disconnected: fd %d
", c_fd);
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, c_fd, NULL);
                        close(c_fd);
                    }


                }

            }

    }

    close(s_fd);
    close(epoll_fd);
    return 0;

}

4.6 套接字函数原型与参数说明清单

4.6.1 套接字基础函数

函数 原型 参数说明
socket() int socket(int domain, int type, int protocol); domain: 地址族,如 AF_INET(IPv4)- type: 套接字类型,如 SOCK_STREAMSOCK_DGRAMprotocol: 协议编号,通常设为 0(自动匹配)
bind() int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: 套接字文件描述符- addr: 绑定的地址结构体指针- addrlen: 结构体大小
listen() int listen(int sockfd, int backlog); sockfd: 套接字描述符- backlog: 最大等待连接队列长度
accept() int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); sockfd: 监听套接字- addr: 客户端地址结构体- addrlen: 地址结构体长度
connect() int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd: 套接字- addr: 服务器地址- addrlen: 地址结构体大小
close() int close(int fd); fd: 文件描述符

4.6.2 数据读写函数

TCP/UDP 通用

函数 原型 参数说明
send() ssize_t send(int sockfd, const void *buf, size_t len, int flags); sockfd: 已连接的套接字- buf: 发送数据缓冲区- len: 数据长度- flags: 通常设为 0
recv() ssize_t recv(int sockfd, void *buf, size_t len, int flags); sockfd: 已连接的套接字- buf: 接收缓冲区- len: 缓冲区大小- flags: 通常设为 0

UDP专用

函数 原型 参数说明
sendto() ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); dest_addr: 目标地址- 其余同上
recvfrom() ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); src_addr: 发送方地址存储结构体- addrlen: 地址结构体大小- 其余同上

4.6.3 线程相关函数(仅模型③)

函数 原型 参数说明
pthread_create() int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); thread: 线程 ID 指针- attr: 属性,通常为 NULLstart_routine: 线程函数- arg: 传递给线程的参数
pthread_detach() int pthread_detach(pthread_t thread); thread: 线程 ID,使其分离

4.6.4 epoll相关函数(仅模型④)

函数 原型 参数说明
epoll_create1() int epoll_create1(int flags); flags: 通常设为 0,或 EPOLL_CLOEXEC
epoll_ctl() int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epfd: epoll 实例描述符- op: 操作,如 EPOLL_CTL_ADD/DEL/MODfd: 目标描述符- event: 已关注的事件
epoll_wait() int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epfd: epoll 实例- events: 返回的事件数组- maxevents: 数组长度- timeout: 等待时间(ms)
fcntl() int fcntl(int fd, int cmd, ...); 设置文件描述符为非阻塞时使用,如:fcntl(fd, F_SETFL, O_NONBLOCK);

4.6.5 地址处理辅助函数

函数 原型 参数说明
inet_pton() int inet_pton(int af, const char *src, void *dst); – 将 IP 字符串转换为网络字节序地址
inet_ntoa() char *inet_ntoa(struct in_addr in); – 将 in_addr 转换为字符串 IP
htons() / ntohs() uint16_t htons(uint16_t hostshort); – 主机序/网络序转换(端口)
htonl() / ntohl() uint32_t htonl(uint32_t hostlong); – 主机序/网络序转换(IP 地址)

4.6.6 总结:函数原型一览表

函数类别 原型
创建套接字 int socket(int domain, int type, int protocol);
绑定地址 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
监听连接 int listen(int sockfd, int backlog);
接受连接 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
发起连接 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
TCP 发送 ssize_t send(int sockfd, const void *buf, size_t len, int flags);
TCP 接收 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
UDP 发送 ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
UDP 接收 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
关闭套接字 int close(int fd);
创建线程 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
分离线程 int pthread_detach(pthread_t thread);
创建 epoll int epoll_create1(int flags);
控制 epoll int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
等待事件 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
设置非阻塞 int fcntl(int fd, int cmd, ...);
地址转换 int inet_pton(int af, const char *src, void *dst);
地址转字符串 char *inet_ntoa(struct in_addr in);
主机/网络序转换 uint16_t htons(uint16_t);, uint32_t htonl(uint32_t);, 等

5,多进程、多线程服务器模型

5.1 基本定义与背景

模型 定义概述
多进程模型 为每个客户端连接创建一个独立进程,服务端进程通过 fork()clone() 复制子进程来处理客户端请求
多线程模型 为每个客户端连接创建一个独立线程,在一个共享进程空间中通过 pthread_create() 启动新线程处理请求

5.2 模型结构与原理

5.2.1 多进程服务器模型

结构示意图:

              +------------------+
              |   主进程 (监听)   |
              +------------------+
                        |
                 +------+------+
                 |             |
         +-------+------+ +----+--------+
         | 子进程 1 (处理) | | 子进程 N (处理) |
         +--------------+ +---------------+

工作流程:

        1,主进程创建监听 socket。

        2,调用 accept() 等待连接。

        3,每当有客户端连接到达,fork() 出一个子进程处理请求。

        4,子进程与客户端通信,处理完后退出或回收。

        5,主进程继续监听下一连接。

示例代码代码片段:

int connfd = accept(sockfd, ...);
pid_t pid = fork();
if(pid == 0) {
    // 子进程
    close(sockfd); // 子进程无需监听套接字
    handle_client(connfd);
    exit(0);
} else {
    // 主进程
    close(connfd); // 主进程无需与客户端通信 
}
5.2.1.1 多进程服务模型具体代码示例

基于C语言,使用IPV4 + TCP + fork() 实现。此代码清晰展现:

        1,基本socket流程(socket -> bind -> listen -> accept)

        2,每个客户端连接创建独立子进程处理

        3,主进程负责监听并继续接受新连接

        4,使用 SIGCHLD 处理子进程回收,避免僵尸进程

多进程TCP服务器模型(完整可编译)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/wait.h>

#define PORT 8888
#define BACKLOG 10
#define BUFSIZE 1024

// SIGCHLD 信号处理函数,防止僵尸进程
void sigchld_handler(int sig)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

// 子进程用于处理客户端通信
void handle_client(int connfd, struct sockaddr_in client_addr)
{
    char buf[BUFSIZE];
    ssize_t n;
    
    printf("子进程 [%d]: 处理客户端 %s:%d
", getpid(), 
            inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    while((n = recv(connfd, buf, BUFSIZE, 0)) > 0) {
        buf[n] = '';
        printf("收到客户端[%s:%d]消息:%s
", inet_ntoa(client_addr.sin_addr), 
                ntohs(client_addr.sin_port), buf);

        send(connfd, buf, n, 0); // 回显
    }

    printf("客户端[%s:%d]断开连接。
", inet_ntoa(client_addr.sin_addr), 
            ntohs(client_addr.sin_port));

    close(connfd);
    exit(0);
}

int main(void)
{
    int listenfd, connfd;
    struct sockaddr_in s_addr, c_addr;
    socklen_t addrlen = sizeof(c_addr);

    // 注册信号处理函数
    signal(SIGCHLD, sigchld_handler);

    // 创建监听 socket
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket failed!
");
        return 1;
    }

    // 地址复用设置,避免 TIME_WAIT 占用端口
    int opt = 1;
    setsockopt(listenfd, SOL_SOKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 配置服务器地址
    memset(&s_addr, 0, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(PORT);
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 绑定地址
    if (bind(listenfd, (struct sockaddr *)*s_addr, sizeof(s_addr)) < 0) {
        printf("Bind failed!
");
        close(listenfd);
        return 1;
    }

    // 监听端口
    if (listen(listenfd, BACKLOG) < 0) {
        printf("Listen failed!
");
        return 1;
    }

    printf("服务器监听端口 %d,等待连接...
", PORT);

    while(1) {
        connfd = accept(listenfd, (struct sockaddr *)&c_addr, &addrlen);
        if (connfd < 0) {
            if (errno == EINTR) continue; // 被信号打断则重试
            printf("accept failed!
");
            continue;
        }

        // 创建子进程处理客户端
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            close(listenfd); // 子进程不再监听新连接
            handle_client(connfd, client_addr);
        } else if (pid > 0) {
            // 父进程
            close(connfd); // 父进程不负责通信
        } else {
            printf("Fork failed!
");
            close(connfd);
            continue;
        }

    }
    
    close(listenfd);
    return 0;
}


使用 telent 或者 nc 连接测试:
telnet 127.0.0.1 8888
# 或者
nc 127.0.0.1 8888

技术要点回顾:

技术点 说明
fork() 为每个连接创建独立子进程
SIGCHLD 捕获子进程退出信号并回收资源
SO_REUSEADDR 允许快速重启服务器端口
close() 父/子进程正确关闭不需要的 fd,防止 fd 泄漏

5.2.2 多线程服务模型

结构示意图:

              +------------------+
              |   主线程 (监听)   |
              +------------------+
                        |
         +--------------+--------------+
         |              |              |
 +--------+----+ +-------+----+ +------+-----+
 | 线程 1 (处理) | | 线程 2 (处理) | | 线程 N (处理) |
 +-------------+ +--------------+ +--------------+

工作流程:

        1,主线程创建监听 socket。

        2,调用accept() 等待连接。

        3,有连接时调用 pthread_create() 创建新线程。

        4,线程执行通信逻辑。

        5,线程执行完毕后可选择退出或重用。

示例核心代码片段:

int connfd = accept(sockfd, ...);
pthread_t tid;

pthread_create(&tid, NULL, handle_client, (void *)&connfd);
pthread_detach(tid); // 线程分离,自动回收资源
5.2.2.1 多线程服务器模型具体代码示例

多线程服务器模型的完整C语言代码示例,基于 pthread 库实现。此例完整展现了:

        1,使用 pthread_create() 为每个客户端连接创建线程;

        2,每个线程独立处理对应客户请求;

        3,主线程持续接受新连接;

        4,使用 detached 模式防止资源泄露

        5,使用 SO_REUSEADDR 避免端口占用问题。

多线程TCP服务器完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

#define PORT 8888
#define BACKLOG 10
#define BUFSIZE 1024

typedef struct c_info_st{
    int connfd;
    struct sockaddr_in c_addr;
} c_info_t;

// 线程处理函数
void *handle_client(void *arg)
{
    c_info_t *client = (c_info_t *)arg;
    char buf[BUFSIZE] = {0, };
    ssize_t n;

    printf("线程[%ld]处理客户端 %s:%d
", pthread_self(), 
            inet_ntoa(client->client_addr.sin_addr), 
            ntohs(client->client_addr.sin_port));

    while((n = recv(client->connfd, buf, BUFSIZE, 0)) > 0) {
        buf[n] = '';
        printf("收到客户端 [%s:%d] 消息:%s
", 
                inet_ntoa(client->client_addr.sin_addr), 
                ntohs(client->client_addr.sin_port), buf);

        send(client->conffd, buf, n, 0); // 回显
    }

    printf("客户端 [%s:%d] 断开连接.
", inet_ntoa(client->client_addr.sin_addr), 
            ntohs(client->client_addr.sin_port));

    close(client->connfd);
    free(client);
    pthread_exit(NULL);
}

int main(void)
{
    int s_fd, c_fd;
    struct sockaddr_in s_addr, c_addr;
    socklen_t addrlen = sizeof(c_addr);

    // 创建socket
    if ((s_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("Socket failed!
");
        return 1;
    }

    // 设置地址复用
    int opt = 1;
    setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定地址
    memset(&s_addr, 0, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(PORT);
    s_aaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(s_fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) < 0) {
        printf("Bind failed!
");
        close(s_fd);
        return 1;
    }

    // 监听连接
    if (listen(s_fd, BACKLOG) < 0) {
        printf("Listen failed!
");
        close(s_fd);
        return 1;
    }

    printf("服务器启动,监听端口 %d... 
", PORT);

    while(1) {
        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &addrlen);
        if (c_fd < 0) {
            if (errno == EINTR) continue;
            printf("Accept failed!
");
            continue;
        }
    }

    // 为每个连接动态分配 c_info 结构
    c_info_t *client = malloc(sizeof(c_info_t));
    if (client == NULL) {
        printf("Malloc failed!
");
        close(c_fd);
        continue;
    }

    client->connfd = c_fd;
    client->client_addr = c_addr;

    pthread_t tid;
    if (pthread_create(&tid, NULL, handle_client, (void *)client) != 0) {
        printf("pthread_create failed!
");
        close(c_fd);
        free(client);
        continue;
    }

    // 设置线程分离,避免资源泄露
    pthread_detach(tid);
    
    close(s_fd);
    return 0;
}

技术要点解析:

关键点 说明
pthread_create() 创建独立线程处理连接
pthread_detach() 设置线程分离,自动释放资源
malloc/free 每个客户端连接都封装为动态结构传递给线程
recv/send 回显服务
SO_REUSEADDR 避免端口重复绑定错误
inet_ntoa()ntohs() 将 IP/端口转换为可读格式

6,常见的网络协议分析:ping、 tftp、 ftp 等(只描述功能)

6.1 ping协议(ICMP)

原理概述:

        1,ping命令基于 ICMP (Internet Control Message Protocol)工作;

        2,属于 网络层协议,非TCP/UDP;

        3,常用 ICMP Echo Request / Echo Reply 来测试目标主机是否可达

6.1.1 ICMP报文结构(部分字段)

字段 位数 说明
Type 8 8=Echo Request, 0=Echo Reply
Code 8 子类型,一般为 0
Checksum 16 整个 ICMP 校验和
Identifier 16 用于区分多个请求
Sequence No. 16 每次发送递增
Data 可变 可携带任意数据

6.2 TFTP (Trivial File Transfer Protocol)

原理概述:

        1,一种极简的文件传输协议;

        2,使用UDP,端口69;

        3,适用于内嵌设备、PEX启动、小型路由器固件更新;

        4,无需认证、无连接、只支持读/写文件。

6.2.1 报文结构(部分字段)

字段 长度 说明
Opcode 2 字节 操作码(1=RRQ, 2=WRQ, 3=DATA, 4=ACK, 5=ERROR)
Filename N 字节 请求文件名
Mode N 字节 octet(默认二进制)或 netascii

6.3 FTP(File Transfer Protocol)

原理概述:

        1,基于 TCP 的传统文件传输协议;

        2,控制连接使用 TCP 21 端口;

        3,数据传输连接使用 TCP 20 或动态端口;

        4,分为主动模式和被动模式(影响NAT穿透);

        5,支持用户认证、命令行操作、文件/目录浏览。

6.3.1 常用FTP命令

命令 说明
USER / PASS 用户登录
LIST 列出目录
RETR / STOR 下载/上传文件
PASV / PORT 主动/被动模式切换

6.5 总结表格

协议 协议层 端口 传输层协议 用途 是否可靠 是否认证
ICMP (ping) 网络层 无(IP直接) 网络测试
TFTP 应用层 69 (UDP) UDP 简单文件传输
FTP 应用层 21/20 (TCP) TCP 完整文件服务

7,Wireshark 抓包分析示例

以下我将以 ping、以及自配协议为例,提供:

        1,抓包前的大准备与过滤语法

        2,抓包后的典型报文结构截图分析

        3,每一层协议的字段结构说明

        4,关键字段的工程含义

7.1 wireshark抓包准备工作

        1,启动抓包: sudo wireshark &

        2,选择网卡接口:选择有流量的接口(如eth0, ens33, wlan0)

        3,使用过滤器   

场景 过滤器语法
捕捉 ping 包 icmp
捕捉 TFTP udp.port == 69
捕捉 FTP 控制连接 tcp.port == 21
只看某 IP ip.addr == 192.168.1.100
同时多个条件 `icmp

7.2 抓包示例与字段分子

7.2.1 示例1:ping 抓包(ICMP)

抓包画面简析:

Frame 1: 98 bytes
Ethernet II
Internet Protocol Version 4
Internet Control Message Protocol
    Type: 8 (Echo Request)
    Code: 0
    Checksum: 0x3a4f
    Identifier: 0x1c46
    Sequence number: 1

字段解析(ICMP):

字段 值示例 含义
Type 8 Echo Request(请求)
Code 0 子类型,固定
Identifier 0x1c46 每个 ping 会生成一个标识符
Seq Number 1 每次 ping 增加,便于匹配应答
Data 实际传输数据(默认 56 字节)

工程意义:

        1,请求有请求 ID 和序列号,用于区分多个并发 ping

        2,若出现 Unreachable / Timeout,ICMP 会返回错误类型(如 Type 3 Destination Unreachable);

        3,TTL 字段可用于判断主机跳数。

7.2.2 自配协议

通用场景下:使用 wireshark 抓包 “原始数据” + 手工分析

如果您的协议是基于 TCP / UDP / 原始 IP 层实现的(大多数都是),那么您可以直接使用 Wireshark 进行抓包,例如:

示例:

假设你定义了一个TCP协议:

自定义协议报文格式:
[2字节 magic] [1字节 type] [4字节 payload_len] [payload 数据]

然后使用 wireshark 抓包 tcp.proto == 9000,得到如下十六进制内容:

0000   ab cd 01 00 00 00 0a 48 65 6c 6c 6f 57 6f 72 6c
0001   64

手动分析过程如下:

偏移 字段 内容 说明
0x00 magic ab cd 魔术字,标识协议
0x02 type 0x01 消息类型:例如登录
0x03 payload_len 00 00 00 0a 长度 10
0x07 payload HelloWorld 实际数据内容

网络编程方面完结!!!

一、pandas是什么?

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

二、使用步骤

1.引入库

代码如下(示例):

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

2.读入数据

代码如下(示例):

data = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())

该处使用的url网络请求的数据。


总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容