#网络编程 笔记

认识网络

网络发展史

ARPnetA--->Internet--->移动互联网--->物联网

TCP 

用来检测网络传输中差错的传输控制协议

UDP

用户数据报协议,专门负责对不同网络进行互联的互联网协议

局域网

实现小范围短距离网络通信

广域网

现大范围长距离网络通信

光猫

类似于基带modem(数字调制解调器)的设备,和基带modem不同的是接入的是光纤专线,是光信号。用于广域网中光电信号的转换和接口协议的转换,接入路由器,是广域网接入。

路由器

用于连接局域网和外网,进行数据转发。路由器需要区分WAN口和LAN口,WAN口是接外网的(从Modem出来的或者从上一级路由器出来的),LAN口是接内网的,现在路由器都带无线功能,本质上无线接入就是LAN

交换机

用于连接局域网内网,进行数据转发。交换机没有IP分配和IP寻址的功能,所以交换机没有路由器的功能,但大部分路由器都有有交换机的功能。

网线
标准568A:1绿白,2绿,3橙白,4蓝,5蓝白,6橙,7棕白,8棕
标准586B:1橙白,2橙,3绿白,4蓝,5蓝白,6绿,7棕白,8棕
1 2 3 6 上网用 1,2下传 3,6上传 4,5,7电话用 8电源用
直通线:两头都是568B标准,两端线序相同的线,用于不同设备之间互连,比如电脑和路由器。
交叉线:一头是568A,而另一头是568B,两端线序不同,用来同种设备之间互连,比如电脑与电脑。

IP地址

概念

IP是主机在Internet中的标识

与别的机器通信必须具有一个IP地址

IPv4 32位 IPv6 128位

NAT 公网转私网,私网转公网

表示形式:点分十进制,最后转为一个32位的无符号整数

IP地址=网络号+主机号

网络号表示是否在一个网段内

主机号表示在本网段内的ID,同一局域网不能重复

分类

A类 0.0.0.0-127.255.255.255
B类 128.0.0.0-191.255.255.255
C类 192.0.0.0-223.255.255.255
D类 224.0.0.0-239.255.255.255

特殊地址

0.0.0.0 在服务器中,指本机上所有IPv4地址

127.0.0.1 本地回环地址 ,发往改类地址的数据包都被loop back
作用:测试网络接口 访问本地服务 配置调试网络设备

网络地址 每一个网段主机号为0的地址
广播地址 每一个网段主机号最大的地址

全网广播地址 255.255.255.255

子网掩码

长度和IP地址一样,32位整数,作用将某一个IP 划分成网络地址和主机地址;
网络号全为1,主机号全为0
网络号=IP & MASK
主机号=IP & ~MASK

B类地址的子网掩码怎么写?
255.255.0.0

B类地址,同一网段最多可以连接多少个主机?
2^16-2= 65536 - 2 = 65534

已知一个子网掩码号为255.255.255.192,最多可以连接多少台主机?
子网掩码:网络号全为1,主机号全为0
255.255.255.192:255.255.255.1100 0000
26-2=64-2=62

一个IP地址为192.168.3.183 ,计算其网络号与主机号?

如果有800台电脑, 在不浪费ip情况下, 选用哪个网段?B

二级划分 IP=网络号+主机号
三级划分 IP=网络号+子网号+主机号 重新组网,提高资源利用率

某公司有四个部门:行政、研发1、研发2、营销,每个部门各50台计算机接入公司局域网,如果要在192.168.1.0网段为每个部门划分子网,子网掩码应该怎么设置,每个子网地址范围分别是?(4个部门之间不能通信)

有两台电脑主机,在最少浪费IP地址的情况下.将172.16.14.4与172.16.13.2划归为同一网段,则子网掩码应该设置为

网络模型

网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。
网络体系结构指网络的层次结构和每层所使用的协议。
OSI模型

TCP/IP

网络接口和物理层:屏蔽硬件差异,向上层提供统一的操作接口
ppp:拨号协议
ARP:地址解析协议 IP -- MAC
RARP: 反向地址转换协议 MAC-- IP

网络层:IP寻址机器,提供设备到设备的传输
IP(IPv4/IPv6):网间互联协议
ICMP:网络控制管理协议,ping命令使用
IGMP:网络分组管理协议,广播和组播协议

传输层:端口寻址,决定数据交给哪个进程处理(端口号只在网络通信中才有)
TCP: 传输控制协议
UDP:用户数据报协议

应用层:应用协议和应用程序的集合
SMTP/POP3:邮箱传输协议
DNS:域名解析协议
HTTP:超文本传输协议
FTP:文件传输协议
SSH:加密协议
telnet:远程登录协议

TCP

特点 

全双工通信;
面向连接;
可靠:(无丢序)     数据无误,数据无丢失,数据无失序,数据无重复到达

可靠原因
三次握手、四次挥手
序列号和应答机制
超时、错误重传机制
拥塞控制、流量控制(滑动窗口)

适用场景
适合于对传输质量要求高的通信
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能

UDP

特点

全双工通信,面向无连接、不可靠

适用场景
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
适合于广播/组播式通信中
MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯

Dos(拒绝式服务)攻击

socket

概念
普通的I/O操作过程 :打开文件->读/写操作->关闭文件
TCP/IP协议被集成到操作系统的内核中,引入了新型的“I/O”操作 ----->进行网络通信的两个进程在不同的机器上,如何连接? 网络协议具有多样性,如何进行统一的操作 ?
需要一种通用的网络编程接口:Socket
是一个编程接口
是一种特殊的文件描述符
是一种通信机制
面向连接/无连接

类型
流式套接字(SOCK_STREAM)
数据报套接字(SOCK_DGRAM)
原始套接字(SOCK_RAM)

端口号
为了区分主机接收到的数据包转交给哪个进程处理
TCP/UDP端口号独立
由IANA管理
用两个字节表示

字节序
不同类型的CPU主机,内存存储大于一个字节类型的数据在内存中的存放顺序。
(高地址) 0x12345678 (低地址)
网络字节序 大端序 高地址 0x12 0x34 0x56 0x78 低地址
主机字节序 小端序 低地址 0x78 0x56 0x34 0x12 高地址

网络传输中,需要将每个主机的主机字节序(CPU决定),转换为网络中统一顺序的网络字节序
才能供双方主机去识别
只需要转换IP和port就可以,不需要转换传输的数据包的字节序
因为IP和port为 4个字节和2个字节, 而数据报一般都为char类型, 占一个字节
根据字节序的性质,内存存储大于一个字节类型的数据在内存中的存放顺序。
所以char类型并不具有字节序的概念

判断当前主机字节序

数据类型强转 

#include <stdio.h>
int main() {int a = 0x12345678;char b;b = (char)a;printf("%#x\n", b);if (b == 0x78)printf("小端\n");elseprintf("大端\n");return 0;
}

指针强转

#include <stdio.h>
int main() {int a=0x12345678;char *p=(char*)&a;printf("%#x\n",*p);if (*p == 0x78)printf("小端\n");elseprintf("大端\n");return 0;
}

端口转换 8888
主机字节序转换为网络字节序 (小端序->大端序)
u_long htonl (u_long hostlong); //host to internet long
u_short htons (u_short short); //服务器、客户端写地址时用到
网络字节序转换为主机字节序(大端序->小端序)
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);//printf时用到

IP地址转换

in_addr_t  inet_addr(const char *strptr);  //该参数是字符串
typedef uint32_t in_addr_t;
struct in_addr {in_addr_t s_addr;
};
功能:  主机字节序转为网络字节序
参数:  const char *strptr: 字符串
返回值: 返回一个无符号长整型数(无符号32位整数用十六进制表示), 否则NULL
char *inet_ntoa(stuct in_addr inaddr);
功能:   将网络字节序二进制地址转换成主机字节序。 
参数:  stuct in_addr in addr  : 只需传入一个结构体变量
返回值:  返回一个字符指针, 否则NULL;

TCP

TCP流程

服务器:------------------------------------------------------------------》接电话者
1. 创建流式套接字(socket)---------------------------------------------》有手机
2. 指定网络信息-----------------------------------------------------------》有号码
3. 绑定套接字(bind)----------------------------------------------------》绑定手机
4. 监听套接字(listen)---------------------------------------------------》待机
5. 接收客户端请求(accept)---------------------------------------------》接电话
6. 发送、接收消息(send recv)------------------------------------------》通话
7. 关闭套接字(close)------------------------------------------ ---------》挂断电话
客户端:-------------------------------------------------------------------》拨打电话者
1. 创建流式套接字(socket)----------------------------------------------》有手机
2. 指定网络(服务器)信息-------------------------------------------------》有对方号码
3. 请求连接(connect)----------------------------------------------------》打电话
4. 发送、接收消息(send/recv)-------------------------------------------》通话
5. 关闭套接字(close)-----------------------------------------------------》挂断电话

函数接口

1. socket

int socket(int domain, int type, int protocol);
功能:创建套接字
参数:domain:协议族AF_UNIX, AF_LOCAL  本地通信AF_INET            ipv4AF_INET6           ipv6type:套接字类型SOCK_STREAM:流式套接字SOCK_DGRAM:数据报套接字SOCK_RAW:原始套接字protocol:协议 - 填0 自动匹配底层 ,根据type系统默认自动帮助匹配对应协议传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)返回值:成功 文件描述符失败 -1,更新errno

2. bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定
参数:socket:套接字addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信当时socket第一个参数确定)   addrlen:结构体大小   返回值:成功 0   失败-1,更新errno通用结构体:struct sockaddr {sa_family_t sa_family;char        sa_data[14];}ipv4通信结构体:
struct sockaddr_in {sa_family_t    sin_family;in_port_t      sin_port;  struct in_addr sin_addr;  
};
struct in_addr {uint32_t       s_addr;    
};本地通信结构体:struct sockaddr_un {sa_family_t sun_family;               /* AF_UNIX */char        sun_path[108];            /* pathname */};

3. listen

int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:sockfd:套接字backlog:同时响应客户端请求链接的最大个数,不能写0.不同平台可同时链接的数不同,一般写6-8个(队列1:保存正在连接)(队列2,连接上的客户端)返回值:成功 0   失败-1,更新errno

4. accept

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件;
参数:Sockfd :套接字addr: 链接客户端的ip和端口号如果不需要关心具体是哪一个客户端,那么可以填NULL;
addrlen:结构体的大小如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值: 成功:文件描述符; //用于通信失败:-1,更新errno

5. recv

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据 
参数: 
sockfd: acceptfd ;
buf  存放位置
len  大小
flags  一般填0,相当于read()函数
MSG_DONTWAIT  非阻塞
返回值: 
< 0  失败出错  更新errno
==0  表示客户端退出
>0   成功接收的字节个数

6. connect

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:sockfd:socket函数的返回值addr:填充的结构体是服务器端的;addrlen:结构体的大小
返回值 -1 失败,更新errno正确 0

7. send

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:sockfd:socket函数的返回值buf:发送内容存放的地址len:发送内存的长度flags:如果填0,相当于write();
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>int main(int argc, char const *argv[])
{char buf[128] = {0};// 1.创建流式套接字(socket)--------------------------------------------》有手机int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd); // 3// 2.指定网络信息-------------------------------------------------------》有号码struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(8867); // 端口号saddr.sin_addr.s_addr = inet_addr("192.168.51.16");// 3.绑定套接字(bind)------------------------------------------------》绑定手机if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}printf("bind ok\n");// 4.监听套接字(listen)------------------------------------------------》待机// 将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen ok\n");// 5.接收客户端请求(accept)-------------------------------------------》接电话int acceptfd = accept(sockfd, NULL, NULL);if (acceptfd < 0){perror("accpet err");return -1;}printf("acceptfd:%d\n", acceptfd);// 6.发送、接收消息(send recv)----------------------------------------》通话// recv--->readint ret;while (1){ret = recv(acceptfd, buf, sizeof(buf), 0);// 当接受成功:独到的个数// 0:客户端退出//-1:接受失败if (ret < 0){perror("recv err");return -1;}else if (ret == 0){printf("client exit\n");break;}elseprintf("buf:%s\n", buf);}//  7.关闭套接字(close)----------------------------------------------------》挂断电话close(acceptfd);close(sockfd);return 0;
}

UDP

流程

服务器:--------------------------------------------------》短信的接收者
1. 创建数据报套接字(socket)---------------------------》有手机
2. 填充网络信息-------------------------------------------》有号码
3. 绑定(bind)--------------------------------------------》绑定手机
4. 接收发送消息(recvfrom sendto)---------------------》接收信息
5. 关闭套接字(close)--------------------------------------》接收完毕
客户端:---------------------------------------------------》短信的发送者
1. 创建数据报套接字(socket)--------------------------》有手机
2. 填充网络(服务器)信息---------------------------------》有对方号码
3. 接收发送消息(recvfrom sendto)---------------------》发信息
4. 关闭套接字(close)---------------------------------------》发送完毕

函数接口

recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:sockfd:套接字描述符buf:接收缓存区的首地址len:接收缓存区的大小flags:0src_addr:发送端的网络信息结构体的指针addrlen:发送端的网络信息结构体的大小的指针
返回值:成功接收的字节个数失败:-10:客户端退出

sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, 
int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:sockfd:套接字描述符buf:发送缓存区的首地址len:发送缓存区的大小flags:0src_addr:接收端的网络信息结构体的指针addrlen:接收端的网络信息结构体的大小
返回值: 成功发送的字节个数失败:-1

TCP 框架 服务器/客户端

//创建流式套接字 socket
//指定网络信息
//绑定套接字 bind
//监听套接字 listen 
//接受客户端请求 accept
//发送、接受消息 send recv
//关闭套接字 close
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{char buf[128] = {0};int ret, acceptfd;// 1创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0int len = sizeof(caddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}// 监听:将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen okk\n");while (1){acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}printf("ip:%s port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));// 接受消息while (1){ret = recv(acceptfd, buf, sizeof(buf), 0);if (ret < 0)
//创建流式套接字 socket
//指定网络信息
//请求连接 connect
//发送、接受消息 send recv
//关闭套接字 close#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{char buf[128] = {0};// 创建流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[2]));saddr.sin_addr.s_addr = inet_addr(argv[1]);// 链接if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("connect err");return -1;}printf("connect ok\n");// 接受发送消息while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = '\0';if (!strcmp(buf, "quit"))break;send(sockfd, buf, sizeof(buf), 0);}close(sockfd);return 0;
}

UDP 框架 服务器/客户端

//创建数据报套接字 socket
//填充网络信息
//绑定套接字 bind
//接受发送消息 recvfrom sendto
//关闭套接字 close
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{char buf[128] = {0};int ret = 0;// 1.创建数据报套接字(socket)--------------------》有手机int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 2.填充网络信息----------------------------------》有号码struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY;int len = sizeof(caddr);// 3.绑定(bind)---------------------------------》绑定手机if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
}
创建数据报套接字 socket
填充网络信息
接受发送信息 recvfrom send
关闭套接字 close#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{char buf[128] = {0};int ret = 0;// 1.创建数据报套接字(socket)-----------------------------》有手机int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 2.填充网络信息--------------------------------------------------》有号码struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[2]));saddr.sin_addr.s_addr = inet_addr(argv[1]);// 3.循环发送消息while (1){

1、对于TCP是先运行服务器,客户端才能运行。
2、对于UDP来说,服务器和客户端运行顺序没有先后,因为是无连接,所以服务器和客户端谁先开始,没有关系,
3、一个服务器可以同时连接多个客户端。想知道是哪个客户端登录,可以在服务器代码里面打印IP和端口号。
4、UDP,客户端当使用send的时候,上面需要加connect,这个connect不是代表连接的作用,而是指定客户端即将要发送给谁数据。这样就不需要使用sendto而用send就可以。
5、在TCP里面,也可以使用recvfrom和sendto,使用的时候将后面的两个参数都写为NULL就OK。

linux IO模型

阻塞IO:(阻塞函数)一直阻塞,等待到所需要的文件描述符有事件发生,才会返回
非阻塞IO:不等待,函数立即返回,没有事件发生(返回错误信息),有事件发生(要执行的事件)
信号驱动IO:异步,当监听的文件描述符有事件发生,内核会给进程的发送SIGIO信号,通知进程,进程就可以捕捉信号做相应的逻辑处理

阻塞IO

最常见、效率低、不浪费cpu
阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。

学习的读写函数在调用过程中会发生阻塞相关函数如下:
读操作中的read、recv、recvfrom
读阻塞:需要读缓冲区中有数据可读,读阻塞解除
写操作中的write、send
写阻塞:阻塞情况比较少,主要发生在写入的缓冲区的大小小于要写入的数据量的情况下,写操作不进行任何拷贝工作,将发生阻塞,一旦缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。

注意:sendto没有写阻塞
1)sendto不是阻塞函数,本身udp通信不是面向链接的,udp无发送缓冲区,即sendto没有发送缓冲区,send是有发送缓存区的,即sendto不是阻塞函数。
2)UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在缓冲区满的情况,在UDP套接字上进行写操作永远不会阻塞。

其他操作:accept、connect
粘包、拆包 丢包
tcp粘包

tcp拆包

udp丢包

UDP通信没有发送缓存区, 它不保证数据的可靠性。因此,UDP通信是将数据尽快发送出去,不关心数据是否到达目标主机. 但是UDP有接收缓存区, 因为数据发送过快, 如果接收缓存区内数据已满, 则继续发送数据, 可能会出现丢包。
丢包出现原因: 接收缓存区满 网络拥堵, 传输错误
相比之下,TCP是一种面向连接的传输协议,它需要保证数据的可靠性和顺序性。TCP有发送缓存区和接收缓存区, 如果发送频率过快, 且内容小于发送缓存区的大小 , 可能会导致多个数据的粘包。如果发送的数据大于发送缓存区, 可能会导致拆包。
udp不会发生粘包和拆包,tcp不会丢包
UDP是基于数据报文发送的,每次发送的数据包,在UDP的头部都会有固定的长度, 所以应用层能很好的将UDP的每个数据包分隔开, 不会造成粘包。
TCP是基于字节流的, 每次发送的数据报,在TCP的头部没有固定的长度限制,也就是没有边界,那么很容易在传输数据时,把多个数据包当作一个数据报去发送,成为了粘包,或者传输数据时, 要发送的数据大于发送缓存区的大小,或者要发送的数据大于最大报文长度, 就会拆包;
TCP不会丢包,因为TCP一旦丢包,将会重新发送数据包。(超时/错误重传)

非阻塞IO

轮询、耗费cpu,可以处理多路IO
•当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”
•当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
•应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
•这种模式使用中不普遍。

设置非阻塞式IO的方式

1. 通过函数自带参数设置

2. 通过设置文件描述符的属性,把文件描述符设置为非阻塞属性

