网络编程学习——1
一、概述
1.基础知识
IPV4:
32位的地址,点分十进制,分为4段,例:192.168.2.1
每一段可表示的范围为:0~255
IPV6:
128位地址,冒号分16进制
MAC:
物理地址
网卡在出厂时厂家设置的唯一编号
端口号:
标识进程
16位 0-65535 一般从 5001开始
例子: HTTP端口——8080
网络字节序:
默认大端序(主要用于不同端序主机间的数据通信)
2.网络模型
理想化模型:OSI
应用层 app
表示层 数据转换 / 加密 / 格式
会话层 进程的逻辑名和物理名做联系
传输层 流控,纠错
网络层 数据分组,路由的选择
链路层 组装帧格式
物理层 物理接口,信号形式,速率
实际模型:TCP/IP
应用层 对应OSI模型的应用层、表示层、会话层的功能
传输层 还是对流量控制、检错纠错等(TCP/UDP两个协议)
网络层 还是数据分组,路由选择等(主要有:IP/ICMP/IGMP)
网络接口层 包含了OSI模型的网络层、链路层、物理层的功能
3.协议
(1)协议
协议:约定和规则
数据的封装与传递过程
(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 *
返回客户端地址结构的实际长度。若 addr
为NULL
,此参数也需设为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;
}
运行截图
协议通信
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;
}
运行截图