网络编程学习——1

一、概述

1.基础知识

IPV4:

32位的地址,点分十进制,分为4段,例:192.168.2.1

每一段可表示的范围为:0~255

IPV6:

128位地址,冒号分16进制

MAC:

物理地址

网卡在出厂时厂家设置的唯一编号

端口号:

标识进程

16位 0-65535 一般从 5001开始

例子: HTTP端口——8080

网络字节序:

默认大端序(主要用于不同端序主机间的数据通信)

2.网络模型

理想化模型:OSI

应用层 app

表示层 数据转换 / 加密 / 格式

会话层 进程的逻辑名和物理名做联系

传输层 流控,纠错

网络层 数据分组,路由的选择

链路层 组装帧格式

物理层 物理接口,信号形式,速率

alt

实际模型:TCP/IP

应用层 对应OSI模型的应用层、表示层、会话层的功能

传输层 还是对流量控制、检错纠错等(TCP/UDP两个协议)

网络层 还是数据分组,路由选择等(主要有:IP/ICMP/IGMP)

网络接口层 包含了OSI模型的网络层、链路层、物理层的功能

alt

3.协议

(1)协议

协议:约定和规则

alt

数据的封装与传递过程

alt

(2)传输层协议

TCP(慢): 有链接,无失序,数据流式,稳定可靠

UDP(快): 无连接,有失序,数据报式,不稳定,不可靠

4.套接字使用

套接字服务端(以 TCP 为例)的整体创建流程可分为以下步骤,遵循 "创建→绑定→监听→接收连接→通信→关闭" 的逻辑

CS模型

(1)套接字socket的使用流程 (Server 服务器端)

1.创建套接字 socket函数

​ 函数功能:

​ 创建套接字文件描述符

​ 函数原型:

​ int socket(int domain, int type, int protoco1);

​ 返回值:

​ 成功返回文件(套接字文件)描述符,-1失败

​ 参数:

​ domain: 地址族,AF_INETipv4

​ type: 套接字类型,SOCK_STREAM tcp

​ protocol: 协议选择默认0(套接字类型会使用相应默认的协议)

domain的常用宏

协议族标识 协议族名称 描述
AF_INET IPv4 协议族 用于 IPv4 网络通信,最常用的协议族
AF_INET6 IPv6 协议族 用于 IPv6 网络通信,支持更大的地址空间
AF_UNIX / AF_LOCAL 本地协议族 用于同一主机内的进程间通信(IPC)
AF_NETLINK 网络链接协议族 用于用户空间与内核空间的通信
AF_PACKET 数据包协议族 直接操作网络层以下的数据包(原始套接字)
AF_BLUETOOTH 蓝牙协议族 用于蓝牙设备间的无线通信

套接字类型

套接字类型标识 类型名称 描述
SOCK_STREAM 流式套接字 提供面向连接、可靠的数据传输服务,基于 TCP 协议
SOCK_DGRAM 数据报套接字 提供无连接的数据传输服务,基于 UDP 协议
SOCK_RAW 原始套接字 允许直接访问底层网络协议(如 IP、ICMP),可以接收和发送未经过 TCP/UDP 封装的原始数据包
2.绑定套接字 bind函数

函数功能:

​ 将套接字与特定的 IP 地址和端口号绑定(通过结构体实现),确保数据能正确发送到该套接字或从该套接字接收。

函数头文件:

​ #include <sys/types.h>

​ #include <sys/socket.h>

函数原型:

​ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

结构体(IPV4专用)

struct sockaddr_in {
    sa_family_t    sin_family;  // 协议族(AF_INET)
    in_port_t      sin_port;    // 端口号(需用htons()转换为网络字节序)
    struct in_addr sin_addr;    // IPv4地址(INADDR_ANY表示绑定所有本地IP)
};
struct in_addr {
    uint32_t	s_addr;			//ip 需要网络字节序
}

特殊IP

127.0.0.1 本机测试回环地址

0.0.0.0 本机所有地址(包括广播地址)

函数参数:

参数 类型 作用
sockfd int 套接字描述符(由socket()函数创建的返回值),标识要绑定的套接字
addr const struct sockaddr * 指向地址结构的指针,包含要绑定的 IP 地址和端口号等信息。
addrlen socklen_t 地址结构的长度(以字节为单位),用于告知系统该地址结构的大小

函数返回值:

​ 成功返回——0

​ 失败返回—— -1 并且设置errno

3.监听套接字 listen

函数功能:

​ 使套接字转为监听套接字,使其能够接收客户端的连接请求。(TCP需要监听,UDP不需要,监听只用于有连接的协议)

函数头文件:

​ #include <sys/types.h>