int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置文件描述符属性
参数:fd:文件描述符cmd:设置方式 - 功能选择F_GETFL  获取文件描述符的状态信息     第三个参数化忽略F_SETFL  设置文件描述符的状态信息     通过第三个参数设置O_NONBLOCK  非阻塞O_ASYNC     异步O_SYNC      同步arg:设置的值  in
返回值:特殊选择返回特殊值 - F_GETFL  返回的状态值(int)其他:成功0  失败-1,更新errno使用:0为例0-原本:阻塞、读权限  修改或添加非阻塞int flags=fcntl(0,F_GETFL);//1.获取文件描述符原有的属性信息flags = flags | O_NONBLOCK;//2.修改添加权限fcntl(0,F_SETFL,flags);    //3.将修改好的权限设置回去

信号驱动IO

异步通知模式,需要底层驱动支持
异步通知:异步通知是一种非阻塞的通知机制,发送方发送通知后不需要等待接收方的响应或确认。通知发送后,发送方可以继续执行其他操作,而无需等待接收方处理通知。
1)通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO。
2)应用程序收到信号后做异步处理即可。
3)应用程序需要把自己的进程号告诉内核,并打开异步通知机制。

//1.设置将文件描述符和进程号提交给内核驱动
//一旦fd有事件响应, 则内核驱动会给进程号发送一个SIGIO的信号fcntl(fd,F_SETOWN,getpid());//2.设置异步通知int flags;flags = fcntl(fd, F_GETFL); //获取原属性flags |= O_ASYNC;       //给flags设置异步   O_ASUNC 通知fcntl(fd, F_SETFL, flags);  //修改的属性设置进去,此时fd属于异步//3.signal捕捉SIGIO信号 --- SIGIO:内核通知会进程有新的IO信号可用
//一旦内核给进程发送sigio信号,则执行handlersignal(SIGIO,handler);

练习:输入鼠标的时候, 响应鼠标事件, 输入键盘的时候, 响应键盘事件 (两路IO)

超时检测

概念
什么是网络超时检测呢,比如某些设备的规定,发送请求数据后,如果多长时间后没有收到来自设备的回复,那么需要做出一些特殊的处理
比如: 链接wifi的时候,等了好长时间也没有连接上,此时系统会发送一个消息: 网络连接失败;

必要性
1. 避免进程在没有数据时无限制的阻塞;
2. 规定时间未完成语句应有的功能,则会执行相关功能;

IO多路复用

应用程序中同时处理多路输入输出流,若采用阻塞模式,得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程/线程,分别处理一条数据通路,将新产生进程/线程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用技术。其(select)基本思想是:
先构造一张有关描述符的表(最大1023),然后调用一个函数。
当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。

select

特点
1. 一个进程最多能监听1024个文件描述符
2. select每次被唤醒,都要重新轮询表,效率低
3. select每次都清空未发生响应的文件描述符,每次都要重新拷贝用户空间的表到内核空间

编程步骤
1. 创建表
2. 清空表
3. 将关心的文件描述符添加到表中
4. select
5 . 判断
6. 处理

函数接口

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:实现IO的多路复用
参数:nfds:关注的最大的文件描述符+1readfds:关注的读表writefds:关注的写表 exceptfds:关注的异常表timeout:超时的设置NULL:一直阻塞,直到有文件描述符就绪或出错时间值为0:仅仅检测文件描述符集的状态,然后立即返回时间值不为0:在指定时间内,如果没有事件发生,则超时返回0,并清空设置的时间值struct timeval {long tv_sec;		/* 秒 */long tv_usec;	/* 微秒 = 10^-6秒 */
};返回值:准备好的文件描述符的个数-1 :失败:0:超时检测时间到并且没有文件描述符准备好	注意:select返回后,关注列表中只存在准备好的文件描述符
操作表:
void FD_CLR(int fd, fd_set *set); //清除集合中的fd位
void FD_SET(int fd, fd_set *set);//将fd放入关注列表中
int  FD_ISSET(int fd, fd_set *set);//判断fd是否在集合中  是--》1   不是---》0
void FD_ZERO(fd_set *set);//清空关注列表

输入鼠标的时候响应鼠标事件, 输入键盘的时候响应键盘事件 (两路IO)

用select创建并发服务器,可以同时连接多个客户端 (监听键盘和sockfd)

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{int ret, maxfd, acceptfd;char buf[32] = {0};int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0int len = sizeof(caddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}// 监听:将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen okk\n");maxfd = sockfd;// 1.构造一张关于文件描述符的表fd_set rdfs;fd_set tempfs;// 2.清空表FD_ZEROFD_ZERO(&rdfs);FD_ZERO(&tempfs);// 3.将关心的文件描述符添加到表中FD_SETFD_SET(0, &rdfs); // 键盘FD_SET(sockfd, &rdfs);while (1){tempfs=rdfs;// 4.调用select函数,监听ret = select(maxfd + 1, &tempfs, NULL, NULL, NULL);if (ret < 0){perror("select errr");return -1;}// 5.判断到底是哪一个或者哪些文件描述符发生的事件FD_ISSETif (FD_ISSET(0, &tempfs)){// 6.做对应的逻辑处理fgets(buf, sizeof(buf), stdin);printf("buf:%s\n", buf);}if (FD_ISSET(sockfd, &tempfs)){acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}printf("ip:%s port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));}memset(buf, 0, sizeof(buf));}close(sockfd);return 0;
}

用select创建并发服务器,可以与多个客户端进行通信(监听键盘、socket、多个acceptfd)

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{int ret, maxfd, acceptfd, rev;char buf[32] = {0};int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0int len = sizeof(caddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}
}

poll

特点
1. 优化了文件描述符的限制
2. poll每次被唤醒,需要重新轮询,效率比较低,耗费cpu
3. poll不需要构造文件描述符的表(也不需要清空表),采用结构体数组,每次也需要从用户空间拷贝到内核空间

编程步骤
1. 创建结构体数组
2. 将关心的文件描述符添加到结构体数组中,并赋予事件
3. 保存数组内最后一个有效元素下标
4. 调用poll函数,监听
5. 判断结构体内文件描述符实际触发的事件
6. 根据不同文件描述符触发的不同事件,做对应的逻辑处理

函数接口

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能: 监视并等待多个文件描述符的属性变化
参数:1.struct pollfd *fds:   关心的文件描述符数组,大小自己定义若想检测的文件描述符较多,则建 立结构体数组struct pollfd fds[N]; struct pollfd{int fd;	 //文件描述符short events;	//等待的事件触发条件----POLLIN读时间触发short revents;	//实际发生的事件(未产生事件: 0 ))}2.   nfds:    最大文件描述符个数3.  timeout: 超时检测 (毫秒级):1000 == 1s      如果-1,阻塞          如果0,不阻塞
返回值:  <0 出错		>0 表示有事件产生;如果设置了超时检测时间:&tv<0 出错		>0 表示有事件产生;	   ==0 表示超时时间已到;	

输入鼠标的时候, 响应鼠标事件, 输入键盘的时候, 响应键盘事件 (两路IO)

#include <stdio.h>
#include <poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>int main(int argc, char const *argv[])
{int ret;char buf[128] = {0};int fd = open("/dev/input/mouse0", O_RDONLY);if (fd < 0){perror("open err");return -1;}printf("fd:%d\n", fd);// 1.创建结构体数组struct pollfd fds[2];// 2.将关心的文件描述符添加到结构体数组中,并赋予事件fds[0].fd = fd;         // 鼠标fds[0].events = POLLIN; // 想要发生的事件fds[1].fd = 0;          // 键盘fds[1].events = POLLIN; // 想要发生的事件// 3.保存数组内最后一个有效元素下标int maxfd = 1;// 4.调用poll函数,监听while (1){ret = poll(fds, maxfd + 1, -1);if (ret < 0){perror("poll err");return -1;}// 5.判断结构体内文件描述符实际触发的事件if (fds[0].revents == POLLIN){// 6.根据不同文件描述符触发的不同事件,做对应的逻辑处理read(fd, buf, sizeof(buf));printf("mouse:%s\n", buf);}if (fds[1].revents == POLLIN){fgets(buf, sizeof(buf), stdin);printf("buf:%s\n", buf);}memset(buf, 0, sizeof(buf));}close(fd);return 0;
}

epoll详解

特点
1. 监听的文件描述符没有了个数的限制(取决于自己系统)
2. 异步IO,epoll当有事件唤醒,文件描述符会主动的调用callback函数拿到唤醒的文件描述符,不需要轮询,效率高
3. epoll不需要构造文件描述符的表。只需要从用户空间到内核空间拷贝一次

编程步骤
1. 创建红黑树和就绪链表 epoll_create
2. 将关心的文件描述符和事件上树 epoll_ctl
3. 阻塞等待事件发生,一旦产生事件,则进行处理epoll_wait
4. 根据链表中准备好的文件描述符,进行处理

epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目;
eg:1GB机器上,这个上限10万个左右。
每个fd上面有callback(回调函数)函数,只有产生事件的fd才有主动调用callback,不需要轮询。
注意:
Epoll处理高并发,百万级
1. 红黑树: 是特殊的二叉树(每个节点带有属性),Epoll怎样能监听很多个呢?首先创建树的根节点,每个节点都是一个fd以结构体的形式存储(节点里面包含了一些属性,callback函数)
2. 就绪链表: 当某一个文件描述符产生事件后,会自动调用callback函数,通过回调callback函数来找到链表对应的事件(读时间还是写事件)

函数接口

int epoll_create(int size); 
功能:创建红黑树根节点(创建epoll实例) , 同时也会创建就绪链表
返回值:成功时返回一个实例epfd(二叉树句柄),失败时返回-1。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);功能:控制epoll属性,比如给红黑树添加节点参数: 1. epfd:   epoll_create函数的返回句柄。//一个标识符2. op:表示动作类型,有三个宏:			        
EPOLL_CTL_ADD:注册新的fd到epfd中EPOLL_CTL_MOD:修改已注册fd的监听事件EPOLL_CTL_DEL:从epfd中删除一个fd3. 要操作的文件描述符4. 结构体信息: 
typedef union epoll_data {int fd;      //要添加的文件描述符uint32_t u32;  typedef unsigned intuint64_t u64;   typedef unsigned long int} epoll_data_t;struct epoll_event {uint32_t events; 事件epoll_data_t data; //共用体(看上面)};关于events事件:
EPOLLIN:  表示对应文件描述符可读EPOLLOUT: 可写EPOLLPRI:有紧急数据可读;EPOLLERR:错误;EPOLLHUP:被挂断;EPOLLET:触发方式,边缘触发;(默认使用边缘触发)ET模式:表示状态的变化;NULL: 删除一个文件描述符使用,无事件返回值:成功:0, 失败:-1
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);功能:等待事件产生内核会查找红黑树中有事件响应的文件描述符, 并将这些文件描述符放入就绪链表就绪链表中的内容, 执行epoll_wait会同时复制到第二个参数events参数: 	epfd:句柄;events:用来保存从就绪链表中响应事件的集合;maxevents:  表示每次在链表中拿取响应事件的个数;timeout:超时时间,毫秒,0立即返回  ,-1阻塞	返回值: 成功: 实际从链表中拿出的文件描述符数目     失败时返回-1

总结

select

poll

epoll

监听个数

一个进程最多监听1024个文件描述符(128字节)

由程序员自己决定

百万级

方式

每次都会被唤醒,都需要重新轮询

每次都会被唤醒,都需要重新轮询

红黑树内callback自动回调,不需要轮询

效率

文件描述符数目越多,轮询越多,效率越低

文件描述符数目越多,轮询越多,效率越低

不轮询,效率高

原理

每次使用select后,都会清空表
每次调用select,都需要拷贝用户空间的表到内核空间
内核空间负责轮询监视表内的文件描述符,将发生事件的文件描述符拷贝到用户空间,再次调用select,如此循环

不会清空结构体数组
每次调用poll,都需要拷贝用户空间的结构体到内核空间
内核空间负责轮询监视结构体数组内的文件描述符,将发生事件的文件描述符拷贝到用户空间,再次调用poll,如此循环

不会清空表
epoll中每个fd只会从用户空间到内核空间只拷贝一次(epoll_ctl添加ADD时)
通过epoll_ctl将文件描述符交给内核监管,一旦fd就绪,内核就会采用callback的回调机制来激活该fd,epoll_wait便可以收到通知(内核空间到用户空间的拷贝

特点

1. 一个进程最多能监听1024个文件描述符
2. select每次被唤醒,都要重新轮询表,效率低

1. 优化文件描述符的个数限制
2. poll每次被唤醒,都要重新轮询,效率比较低(耗费cpu)

1. 监听的文件描述符没有个数限制(取决于自己的系统)
2. 异步IO,epoll当有事件产生被唤醒,文件描述符会主动调用callback函数拿到唤醒的文件描述符,不需要轮询,效率高

结构

文件描述符表(位表)

结构体数组

红黑树和就绪链表

开发复杂度

阻塞式IO

不轮询,效率低,不浪费cpu

非阻塞IO

轮询,耗费cpu,可以处理多路IO

信号驱动IO/异步IO

异步通知方式,需要底层驱动的支持

IO多路复用

可以同时处理多路IO,效率高

服务器模型

在网络通信中,通常一个服务器要连接多个客户端
为了处理多个客户端的请求,服务器的程序就有多种表现形式

循环服务器
一个服务器在同一时间只能链接一个客户端

tcp:
socket();//流式
指定信息
bind();
listen();
while(1)
{accept();while(1)recv();
}
close();udp:
socket();//数据报
指定网络
bind();
while(1)recvfrom()/sendto();
close();

并发服务器
一个服务器在同一时间能同时处理多个客户端请求

多进程
accept之后fork
子进程:通信
父进程:循环等待链接
父进程中进行子进程回收,waitpid
信号:SIGCHLD:当子进程状态改变给父进程发送的信号
优点: 服务器更稳定 , 父子进程资源独立, 安全性高一点
缺点: 需要开辟多个进程,大量消耗资源

int main(int argc, char const *argv[])
{char buf[128] = {0};int ret, acceptfd;pid_t pid;// 1创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0int len = sizeof(caddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}// 监听:将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen okk\n");signal(SIGCHLD, handler);while (1){acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}printf("ip:%s port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pid = fork();if (pid < 0){perror("fork err");return -1;}else if (pid == 0){// 子进程//  接受消息while (1){ret = recv(acceptfd, buf, sizeof(buf), 0);if (ret < 0){perror("recv err");break;}else if (ret == 0){printf("client exit\n");break;}elseprintf("buf:%s\n", buf);memset(buf, 0, sizeof(buf));}close(acceptfd);exit(0);}elseclose(acceptfd);}close(sockfd);return 0;
}

多线程
每有一个客户端连接,就开辟一个线程,与客户端通信
accept之后pthread_create
主:循环等待连接
子:通信
优点: 相对多进程, 资源开销小, 线程共享同一个进程的资源
缺点: 需要开辟多个线程,而且线程安全性较差

void *handler(void *arg)
{// 通信int ret;char buf[128] = {0};int acceptfd = *((int *)arg);//  接受消息while (1){ret = recv(acceptfd, buf, sizeof(buf), 0);if (ret < 0){perror("recv err");return NULL;}else if (ret == 0){printf("client exit\n");break;}elseprintf("buf:%s\n", buf);memset(buf, 0, sizeof(buf));}close(acceptfd);pthread_exit(NULL);
}int main(int argc, char const *argv[])
{int acceptfd;pthread_t tid;// 1创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0int len = sizeof(caddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}// 监听:将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen okk\n");while (1){acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}printf("ip:%s port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pthread_create(&tid, NULL, handler, &acceptfd);pthread_detach(tid);}close(sockfd);return 0;
}

IO多路复用
优点: 节省资源, 减小系统开销,性能高;
缺点: 代码复杂性高

FTP项目

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>void list(int acceptfd);
void putfile(int acceptfd, char *p);
void getfile(int acceptfd, char *p);int main(int argc, char const *argv[])
{char buf[128] = {0};int ret, acceptfd;// 1创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0int len = sizeof(caddr);// 绑定if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}// 监听:将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen okk\n");while (1){acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){perror("accept err");return -1;}printf("ip:%s port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));// 接受消息while (1){ret = recv(acceptfd, buf, sizeof(buf), 0);if (ret < 0){perror("recv err");return -1;}else if (ret == 0){printf("client exit\n");break;}else{printf("buf:%s\n", buf);if (!strcmp(buf, "list"))list(acceptfd);else if (!strncmp(buf, "put ", 4))putfile(acceptfd, buf);else if (!strncmp(buf, "get ", 4))getfile(acceptfd, buf);}memset(buf, 0, sizeof(buf));}close(acceptfd);}close(sockfd);return 0;
}
/*列出服务器路径下除目录外的所有文件,并发送给客户端*/
void list(int acceptfd)
{struct dirent *p;struct stat st;// 打开目录DIR *dir = opendir(".");if (dir == NULL){perror("opendir err");return;}// 读目录while ((p = readdir(dir)) != NULL){// 判断是否为目录文件stat(p->d_name, &st);if ((st.st_mode & S_IFMT) != S_IFDIR)// 发送非目录文件send(acceptfd, p->d_name, sizeof(p->d_name), 0);}send(acceptfd, "end", 128, 0);closedir(dir);
}void putfile(int acceptfd, char *p)
{char buf[128] = {0};int ret;// 打开文件int fd = open(p + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open err");return;}// 循环接受客户端发来的文件内容,写到目标文件里while (1){ret = recv(acceptfd, buf, 128, 0);if (ret < 0){perror("recv file err");return;}else{if (!strcmp(buf, "end"))break;printf("%s", buf);write(fd, buf, strlen(buf));memset(buf, 0, sizeof(buf));}}close(fd);return;
}void getfile(int sockfd, char *p)
{char buf[128] = {0};ssize_t s;// 打开文件int fd = open(p + 4, O_RDONLY);if (fd < 0){perror("open err");return;}// 循环读源文件,发送给服务器while (1){s = read(fd, buf, sizeof(buf) - 1);buf[s] = '\0';if (s == 0){printf("send end\n");send(sockfd, "end", 128, 0);break;}else{printf("%s", buf);send(sockfd, buf, sizeof(buf), 0);}memset(buf, 0, sizeof(buf));}close(fd);return;
}
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>void list(int sockfd);
void show();
void putfile(int sockfd, char *p);
void getfile(int sockfd, char *p);int main(int argc, char const *argv[])
{char buf[128] = {0};// 创建流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[2]));saddr.sin_addr.s_addr = inet_addr(argv[1]);// 链接if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("connect err");return -1;}printf("connect ok\n");// 接受发送消息while (1){show();fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = '\0';send(sockfd, buf, sizeof(buf), 0);if (!strcmp(buf, "list"))list(sockfd);else if (!strncmp(buf, "put ", 4))putfile(sockfd, buf);else if(!strncmp(buf, "get ", 4))getfile(sockfd, buf);else if (!strcmp(buf, "exit"))break;}close(sockfd);return 0;
}void show()
{printf("----------------list----------------\n");printf("------------put filename------------\n");printf("------------get filename------------\n");printf("----------------exit----------------\n");
}void list(int sockfd)
{char buf[128] = {0};int ret;while (1){ret = recv(sockfd, buf, sizeof(buf), 0);if (ret < 0){perror("recv err");return;}else{if (!strcmp(buf, "end"))break;printf("%s ", buf);}memset(buf, 0, sizeof(buf));}putchar(10);return;
}
/*上传客户端路径下的文件到服务器路径下*/
void putfile(int sockfd, char *p)
{char buf[128] = {0};ssize_t s;// 打开文件int fd = open(p + 4, O_RDONLY);if (fd < 0){perror("open err");return;}// 循环读源文件,发送给服务器while (1){s = read(fd, buf, sizeof(buf) - 1);buf[s] = '\0';if (s == 0){printf("send end\n");send(sockfd, "end", 128, 0);break;}else{send(sockfd, buf, sizeof(buf), 0);}memset(buf, 0, sizeof(buf));}close(fd);return;
}void getfile(int acceptfd, char *p)
{char buf[128] = {0};int ret;// 打开文件int fd = open(p + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open err");return;}// 循环接受客户端发来的文件内容,写到目标文件里while (1){ret = recv(acceptfd, buf, 128, 0);if (ret < 0){perror("recv file err");return;}else{if (!strcmp(buf, "end"))break;write(fd, buf, strlen(buf));memset(buf, 0, sizeof(buf));}}close(fd);return;
}

setsockopt

set:设置 sock:套接字 option:属性

int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)功能:获得/设置套接字属性
参数:sockfd:套接字描述符level:协议层optname:选项名optval:选项值optlen:选项值大小
返回值:     成功 0                  失败-1