​ #include <sys/socket.h>

函数原型:

​ int listen(int sockfd, int backlog);

函数参数:

参数名 类型 含义
sockfd int 套接字描述符(由socket()创建并经bind()绑定后的套接字),需为SOCK_STREAM类型(TCP)。
backlog int 表示未完成连接队列的最大长度(处于 “三次握手” 过程中但尚未完成的连接数)(一般填写3~7就够用)

函数返回值: 成功:返回 0 失败:返回 -1,并设置errno

4.被动等待连接 accept

函数功能:

​ 用于 TCP 服务器端接收客户端的连接请求,从listen()创建的连接队列中取出一个已完成三次握手的连接,并创建一个新的套接字用于与该客户端进行后续的数据通信

函数头文件:

​ #include <sys/types.h>

​ #include <sys/socket.h>

函数原型:

​ int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

函数参数:

参数名 类型 含义
sockfd int 监听套接字描述符(由socket()创建、bind()绑定、listen()设置为监听状态的套接字)
addr struct sockaddr * 用于存储客户端的地址信息。需提前分配内存,若不需要客户端地址,可设为NULL
addrlen socklen_t * 返回客户端地址结构的实际长度。若addrNULL,此参数也需设为NULL

函数返回值:

​ 成功:返回连接成功的客户端的文件描述符(用于后续与客户端通信)

​ 失败:-1,并且设置errno

5.通过accept 之后进行通信

while(1)

​ {

​ 通信

​ }

6.Server示例
/*===============================================
*   文件名称:server.c
*   创 建 者:青木莲华
*   创建日期:2025年08月13日
*   描    述:套接字服务端示例
================================================*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    //2.1 初始化结构体(IPV4)
    struct sockaddr_in s_addr;
    s_addr.sin_family = AF_INET;
    //htons 转网络字节序函数
    s_addr.sin_port = htons(6666);
    //inet_addr IPV4地址转网络字节序
    s_addr.sin_addr.s_addr = inet_addr("192.168.6.174");
     
    //2.2 绑定套接字
    if(-1 == bind(sockfd,(struct sockaddr *)&s_addr,sizeof(s_addr)))
    {
        perror("bind");
        return -1;
    }
    //3.监听
    if(-1 == listen(sockfd,5))
    {
        perror("listen");
        return -1;
    } 
    //4.等待连接
    socklen_t len = sizeof(s_addr);
    int connfd = accept(sockfd,(struct sockaddr *)&s_addr,&len);
    printf("len : %d\n",len);
    if(-1 == connfd)
    {
        perror("accept");
        return -1;
    }
    printf("Connect success!\n");
    printf("Client ip : %s  , port : %d\n",inet_ntoa(s_addr.sin_addr),ntohs(s_addr.sin_port));
    char buf[100];
    while(1)
    {
        read(connfd,buf,sizeof(buf));
        puts(buf);
        memset(buf,0,sizeof(buf));
    }
    return 0;
} 

(2)套接字使用(Client 客户端)

1.创建套接字
socket();	//调用函数
2.绑定套接字信息(可以忽略)
bind();	//可以省略
3.连接(connect函数)

该函数与accept函数类似

原型:

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

返回值:0和-1

参数:sockfd:套接字的文件描述符

​ addr:需要连接的服务器的信息

​ addrlen:结构体大小

4.通信上的问题(收发一致)

(1)用固定长度的发送和接收 简单,但是对空间会造成浪费

(2)制定协议 长度(在前)+内容

5.通信的相关函数(读/写)

recv函数

功能:

​ 等同于read的读取操作

原型:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

参数名 类型 用法和作用
sockfd int 套接字描述符,指定接收数据的套接字。
buf void * 指向接收缓冲区的指针,用于存储接收到的数据。
len size_t 指定接收缓冲区的大小
flags int 接收操作的标志,用于控制接收行为 0:默认方式,阻塞接收(设置为0与read函数功能一致)

返回值:

返回值 含义
大于 0 的整数 成功接收的数据字节数
0 连接关闭
-1 操作失败

send函数

功能:

​ 等同于write的写操作

原型:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

参数名 类型 作用与说明
sockfd int 套接字描述符,指定发送数据的套接字。对于 TCP,必须是已连接的套接字;对于 UDP,需是已绑定的套接字
buf const void* 指向待发送数据缓冲区的指针,存储要发送的原始数据
len size_t 待发送数据的字节数,即buf中有效数据的长度
flags int 发送操作的标志,控制发送行为,常用值: - 0:默认模式,正常发送 (设置为0与write函数功能类似)

返回值:

返回值 含义
大于 0 的整数 成功发送的字节数
0 未发送任何数据
-1 发送失败

5.作业

写一个时间服务器,若客户端发来的请求中有time关键字,就返回本地时间给客户端

time()获取时间——返回1971年距现在系统时间的秒数

ctime()把秒转换为一个字符串(格式固定)

loactime()把秒按 月 日 存到结构体

定长通信

server

/*===============================================
*   文件名称:server_time.c
*   创 建 者:青木莲华
*   创建日期:2025年08月13日
*   描    述:套接字TCP通信(服务端)
================================================*/
#include "head.h" 
#include <time.h>