int 类型中 允许则为1或其他值 , 不允许则为0

设置地址重用

设置超时检测

通过函数自带参数设置

select:最后一个参数,通过结构体设置
poll:最后一个参数,毫秒级别
epoll_wait:最后一个参数,毫秒级别

setsockopt


image.png


alarm定时器与sigaction函数结合

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
功能:对接收到的指定信号处理
参数:signum:要捕获的信号act:接收到信号之后对信号进行处理的结构体oldact:接收到信号之后,保存原来对此信号处理的各种方式与信号(可用来做备份)。如果不需要备份,此处可以填NULL
struct sigaction 
{void     (*sa_handler)(int); //信号处理函数void     (*sa_sigaction)(int, siginfo_t *, void *);  //查看信号的各种详细信息sigset_t   sa_mask;int        sa_flags;      //信号属性; SA_RESTART自重启属性
#define SA_RESTART  0x10000000void     (*sa_restorer)(void);//不再使用};     //设置信号属性struct sigaction act;sigaction(SIGALRM,NULL,&act);//获取原属性act.sa_handler=handler;//修改属性sigaction(SIGALRM,&act,NULL);//将修改的属性设置回去
返回值:成功:0出错:-1,并将errno设置为指示错误
#include <stdio.h>
#include <unistd.h>
#include <signal.h>//SIGCHLDvoid handler(int sig)
{printf("time out......\n");
}int main(int argc, char const *argv[])
{// fcntlstruct sigaction act;sigaction(SIGALRM, NULL, &act);//获取原来的属性act.sa_handler = handler;//修改sigaction(SIGALRM, &act, NULL);//设置新的属性char buf[32] = {0};while (1){alarm(2);if (read(0, buf, 32) < 0){printf("errr\n");continue;}printf("buf:%s\n", buf);}return 0;
}

广播与组播(UDP)

1. 广播
● 前面介绍的数据包发送方式只有一个接受方,称为单播
● 如果同时发给局域网中的所有主机,称为广播
● 只有用户数据报(使用UDP协议)套接字才能广播
● 一般被设计成局域网搜索协议
● 广播地址:192.168.51.255
发送者(udp客户端)
1. 创建数据报套接字
2. 由于套接字原本的属性不允许广播,所以要给套接字设置广播属性
3. 填充网络信息
4. 发送消息
5. 关闭套接字

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>int main(int argc, char const *argv[])
{char buf[32] = {0};int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err\n");return -1;}// 设置套接字属性:广播属性int optval = 1;setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
//sockfd: 这是套接字描述符,是在之前用 socket() 函数创建的。
//SOL_SOCKET: 这是 setsockopt 的层参数,表示要设置的选项属于套接字级别。
//SO_BROADCAST: 这是选项参数,表示设置广播选项。
//&optval: 这是一个指向 optval 的指针,表示要设置的选项的值。在这个例子中,optval 是 1,表示启用广播。
//sizeof(optval): 这个参数指定了 optval 的大小。struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = inet_addr("192.168.51.255");int len = sizeof(caddr);while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = '\0';sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr,len);}close(sockfd);return 0;
}

接收者(udp服务器)
1. 创建数据报套接字
2. 填充结构体信息(~.255)
3. 绑定
4. 接收消息
5. 关闭套接字
缺点:
广播方式发给所有的主机,过多的广播会大量的占用网络带宽,造成广播风暴,影响正常的通信
广播风暴:
过多的广播大量的占用网络带宽,无法正常进行点对点通信
网络长时间被大量的广播数据包所占用,使正常的点对点通信无法正常进行,其外在表现为网络速度奇慢无比,甚至导致网络瘫痪

组播(多播)

● D类:224.0.0.0-239.255.255.255
● 单播方式只能发给一个接收方。
● 广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
● 组播是一个人发送,加入到多播组的人接收数据。
● 多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
发送者(udp客户端)
1. 创建数据报套接字
2. 指定网络信息
3. 发送消息
4. 关闭套接字
接收者(udp服务器)
1. 创建数据报套接字
2. 设置多播属性,将自己的IP加入多播组
3. 指定网络信息
4. 绑定
5. 接收消息
6. 关闭套接字

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <linux/in.h>
int main(int argc, char const *argv[])
{char buf[32] = {0};// udp服务器int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}设置多播属性struct ip_mreq req;req.imr_multiaddr.s_addr = inet_addr(argv[1]); // 组播IPreq.imr_interface.s_addr = INADDR_ANY;         // 自己的IPsetsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req));struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[2]));saddr.sin_addr.s_addr = inet_addr(argv[1]);int len = sizeof(caddr);if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}int ret;while (1){ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);if (ret < 0){perror("recvfrom err");return -1;}else{printf("%s\n", buf);}memset(buf, 0, sizeof(buf));}return 0;
}

本地套接字

特性
1. socket同样可以用于本地间进程通信,创建套接字时使用本地协议AF_LOCAL或AF_UNIX
2. 分为流式套接字和数据报套接字
3. 和其他进程间通信相比使用方便、效率更高,常用于前后台进程通信。
客户端 流程(流式)
1. socket
2. sockaddr_un
3. connect
4. send
5. close

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{char buf[128] = {0};// 创建流式套接字int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_un saddr;saddr.sun_family = AF_UNIX;strcpy(saddr.sun_path, "./myunix");// 链接if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("connect err");return -1;}printf("connect ok\n");// 接受发送消息while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = '\0';if (!strcmp(buf, "quit"))break;send(sockfd, buf, sizeof(buf), 0);}close(sockfd);return 0;
}

服务器 流程(流式)
1. socket
2. sockaddr_un
3. bind
4. listen
5. accept
6. recv
7. close

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char const *argv[])
{char buf[128] = {0};int ret, acceptfd;// 1创建套接字int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 指定网络信息struct sockaddr_un saddr;saddr.sun_family = AF_UNIX;strcpy(saddr.sun_path, "./myunix");// 删除文件//system("rm ./myunix");unlink("./myunix");//专门删除文件// 绑定:创建并打开套接字文件if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}// 监听:将主动套接字变为被动套接字if (listen(sockfd, 6) < 0){perror("listen err");return -1;}printf("listen okk\n");while (1){acceptfd = accept(sockfd, NULL, NULL);if (acceptfd < 0){perror("accept err");return -1;}// printf("ip:%s port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));//  接受消息while (1){ret = recv(acceptfd, buf, sizeof(buf), 0);if (ret < 0){perror("recv err");return -1;}else if (ret == 0){printf("client exit\n");break;}elseprintf("buf:%s\n", buf);memset(buf, 0, sizeof(buf));}close(acceptfd);}close(sockfd);return 0;
}

三次握手与四次挥手

三次握手
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
服务器必须准备好接受外来的连接。这通过调用socket、 bind和listen函数来完成,称为被动打开(passive open)。
第一次握手:客户通过调用connect进行主动打开(active open)。这引起客户TCP发送一个SYN(表示同步)分节(SYN=J),它告诉服务器客户将在连接中发送到数据的初始序列号。并进入SYN_SEND状态,等待服务器的确认。
第二次握手:服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个字节向客户发送SYN和对客户SYN的ACK(表示确认),此时服务器进入SYN_RECV状态。
第三次握手:客户收到服务器的SYN+ACK。向服务器发送确认分节,此分节发送完毕,客户服务器进入ESTABLISHED状态,完成三次握手。

客户端的初始序列号为J,而服务器的初始序列号为K。在ACK里的确认号为发送这个ACK的一端所期待的下一个序列号。因为SYN只占一个字节的序列号空间,所以每一个SYN的ACK中的确认号都是相应的初始序列号加1.类似地,每一个FIN(表示结束)的ACK中的确认号为FIN的序列号加1.
完成三次握手,客户端与服务器开始传送数据,在上述过程中还有一些重要概念。
未连接队列:在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户端确认包。这些条目所标识的连接在服务器处于SYN_RECV状态,当服务器收到客户端确认包时,删除该条目,服务器进入ESTABLISHED状态。