//获取时间函数
void getTime(char *buf)
{ 
    time_t now;
    time(&now);     //获取当前时间
    struct tm *time = localtime(&now);   //转格式存入结构体
    sprintf(buf,"当前时间 : %d - %d - %d , %02d : %02d : %02d",
            1900 + time->tm_year,
            1 + time->tm_mon,
            time->tm_mday,
            time->tm_hour,
            time->tm_min,
            time->tm_sec);
}


int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    //2.1 初始化结构体
    
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(31111);
    //2.2 绑定套接字
    if(-1 == bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)))
    {
        perror("bind");
        close(sockfd);
        return -1;
    }
    //3.监听
    if(-1 == listen(sockfd,5))
    {
        perror("listen");
        close(sockfd);
        return -1;
    }
    printf("Server is waiting connect!\n");
    //4.接收连接
    socklen_t len = sizeof(addr);
    int connfd = accept(sockfd,(struct sockaddr *)&addr,&len);
    if(-1 == connfd)
    {
        perror("accept");
        close(sockfd);
        return -1;
    }
    printf("Client is connect!\n");
    printf("Client ip<%s:%d>\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    //5.通信
    char buf[128] = {0};
    int ret = -1;
    while(1)
    {
        
        ret = recv(connfd,buf,sizeof(buf),0);
        if(-1 == ret)
        {
            perror("recv");
            close(connfd);
            close(sockfd);
            return -1;
        }
        else if(0 == ret)
        {
            printf("Client has disconnected!\n");
            break;
        }
        if(strcmp(buf,"time") == 0)
        {
            getTime(buf);
            send(connfd,buf,strlen(buf),0);
            memset(buf,0,sizeof(buf));
        }
        else
        {
            printf("%s\n",buf);
            memset(buf,0,sizeof(buf));
        }
    }
    close(connfd);
    close(sockfd);
    return 0;
} 

client

/*===============================================
*   文件名称:client_time.c
*   创 建 者:青木莲华
*   创建日期:2025年08月13日
*   描    述:套接字TCP通信(客户端)
================================================*/
#include "head.h"


int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    //2.初始化服务器地址端口 ipv4
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(31111);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //3.连接
    int connfd = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    if(-1 == connfd)
    {
        perror("connect");
        return -1;
    }
    char buf[128];
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        char *p = NULL;
        if(p = strstr(buf,"\n"))
        {
            *p = 0;
        }
        if(strcmp(buf,"quit") == 0)
        {
            break;       
        }
        send(sockfd,buf,strlen(buf),0);
        if(strcmp(buf,"time") == 0)
        {
            memset(buf,0,sizeof(buf));
            recv(sockfd,buf,sizeof(buf),0);
            puts(buf);
        }
        memset(buf,0,sizeof(buf));
    }
    close(connfd);
    return 0;
} 

运行截图

alt

协议通信

server

/*===============================================
*   文件名称:server_time_2.c
*   创 建 者:青木莲华
*   创建日期:2025年08月13日
*   描    述:套接字TCP通信基于协议(服务端)
================================================*/
#include "head.h" 
#include <time.h>


//函数功能:解析通信数据(每次先读4个字节)
//参数:缓冲区
//返回值:实际数据的长度
int resolve_head(char *buf)
{
    int data_len = 0;
    data_len = *(int *)buf;
    return data_len;
}


//函数功能:为发送数据添加头
//参数:缓冲区
//返回值:添加头后数据的长度
int add_head(char *buf) {
    //1. 计算原始数据长度
    int data_len = strlen(buf);
    
    //2. 将长度强转为4个char,存入temp数组
    char temp[4];
    int *len_ptr = &data_len;  
    temp[0] = *((char *)len_ptr + 0);  
    temp[1] = *((char *)len_ptr + 1);  
    temp[2] = *((char *)len_ptr + 2);
    temp[3] = *((char *)len_ptr + 3);
    
    //3. 移动原始数据,为头部的4字节长度腾出空间
    memmove(buf + 4, buf, data_len + 1);
    //4. 将temp中的4字节长度信息复制到缓冲区头部
    memcpy(buf, temp, 4);
    return data_len + 4;
}



//获取时间函数
void getTime(char *buf)
{ 
    time_t now;
    time(&now);     //获取当前时间
    struct tm *time = localtime(&now);   //转格式存入结构体
    sprintf(buf,"当前时间 : %d - %d - %d , %02d : %02d : %02d",
            1900 + time->tm_year,
            1 + time->tm_mon,
            time->tm_mday,
            time->tm_hour,
            time->tm_min,
            time->tm_sec);
}


int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    //2.1 初始化结构体
    
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(31111);
    //2.2 绑定套接字
    if(-1 == bind(sockfd,(struct sockaddr *)&addr,sizeof(addr)))
    {
        perror("bind");
        close(sockfd);
        return -1;
    }
    //3.监听
    if(-1 == listen(sockfd,5))
    {
        perror("listen");
        close(sockfd);
        return -1;
    }
    printf("Server is waiting connect!\n");
    //4.接收连接
    socklen_t len = sizeof(addr);
    int connfd = accept(sockfd,(struct sockaddr *)&addr,&len);
    if(-1 == connfd)
    {
        perror("accept");
        close(sockfd);
        return -1;
    }
    printf("Client is connect!\n");
    printf("Client ip<%s:%d>\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    //5.通信
    int ret = -1;
    int data_len = 0;   //实际数据长度(不包含头)
    char buf[128];
    while(1)
    {
        
        ret = recv(connfd,buf,4,0); //第一次读取头部4字节(获取长度数据)
        if(-1 == ret)
        {
            perror("recv");
            close(connfd);
            close(sockfd);
            return -1;
        }
        else if(0 == ret)
        {
            printf("Client has disconnected!\n");
            break;
        }

        data_len = resolve_head(buf);
        if(data_len > 0)
        {
            recv(connfd,buf,data_len,0);    //按照头部(长度)读取数据
        }
        else
        {
            break;
        }
        if(strcmp(buf,"time") == 0)
        {
            getTime(buf);
            data_len = add_head(buf);                      //添加头
            send(connfd,buf,data_len,0);
            memset(buf,0,sizeof(buf));
        }
        else
        {
            puts(buf); 
            memset(buf,0,sizeof(buf));
        }
    }
    close(connfd);
    close(sockfd);
    return 0;
} 

client

/*===============================================
*   文件名称:client_time_2.c
*   创 建 者:青木莲华
*   创建日期:2025年08月13日
*   描    述:套接字TCP通信基于协议(客户端)
================================================*/
#include "head.h"


//函数功能:解析通信数据(每次先读4个字节)
//参数:缓冲区
//返回值:实际数据的长度
int resolve_head(char *buf)
{
    int data_len = 0;
    data_len = *(int *)buf;
    return data_len;
}


//函数功能:为发送数据添加头
//参数:缓冲区
//返回值:添加头后数据的长度
int add_head(char *buf) {
    //1. 计算原始数据长度
    int data_len = strlen(buf);
    
    //2. 将长度强转为4个char,存入temp数组
    char temp[4];
    int *len_ptr = &data_len;  
    temp[0] = *((char *)len_ptr + 0);  
    temp[1] = *((char *)len_ptr + 1);  
    temp[2] = *((char *)len_ptr + 2);
    temp[3] = *((char *)len_ptr + 3);
    
    //3. 移动原始数据,为头部的4字节长度腾出空间
    memmove(buf + 4, buf, data_len + 1);
    //4. 将temp中的4字节长度信息复制到缓冲区头部
    memcpy(buf, temp, 4);
    return data_len + 4;
}



int main(int argc, char *argv[])
{ 
    //1.创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("socket");
        return -1;
    }
    //2.初始化服务器地址端口 ipv4
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(31111);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //3.连接
    int connfd = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    if(-1 == connfd)
    {
        perror("connect");
        return -1;
    }
    char buf[128];
    int ret = -1;
    int data_len = 0;
    while(1)
    {
        fgets(buf,sizeof(buf),stdin);
        char *p = NULL;
        if(p = strstr(buf,"\n"))
        {
            *p = 0;
        }
        if(strcmp(buf,"quit") == 0)
        {
            break;       
        }

        data_len = add_head(buf);           //添加头
        send(sockfd,buf,data_len,0);
        if(strcmp(buf+4,"time") == 0)
        {
            memset(buf,0,sizeof(buf));
            //解析头
            recv(sockfd,buf,4,0);     
            data_len = resolve_head(buf);
            //根据头信息读取数据
            recv(sockfd,buf,data_len,0);    
            puts(buf);
        }
        memset(buf,0,sizeof(buf));
    }
    close(connfd);
    return 0;
} 

运行截图