为什么式三次握手,而不是两次握手?
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而导致不必要的错误和资源的浪费。
两次握手只能保证单向连接是畅通的。因为TCP是一个双向传输协议,只有经过第三次握手,才能确保双向都可以接收到对方的发送的数据。
举例:
假如说有两次连接
第一次连接
第一次握手:客户端发送SYN,由于网络拥堵没有及时地到达服务器
客户端此连接关闭
第二次连接
第一次握手:客户端发送SYN,第一次连接的握手包也到达了服务器,第一次连接的第二次握手成功服务回复ACK消息
现在服务器认为建立了链接,客户端认为没有建立连接
现在第二次连接的第一次握手包也到达了服务器
第二次握手:服务器收到SYN包之后,回复ACK确认包给客户端
服务器认为建立了两次连接,但是客户端只认为建立了一次

四次挥手

image.png


第一次挥手:某个应用进程首先调用close,我们称这一端执行主动关闭。这一端的TCP于是发送一个FIN分节,表示数据发送完毕。
第二次挥手:接收到FIN的另一端执行被动关闭(passive close)这个FIN由TCP确认。它的接收也作为文件结束符传递给接收端应用进程(放在已排队等候应用进程接收到任何其他数据之后)
第三次挥手:一段时间后,接收到文件结束符的应用进程将调用close关闭它的套接口。这导致它的TCP也发送一个FIN。
第四次挥手:接收到这个FIN的原发送端TCP对它进行确认。

image.png

网络协议头

数据的封装与传递过程
思考:
1. 应用层调用send后,是如何把数据发送到另一台机器的某个进程的
2. 接收的设备收到数据包后,如何处理给应用层?

思考:在协议栈封装的过程中,这些头部信息具体有什么呢???

以太网帧完整帧

● 对于网络层最大数据帧长度是1500字节
● 对于链路层最大数据长度是1518字节(1500+14+CRC)
● 发送时候,IP层协议栈程序检测到发送数据和包头总长度超过1500字节时候,会进行自动分包处理,接收端在IP层进行包重组,然后才继续往上传递

TCP粘包、拆包发生原因
发生TCP粘包或拆包有很多原因,常见的几点:
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(传输层的最大报文长度),将进行拆包(到网络层拆包 - id ipflags )。
3、要发送的数据小于TCP发送缓冲区大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

image.png


粘包解决办法:
解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度,这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
4. 延时发送

https://www.cnblogs.com/111testing/p/12810253.htmlicon-default.png?t=N7T8https://www.cnblogs.com/111testing/p/12810253.html以太网头部

image.png


IP头


image.png

IHL:数据流量控制

image.png


面试:用于拆包、组包的头? IP头 (网络层)
IP哪些内容用于拆包? ID flag
IP里面包含了两个地址,哪两个?源地址,目标地址

TCP头

UDP头

image.png


UDP不会造成粘包和拆包, TCP不会造成丢包
UDP是基于数据报文发送的,每次发送的数据包,在UDP的头部都会有固定的长度, 所以应用层能很好的将数据包分隔开, 不会造成粘包。
UDP不会造成拆包, 但会出现拆包, 这个拆包是在网络层的IP头进行的拆包(判断MTU)。
TCP是基于字节流的, 每次发送的数据报,在TCP的头部没有固定的长度限制,也就是没有边界,那么很容易在传输数据时,把多个数据包当作一个数据报去发送,成为了粘包,或者传输数据时, 要发送的数据大于发送缓存区的大小,或者要发送的数据大于最大报文长度, 就会拆包;
TCP不会丢包,因为TCP一旦丢包,将会重新发送数据包。(超时/错误重传)
为什么UDP会造成丢包:
UDP通信没有发送缓存区, 它不保证数据的可靠性。因此,UDP通信是将数据尽快发送出去,不关心数据是否到达目标主机. 但是UDP有接受缓存区, 因为数据发送过快, 如果接收缓存区内数据已满, 则继续发送数据, 可能会出现丢包。
丢包出现原因: 接收缓存区满 网络拥堵, 传输错误

wireshark抓包

安装

虚拟机

安装:sudo apt-get install wireshark
启动:sudo wireshark

windows

关闭防火墙,可以直接默认安装

打开抓包工具,选择网卡

windows下:

image.png

抓包工具的界面介绍如下:

image.png


过滤其他无关的包,命令可以选择:tcp.port==8888 and ip.addr=="192.168.51.198"


image.png




image.png

SQL数据库

常用的数据库

大型数据库 :Oracle
中型数据库 :Server是微软开发的数据库产品,主要支持windows平台
小型数据库 : mySQL是一个小型关系型数据库管理系统。开放源码 (嵌入式不需要存储太多数据)

mySQL与SQLite区别:
MySQL和SQLite是两种不同的数据库管理系统,它们在多个方面有所不同。

1. 性能和规模:MySQL通常用于大型应用程序和网站,它可以处理大量数据和高并发访问。SQLite则更适合于小型应用程序或移动设备,因为它是一个轻量级的数据库引擎,不需要独立的服务器进程,可以直接访问本地文件。

2. 部署和配置:MySQL需要单独的服务器进程来运行,需要配置和管理数据库服务器。而SQLite是一个嵌入式数据库,可以直接嵌入到应用程序中,不需要单独的服务器进程。

3. 功能和特性:MySQL提供了更多的功能和高级特性,比如存储过程、触发器、复制和集群支持等。SQLite则是一个轻量级的数据库引擎,功能相对较少,但对于简单的数据存储和检索已经足够。

4. 跨平台支持:SQLite在各种操作系统上都能够运行,而MySQL需要在特定的操作系统上安装和配置数据库服务器。
总之,MySQL适用于大型应用程序和网站,需要处理大量数据和高并发访问,而SQLite适用于小型应用程序或移动设备,对性能和规模要求没有那么高。

SQL基础

SQLite的源代码是C,其源代码完全开放。它是一个轻量级的嵌入式数据库。
SQLite有以下特性:
        零配置一无需安装和管理配置;
        储存在单一磁盘文件中的一个完整的数据库;
        数据库文件可以在不同字节顺序的机器间自由共享;
        支持数据库大小至2TB(1024G = 1TB);//嵌入式足够
        足够小,全部源码大致3万行c代码,250KB;
        比目前流行的大多数数据库对数据的操作要快;
创建:
手动:
        使用sqlite3工具,手工输入命令
        命令行输入
代码:
        利用代码编程,调用接口

虚拟机中安装sqlite3

源码安装:
tar xf sqlite-autoconf-3140100.tar.gz
cd sqlite-autoconf-3140100
./configure
make
sudo make install
安装完成后测试安装是否成功
sqlite3 -version
3.11.0 2016-02-15 17:29:24 3d862f207e3adc00f78066799ac5a8c282430a5f
图形化:
sudo apt-get install sqlitebrowser

基础SQL语句使用

命令的方式操作

格式:sqlite3 数据库文件名(stu.db)

(创建一个新的数据库)

两种命令:

1. sqlite3系统命令(类似Windows系统命令,开机关机等,都是以.开头的)

都是以 '.' 开头的

a. .help 查看所有支持的命令

b. .quit 退出

c. .tables 查看有哪些表

d. .schema stu2 查看表结构

2. SQL命令 (具体对数据库怎样操作,对数据库增删改查用SQL命令)

SQL命令是以 “;” 结尾

在库当中创建一个表

(在数据库里面不严格检查数据类型,char可以表示字符,也可以表示字符串

1创建一个表

create table stu(id int,name char,score float);

create table stu1(id int primary key, name char, score float);

注:把id字段设置为主键(在表中唯一);

字符串:char string text

小数:float real

不支持严格的类型检查的;

2 删除一个表

drop table <table_name>

...>;

3》 向表里面插入数据

insert into <table_name> values(value1, value2,…);

insert into stu values(1,'xiaomingx',99.9);

//只插入部分字段 id name score

insert into stu(id,name) values(4,'xiaoming')

4 查找数据

查询表中所有记录

select * from <table_name>;

(*表示查询所有的值)

按指定条件查询表中记录

select * from <table_name> where <expression>;

select * from stu where id=2;

select * from stu where id=2 and name='lisi';

select * from stu where id=1 or name='zhangsan';

select score from stu where name='LiSi' or id=3; //满足条件的某列

select name,score from stu where name='LiSi' or id=3;

select * from stu limit 5; //只查询前n条记录

select * from stu order by id desc; //按id从大到小进行排序

5 修改(更新)数据

update <table_name> set <f1=value1>, <f2=value2>… where <expression>;

update stu set id=10 where id=1;

6 增加字段

alter table <table> add column <field> <type> default …;

alter table stu add column class int default 1;

(表示添加了一列class,默认值为1)

7 删除字段(在数据库当中其实不支持直接删除一个字段(及一列),

如果就想删除一列,那么需要三步骤)

1)创建一个student表,从stu表当中复制id,name,score

2) drop table stu;删除原有的stu表

 3) alter table student rename to stu; 重命名

最后一列为1的被删除掉了。

8》删除一行

操作完以后可以图形化界面修改东西,然后在命令行去查看的时候就被修改了。

或者

为什么不用图形化界面而是使用命令方式操作:

因为嵌入式里面用C写代码,C代码里面想实现对数据库进行操作,

用的就上面的命令,而C里面你不能在里面嵌套图像化界面。

sqlite3编程

官方文档:List Of SQLite Functions

头文件:#include <sqlite3.h>

编译:gcc sqlite1.c -lsqlite3

打开数据库
int sqlite3_open(char  *path, sqlite3 **db);
功能:打开sqlite数据库,如果数据库不存在则创建它
参数:path: 数据库文件路径db: 指向sqlite句柄的指针
返回值:成功返回SQLITE_OK(0),失败返回错误码(非零值)
返回错误信息
char  *sqlite3_errmsg(sqlite3 *db);
功能:  打印错误信息
返回值:返回错误信息使用:   fprintf(stderr,"sqlite3_open failed %s\n",sqlite3_errmsg(db));
关闭数据库
int sqlite3_close(sqlite3 *db);
功能:关闭sqlite数据库
返回值:成功返回SQLITE_OK,失败返回错误码
执行sql语句
int sqlite3_exec(sqlite3 *db,                           /* An open database */const char *sql,                       /* SQL to be evaluated */int (*callback)(void*,int,char**,char**),/* Callback function */void *arg,                        /* 1st argument to callback */char **errmsg                     /* Error msg written here */
);功能:执行SQL操作
参数:db:数据库句柄sql:要执行SQL语句callback:回调函数(满足一次条件,调用一次函数,用于查询)再调用查询sql语句的时候使用回调函数打印查询到的数据arg:传递给回调函数的参数errmsg:错误信息指针的地址
返回值:成功返回SQLITE_OK,失败返回错误码
回调函数:
typedef int (*sqlite3_callback)(void *para, int f_num, char **f_value, char **f_name);功能:select:每找到一条记录自动执行一次回调函数
参数:para:传递给回调函数的参数(由 sqlite3_exec() 的第四个参数传递而来)f_num:记录中包含的字段数目f_value:包含每个字段值的指针数组(列值)f_name:包含每个字段名称的指针数组(列名)
返回值:成功返回SQLITE_OK,失败返回-1,每次回调必须返回0后才能继续下次回调
不使用回调函数执行SQL语句(只用于查询)
int sqlite3_get_table(sqlite3 *db, const  char  *sql, char ***resultp,  int *nrow,  int *ncolumn, char **errmsg);功能:执行SQL操作
参数:db:数据库句柄sql:SQL语句resultp:用来指向sql执行结果的指针nrow:满足条件的记录的数目(但是不包含字段名(表头 id name score))ncolumn:每条记录包含的字段数目errmsg:错误信息指针的地址
返回值:成功返回SQLITE_OK,失败返回错误码
#include <stdio.h>
#include <sqlite3.h>int callback(void *buf, int num, char **value, char **name);int main(int argc, char const *argv[])
{sqlite3 *db = NULL;// 打开数据库if (sqlite3_open("./stu.db", &db) != SQLITE_OK){// printf("stdout:%s\n",sqlite3_errmsg(db));fprintf(stderr, "open err:%s\n", sqlite3_errmsg(db));return -1;}printf("open ok\n");// 数据库操作char *errmsg = NULL;// 创建一张表if (sqlite3_exec(db, "create table stu(id int,name char,score float);",NULL, NULL, &errmsg) != SQLITE_OK){fprintf(stderr, "create err:%s\n", errmsg);// return -1;}printf("create table ok\n");// 添加数据//  if (sqlite3_exec(db, "insert into stu values(1,\"xiaodou\",69.99);",//                   NULL, NULL, &errmsg) != SQLITE_OK)//  {//      fprintf(stderr, "insert err:%s\n", errmsg);//      //return -1;//  }//  printf("insert ok\n");//  if (sqlite3_exec(db, "insert into stu values(2,\"ayuan\",70.00);",//                   NULL, NULL, &errmsg) != SQLITE_OK)//  {//      fprintf(stderr, "insert err:%s\n", errmsg);//      //return -1;//  }//  printf("insert ok\n");int num = 0, id;char name[32] = {0};float score;printf("请输入学生人数:");scanf("%d", &num);char sql[128] = {0};for (int i = 0; i < num; i++){printf("请输入学生学号 姓名 成绩:\n");scanf("%d %s %f", &id, name, &score);sprintf(sql, "insert into stu values(%d,\"%s\",%f);", id, name, score);if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){fprintf(stderr, "insert err:%s\n", errmsg);// return -1;}printf("insert ok\n");}// 查找数据// if (sqlite3_exec(db, "select * from stu;", callback, "hello", &errmsg) != SQLITE_OK)// {//     fprintf(stderr, "select err:%s\n", errmsg);//     return -1;// }char **result = NULL;int row = 0;    // 存放有几行符合条件的信息int column = 0; // 列数sqlite3_get_table(db, "select * from stu where id=1;", &result, &row, &column, &errmsg);int k = 0;printf("row:%d column:%d\n", row, column);for (int i = 0; i <= row; i++){for (int j = 0; j < column; j++)printf("%s ", result[k++]);putchar(10);}// 关闭数据库sqlite3_close(db);return 0;
}int callback(void *buf, int num, char **value, char **name)
{// 每查找到一次符合要求的数据便会调用一次callback回调函数static int d = 1;printf("buf:%s d:%d\n", (char *)buf, d++);// num:列数printf("num:%d\n", num);// name:列名for (int i = 0; i < num; i++)printf("%s ", name[i]);putchar(10);// value:内容for (int i = 0; i < num; i++)printf("%s ", value[i]);putchar(10);printf("----------------------\n");return 0; // 必须带,不然查找会报错
}

UDP聊天室

#include "head.h"link_t *link_creat();
void client_login(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
void client_quit(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);
void client_chat(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr);int main(int argc, char const *argv[])
{// 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 绑定struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(caddr);if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}printf("bind ok\n");// 创建链表link_t *p = link_creat();printf("creat ok\n");MSG_t msg;pid_t pid = fork();if (pid < 0){perror("fork err");return -1;}else if (pid == 0){while (1){// 可扩展功能,服务器可以给所有用户发送“公告”// 可以发送消息给自己fgets(msg.text, sizeof(msg.text), stdin);if (msg.text[strlen(msg.text) - 1] == '\n')msg.text[strlen(msg.text) - 1] = '\0';strcpy(msg.name, "server");msg.type = chat;sendto(sockfd, &msg, sizeof(MSG_t), 0,(struct sockaddr *)&saddr, sizeof(saddr));}}else{while (1){if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len) < 0){perror("recvfrom err");return -1;}printf("msg.name:%s\n", msg.name);switch (msg.type){case login: // 上线函数client_login(sockfd, p, msg, caddr);break;case chat: // 聊天函数client_chat(sockfd, p, msg, caddr);break;case quit: // 下线函数client_quit(sockfd, p, msg, caddr);break;default:break;}}}close(sockfd);return 0;
}
// 创建链表
link_t *link_creat()
{link_t *p = (link_t *)malloc(sizeof(link_t));if (p == NULL){printf("malloc err\n");return NULL;}p->next = NULL;return p;
}
// 客户端上线功能:将用户上线消息发送给其他用户,并将用户信息添加到链表中
void client_login(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{sprintf(msg.text, "%s login.....", msg.name);printf("%s\n", msg.text);while (p->next != NULL){p = p->next;sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&(p->addr), sizeof(p->addr));}link_t *pnew = (link_t *)malloc(sizeof(link_t));pnew->addr = caddr;pnew->next = NULL;p->next = pnew;return;
}
// 聊天功能:转发消息到在链表中除了自己的所有用户
void client_chat(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{printf("%s says:%s\n", msg.name, msg.text);while (p->next != NULL){p = p->next;if (memcmp(&(p->addr), &caddr, sizeof(caddr)) != 0)sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&(p->addr), sizeof(p->addr));}return;
}
// 客户端退出功能:将用户下线消息发送给其他用户,将下线用户从链表中删除
void client_quit(int sockfd, link_t *p, MSG_t msg, struct sockaddr_in caddr)
{sprintf(msg.text, "%s quit.....", msg.name);printf("%s\n", msg.text);link_t *pdel = NULL;while (p->next != NULL){if (!memcmp(&(p->next->addr), &caddr, sizeof(caddr))){pdel = p->next;p->next = pdel->next;free(pdel);pdel = NULL;}else{p = p->next;sendto(sockfd, &msg, sizeof(msg), 0,(struct sockaddr *)&(p->addr), sizeof(p->addr));}}return;
}
#include "head.h"int main(int argc, char const *argv[])
{// 创建套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd:%d\n", sockfd);// 绑定struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[2]));saddr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t len = sizeof(caddr);MSG_t msg;// 给服务器发送上线消息printf("请输入用户名:");fgets(msg.name, sizeof(msg.name), stdin);if (msg.name[strlen(msg.name) - 1] == '\n')msg.name[strlen(msg.name) - 1] = '\0';msg.type = login;sendto(sockfd, &msg, sizeof(MSG_t), 0, (struct sockaddr *)&saddr, sizeof(saddr));pid_t pid = fork();if (pid < 0){perror("fork err");return -1;}else if (pid == 0) // 循环接受服务器消息{while (1){if (recvfrom(sockfd, &msg, sizeof(MSG_t), 0, NULL, NULL) < 0){perror("recv from err");return -1;}else{if (msg.type == chat)printf("%s says:%s\n", msg.name, msg.text);elseprintf("%s\n", msg.text);}}}else // 循环发送消息{while (1){fgets(msg.text, sizeof(msg.text), stdin);if (msg.text[strlen(msg.text) - 1] == '\n')msg.text[strlen(msg.text) - 1] = '\0';if (!strcmp(msg.text, "quit")){msg.type = quit;sendto(sockfd, &msg, sizeof(MSG_t), 0,(struct sockaddr *)&saddr, sizeof(saddr));break;}else{msg.type = chat;sendto(sockfd, &msg, sizeof(MSG_t), 0,(struct sockaddr *)&saddr, sizeof(saddr));}}kill(pid, SIGKILL);wait(NULL);}close(sockfd);return 0;
}

网编内容梳理

发展:阿帕网-----因特网-----互联网------物联网

局域网、广域网、光猫、路由器

IP:概念:标识、IPV4(32,点分十进制) IPV6(128),分类(A、B、C、D、E),

特殊地址

子网掩码:作用(区分网络号和主机号),特点(网络号全为1,主机号全为0)

三级划分:重新组网,提高资源利用率

网络模型:OSI(7层:应、表、会、传、网、数、物)、TCP/IP(4层:应、传、网、网和物)每一层的功能、每层协议、DOS

socket:是什么、类型(流式、数据报、原始)、位置(应用层与传输层之间)

端口(2个字节)、字节序(网络、主机)

TCP与UDP区别,TCP高可靠性是什么、为什么,

TCP客户端、服务器流程,UDP客户端、服务器流程

linux IO模型:阻塞IO(最常见、效率低、不耗费cpu)、非阻塞IO(轮询、耗费cpu、可以处理多路IO)(设置方式:函数自带参数、fcntl改变文件描述符的属性)、信号驱动IO(异步通知方式、需要底层驱动的支持)、IO多路复用(可以同时处理多路IO,效率高)(select(特点、编程步骤、实现并发服务器))poll(特点、编程步骤)epoll(特点)

服务器模型:循环服务器、并发服务器(多进程、多线程、IO多路复用(优缺点))

setsockopt:设置套接字属性(地址重用、超时检测、广播、组播)

超时检测:必要性,设置方法(参数、setsockopt、alarm函数与sigaction)

广播、组播:将广播的发送者设置为广播属性(广播风暴),将组播的接收者设置为组播属性

本地套接字:在本地形成一个文件,进行通信

网络协议头:数据的封装、传递过程,数据帧组成,以太网头、IP头、TCP头、UDP头

粘包、拆包

三次握手、四次挥手

sql数据库:增(create inset)删(drop、delete)改(update)查(select)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python编码系列—Python项目架构的艺术:最佳实践与实战应用

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

(一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)

ZSWatch是一个包括硬件、软件和3D打印外壳全部开源的智能手表&#xff0c;软件功能丰富&#xff0c;并可与手机互联用来接收信息和切换歌曲&#xff0c;开源协议为GPL-3.0。 因为ZSWatch建立在Zephyr™项目RTOS之上&#xff0c;因此得名ZSWatch- Zephyr&#xff0c;目前该项目…

HANA5 游戏逆向

前言 某著名百合R18游戏 以前尝试逆过一次&#xff0c;半途而废了。今天想起来再逆一下&#xff0c;记录下逆向的过程。 游戏文件结构&#xff1a; 游戏资源extract 主要目标是弄明白游戏资源&#xff1a;SE、CG这些怎么加载解密的。 还是像万华镜那样下三个API断点&…

稚晖君智元机器人远程机器人系列发布:引领具身智能新高度

在最近的发布会上&#xff0c;前华为“天才少年”稚晖君及其团队亮相了他们的最新作品——智元机器人的第二代远程机器人系列。这次发布会不仅展示了丰富的产品线&#xff0c;还揭示了其未来的发展路线以及开源计划。本文将详细解析本次发布会的亮点和技术背后的创新。 一、发…

Django国际化和本地化

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) 本节主要介…

【Go函数详解】二、参数传递、变长参数与多返回值

文章目录 一、传递参数1. 按值传参2. 引用传参2.1 特殊情况2.1.1 切片slice2.1.2 字典map 二、变长参数1. 基本定义和传值1.1 基本定义1.2 传值1.2.1 普通传值1.2.2 传递切片 2. 任意类型的变长参数&#xff08;泛型&#xff09; 三、多返回值1. 命名返回值 一、传递参数 1. 按…

customRef 与 ref

ref() 我们已经很熟悉了&#xff0c;就是用来定义响应式数据的&#xff0c;其底层原理还是通过 Object.defineprotpty 中的 get 实现收集依赖( trackRefValue 函数收集)&#xff0c;通过 set 实现分发依赖通知更新( triggerRefValue 函数分发 )。我们看看 ref 的源码就知道了 …

微气象在线监测系统:宏观层面的电网灾害预防和应急管理

微气象受局部地形&#xff08;如山谷、河谷&#xff09;、地物&#xff08;如建筑物、森林&#xff09;和地面条件&#xff08;如水面、农田&#xff09;的影响较大&#xff0c;而大范围气象环境则更多地受气候系统和天气模式的控制。输电线路微气象监测的主要目的是为了评估和…

YOLOv8环境搭建、创建数据集、训练推理教程(超级详细)

yolov8和yolov10 是一个流派&#xff0c;和yolov5区别还挺大&#xff0c;所以尝试使用yolov8来进行模型训练&#xff0c;下面是详细使用流程&#xff1a; 一、环境搭建 1.1 Anaconda安装 Anaconda是一个强大的开源数据科学平台,它将很多好的工具整合在一起&#xff0c;极大地…

华为海思招聘-芯片与器件设计工程师-数字芯片方向- 机试题——(共九套)(每套四十题)

华为海思招聘-芯片与器件设计工程师-数字芯片方向- 机试题-题目分享——共九套&#xff08;每套四十题&#xff09; 岗位——芯片与器件设计工程师 岗位意向——数字芯片 真题题目分享&#xff0c;完整版带答案(有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&am…

论文阅读:VideoMamba: State Space Model for Efficient Video Understanding

论文地址&#xff1a;arxiv 摘要 为了解决视频理解中的局部冗余与全局依赖性的双重挑战。作者将 Mamba 模型应用于视频领域。所提出的 VideoMamba 克服了现有的 3D 卷积神经网络与视频 Transformer 的局限性。 经过广泛的评估提示了 VideoMamba 的能力&#xff1a; 在视觉领…

Hbuilder创建的项目(uniApp + Vue3)中引入UnoCSS原子css引擎

这里是UnoCSS的官网介绍 UnoCS通过简化和优化CSS的编写过程来提高Web开发的效率和可维护性。好处是&#xff1a; 提升开发效率提升开发效率提高一致性增强灵活性易于维护方便的集成与配置 同时还支持预设变量和规则。这些可参看官网进行配置。Unocss通过其原子化方法、高度的…

第二证券:静态市盈率与动态市盈率有什么区别?

市盈率&#xff08;PE&#xff09;&#xff0c;是指投资者愿意为每一元净利润所支付的价格。 股票的市盈率股票价格&#xff08;P&#xff09;/每股净利润&#xff08;EPS&#xff09;&#xff0c;或者用公司其时总市值/公司上一年总净利润。 动态市盈率与静态市盈率的区别&a…

<数据集>遥感航拍飞机和船舶和识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;19973张 标注数量(xml文件个数)&#xff1a;19973 标注数量(txt文件个数)&#xff1a;19973 标注类别数&#xff1a;2 标注类别名称&#xff1a;[ship,plane] 序号类别名称图片数框数1ship17575416292plane239815…

对比 PDAF、CDAF 和 LAAF 自动对焦技术

深入解析相位检测自动对焦&#xff08;PDAF&#xff09; 相位检测自动对焦&#xff08;PDAF&#xff0c;Phase Detection Auto Focus&#xff09;是一种高效的自动对焦技术&#xff0c;广泛应用于现代数码相机、无反相机和智能手机摄像头中。为了更好地理解 PDAF&#xff0c;我…

基于协同过滤算法的电影推荐系统的设计与实现(论文+源码)_kaic

摘 要 现在观看电影已逐渐成为人们日常生活中最常见的一种娱乐方式&#xff0c;人们通常会在周末或在休息、吃饭时间不由自主地在各种视频软件中搜索当前火热的影视节目。但是现在的视频软件电影推荐功能不够完善&#xff0c;所以需要开发出一套系统来使用户只需要简单操作就能…

华为云征文|部署私有云和文档管理系统 Kodcloud

华为云征文&#xff5c;部署私有云和文档管理系统 Kodcloud 一、Flexus云服务器X实例介绍1.1 云服务器介绍1.2 应用场景1.3 对比普通ECS 二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 Kodcloud3.1 Jellyfin 介绍3.2 Docker 环境搭建3.3 Jell…

【智能算法改进】路径规划问题的多策略改进樽海鞘群算法研究

目录 1.算法原理2.改进点3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】樽海鞘群算法&#xff08;SSA)原理及实现 2.改进点 无标度网络策略 复杂网络在图论中可以用边和节点表示&#xff0c; Barabasi 等于1999年通过分析大量的数据提出了无标度网络模型. 该网络…

人像比对-人证比对-人脸身份证比对-人脸身份证实名认证-人脸三要素对比-实人认证

​ 人证比对API接口&#xff0c;全称为人脸身份证比对API接口&#xff0c;也被称为人脸实名认证API接口或实人认证API接口。这种接口服务主要用于将提供的人脸图片和对应的身份证照片、姓名、身份证号码进行比对&#xff0c;以此验证用户的身份。以下是关于人证比对API接口的详…

[易聊]软件项目测试报告

一、项目背景 随着互联网发展&#xff0c;各种各样的软件&#xff0c;比如游戏、短视频、购物软件中都有好友聊天功能&#xff0c;这是一个可在浏览器中与好友进行实时聊天的网页程序。“ 易聊 ”相对于一般的聊天软件&#xff0c;可以让用户免安装、随时随地的通过浏览器网页…