华清远见学习笔记
1.网络编程
进程间通信(程序是静态的)
//数据传输步骤要遵循一定的模型进行网络通信设计
api == 接口 == 函数
ARP是主要完成IP地址向物理地址的转换;
ARP协议:地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议;
RARP协议:反向地址转换协议(RARP:Reverse Address Resolution Protocol) 允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址;
2. osi模型
osi模型(一共7层)是理想化模型//一定不会存在数据丢失
1层---应用层:软件层面的协议,接收数据//app
2层---表示层:数据加密解析
//前两层一般是放在一起使用
3层---会话层:建立逻辑名字和物理名字的联系
//数据交互是硬件上的交互 逻辑:程序操作 物理:硬件方面,数据存在的物理位置(地址
//前三层都是应用层
4层---传输层:确保数据的传输,流量控制
//接收到数据后进行回应,(自己写的协议)保证数据的正常传输
5层---网络层:数据分组,选择路由
//通过路由器发出信息,路由器选择A到B的传输路线(自动)
//选多条线路同时发送,接收到一条线路的数据,其他数据不再接收
6层---数据链路层:把数据组装成可以发送的数据帧的格式
7层---物理层:把数据转化成具体的物理信号,传递给另外一端的物理接口
3.TCP/IP协议模型
3.1 具体实现
TCP/IP协议模型只是将osi模型做了一个压缩(一共四层)
1层---==应用层==:应用、表示、会话层
//组装应用头和tail
2层---传输层:TCP协议、UDP协议
//组装TCP头信息
3层---网络层:让不同网络连接起来//IP协议
//组装IP头信息
4层---网络接口和物理层:数据组装和发送
//组装net头,选择路由器
3.2 tcp协议与udp协议
tcp:稳定可靠,有连接,无失序,数据流式
数据流式:TCP把数据看成一连串无结构的字节流,都在一个缓冲区;
udp:不稳定不可靠,无连接,有失序,数据报式
UDP是面向报文的,数据报式。
3.3 区别
1、 TCP面向连接 (如打电话要先拨号建立连接); UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。
Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
3.4 TCP协议标识
ip地址:标识唯一主机 ipv4 4字节 点分形式:192.168.7.33 ifconfig查看ip
端口号:标识进程 2字节 0-1023 系统内核软件接口 1024-5000 可以使用(通常情况下系统软件端口号固定) 5001- 65535 建议使用
地址族:说明ip类型(如:ipv4或ipv6)
3.5 三次握手
SYN请求连接信号
0未确认;1代表确认
seq信息编号
记录发送数据的编号//字节数依次累加
ACK确认信号//确认收到消息
0未确认;1代表确认
ack确认发送的包的编号
值是seq+1
第一次
客户端向服务器发送建立连接请求:(SYN = 1)通过标志位置1
数据包编号:seq = x
客户端:CLOSE -> SYN_SEND
第二次
服务器收到客户端请求:发送建立连接请求:(SYN = 1)
数据包编号:seq = y
回复客户端请求:(ACK = 1, ack = x+1)
服务器:LISTEN -> SYN_RECV
第三次
客户端收到请求:回复服务器:(ACK = 1, ack = y+1)
数据包编号:seq = x+1
客户端:SYN_SEND -> ESTABLISHED
服务器收到最后一条信息之后
服务器:SYN_RCVD -> ESTABLISHED
3.6 四次挥手
第一次
客户端向服务器发送断开连接请求:(FIN = 1),数据包编号u
客户端:ESTABLISHED -> FIN_WAIT_1
第二次
服务端收到客户端请求:发送(ACK = 1, ack = u+1)//确认分手信息,剩余数据(seq = v)
服务器:ESTABLISHED -> CLOSE_WAIT
客户端:FIN_WAIT_1 -> FIN_WAIT_2
第三次
服务器向客户端发送断开连接请求:(FIN = 1, seq = w, ack = u+1)
服务器:CLOSE_WAIT -> LAST_ACK
客户端:FIN_WAIT_2 -> TIME_WAIT3
服务器:LAST_ACK -> CLOSED
客户端:FIN_WAIT_2 -> TIME_WAIT等待两个最长报文时间进入CLOSED
4. TCP-CS架构
4.1 服务器
4.1.1 创建套接字
int socket(int domain, int type, int protocol)
参数:
地址族 AF_INET //表示ipv4
套接字类型为tcp SOCK_STREAM //流式套接字协议类型为TCP
协议 默认0//TCP协议无子协议
返回值:
成功: 套接字文件描述符
失败: -1
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
4.1.2 绑定套接字信息
1. 函数代码
将socket创建的套接字与本地协议地址进行绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
套接字socket返回值(套接字文件描述符)
addr:存储信息的结构体地址//指向特定协议的地址结构的指针
{6
127.0.0.1//本机回环地址,只用来做测试
0.0.0.0//本机地址
}
addrlen: 结构体大小
//通用结构体
struct sockaddr
{
sa_family_t sa_family; //地址族
char sa_data[14];
}
//ipv4专用结构体
struct sockaddr_in
{
sa_family_t sin_family;//地址族 AF_INET
int_port_t sin_port;//进程端口号 htons()
struct in_addr sin_addr;//存储ip地址的结构体 inet_addr("0")
}
struct in_addr
{
uint32_t s_addr;//无符号的32位(4字节)整型 “点分形式地址”
}
返回值:成功0;失败-1
2. 大小端与网络字节序
大小端两种存储方式由硬件决定
ip协议规定统一转化为网络字节序(大端)
#include <arpa/inet.h>
htons()//把短整型转化为网络字节序;
ntohs()//把网络字节序转化为短整型;
inet_addr()//把字符串转化为网络字节序;
inet_ntoa()//把网络字符串转化为字符串;
大端存储:低字节存高位,高字节存低位;
小端存储:低字节存低位,高字节存高位;
3. 示例
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));//结构体清零
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);//转化为网络字节序
addr.sin_addr.s_addr = inet_addr("127.0.0.1");//本机回环地址
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))//不是所有的地址都可以强转
//要求通用结构体类型,用强转;
{
perror("bind");
exit(-1);
}
4.1.3 监听套接字
等待客户端连接,设置监听队列;tcp连接:三次握手
1. listen函数
int listen(int sockfd, int backlog);
参数:
套接字文件描述符;
监听队列的大小;//一般三到四个
返回值:成功0;失败-1
2.示例
if(-1 == listen(sockfd, 3))
{
perror("listen");
exit(-1);
}
4.1.4 被动等待连接
阻塞直到客户端连接
1. accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
服务器文件描述符
客户端返回的信息//不想接收客户端信息填NULL
客户端结构体大小
返回值:
成功:返回客户端的文件描述符
失败:-1
2. recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
指定客户端发送,服务器接收,则为客户端套接字描述符;//从哪收到
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数指flags读的方式 一般置0,和read使用方式一样。
//recv的返回值是接收到的字节数;失败返回-1,表示客户端退出。
3. send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
指定客户端发送,服务器接收,则为服务器套接字描述符;//发送给谁
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
buf的长度;
flags读的方式 一般置0,此时和read使用方式一样。
//send的返回值是发送的字节数;失败返回-1,表示客户端退出。
4.1.5 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1){
perror("socket");
exit(-1);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));//结构体清零
addr.sin_family = AF_INET;
addr.sin_port = htons(6000);//转化为网络字节序
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
exit(-1);
}
if(-1 == listen(sockfd, 3))
{
perror("listen");
exit(-1);
}
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
int connfd = accept(sockfd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
while(1)
{
char buf[20];
int ret = recv(connfd, buf, sizeof(buf), 0);
buf[ret] = 0;
puts(buf);
}
return 0;
}
4.2 客户端通信
4.2.1 创建套接字
同服务器
4.2.2绑定(可省略)
4.2.3 交互—connect函数
函数说明:connect()用来将参数sockfd 的socket 连至参数serv_addr 结构体指定的网络地址;
如果客户端没有去connect, 那么服务端的accept会一直在那里等待;
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//connect 不会返回文件描述符 用sockfd表示将客户端连接到服务器的信息
参数:
客户端的套接字
服务器信息结构体
结构体大小
返回值:成功返回0;错误时返回错误码;
4.2.4 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1){
perror("socket");
exit(-1);
}
//2.连接
//创建结构体存储服务器的信息
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(6000);
addr.sin_addr.s_addr = inet_addr("192.168.0.7");
int confd = co(sockfd, (struct sockaddr *)&addr, sizeof(addr));
if(-1 == confd)
{
perror("connect");
return -1;
}
printf("connect a server\n");
/*
pid_t pid;
pid = fork();
*/
while(1)
{
char buf[20];
/*if(pid == 0){
while(1){
int ret = recv(sockfd, buf, sizeof(buf), 0);
buf[ret] = 0;
printf("服务器:\n");
puts(buf);
}
}
*/
fgets(buf, sizeof(buf), stdin);
send(sockfd, buf, sizeof(buf),0);
}
return 0;
}
5 并发服务器模型
5.1 多进程实现
5.1.1 步骤
int main()
{
1.创建套接字 socket
2.绑定套接字 bind
3.监听套接字 listen
捕获17号信号,回收子进程资源 signal
while(1)
{
4.被动等待连接 accept
fork()
if(pid == 0)
{
处理客户端请求
if(0==read)
close(connfd);
exit(0);
}
}
close(sockfd);
}
5.1.2 优缺点
优点:相互独立,抗干扰;
缺点:开辟进程的数量是有限的,占用资源;进程交互比较麻烦。
5.1.3 示例代码
void func(int arg)
{
wait(NULL);//回收所有的子进程资源(不包括孙子进程)
}
int main(int argc, char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.绑定套接字信息
//先把套接字的信息存入结构体
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));//客户端口号由命令行参数输入
//atoi函数可以把字符串转换成整型数
addr.sin_addr.s_addr = inet_addr("0");
//由bind函数将创建的套接字和信息进行绑定
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
//3.设置监听队列
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
//4.被动等待连接
//创建结构体存储客户端返回的信息
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
signal(17, func);
//捕捉结束进程产生的SIG信号,用于在func函数中回收资源
//在内核注册的回调函数,放到循环内部也只会注册一次
//由accept函数将客户端的信息接入服务器
while(1)
{
int connfd = accept(sockfd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));//打印IP地址和进程端口号
//创建读写进程
pid_t pid;
pid = fork();
if(pid == 0)
{//在子进程中再次创建孙子进程
signal(17, func);//孙子进程结束时回收资源
pid_t pid1 = fork();
char buf[20];
if(pid1 == 0)
{//在孙子进程中实现从客户端接收数据
while(1)
{
int ret = recv(connfd, buf, sizeof(buf), 0);
if(ret == 0)
{
close(connfd);
exit(0);
}
buf[ret] = 0;
puts(buf);
}
}
else if(pid1 < 0)//判断孙子进程是否创建成功
{
perror("fork");
exit(-1);
}
while(1)//在子进程中向客户端发送数据
{
fgets(buf, sizeof(buf), stdin);
send(connfd, buf, sizeof(buf),0);
}
}
}
close(socket);
return 0;
}
5.1.4 signal函数
设置某一信号的对应动作
捕获17号信号,回收子进程;
5.2 多线程实现
5.2.1 步骤
int main()
{
1.创建套接字 socket
2.绑定套接字 bind
3.监听套接字 listen
while(1)
{
4.被动等待连接 accept
pthread_create();
pthread_detach();
}
close(sockfd);
}
void*func(int *arg)
{
while(1)
{
处理客户端请求;
if(0 == read)
{
break;
}
}
pthread_exit(NULL);
}
5.2.2 优缺点
优点:交互简单(全局变量),资源占用少;
缺点:抗干扰能力弱;需要考虑共享数据的同步与互斥。
分离属性:detach
自己回收资源
结合属性:默认属性
主线程回收资源
5.2.3 示例
void*func(void* arg)
{
int connfd = *(int*)arg;//通过arg将connfd传入funch
while(1)
{
char buf[20];
int ret = recv(connfd, buf, sizeof(buf), 0);
buf[ret] = 0;
if(0 == ret)
{
break;
}
puts(buf);
fgets(buf, sizeof(buf), stdin);
send(connfd, buf, sizeof(buf),0);
}
}
***********************************************************************************
int main(int argc, char *argv[])
{
//1.
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
//3.
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
//4.被动等待连接
//创建结构体存储客户端返回的信息
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
//在循环中用accept打开多个接口,可以接入多个客户端
while(1)
{
int connfd = accept(sockfd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));//打印IP地址和进程端口号
***********************************************************************************
//创建一个线程,将accept的返回值作为参数传递
pthread_t thread;
if(0 != pthread_create(&thread, NULL, func, (void*)&connfd))
{
perror("create");
exit(-1);
}
//pthread_detach() 线程设置为分离属性,子线程可以自己回收资源
if(0 != pthread_detach(thread))
{
perror("detach");
exit(-1);
}
}
5.3 IO多路复用
见7.4
6. UDP-CS架构
接收信息时只需要有端口号和ip地址即可,不需要结构体信息绑定;发送信息时需要用结构体绑定接收端的信息
以下实现单播书写方式
6.1 服务器
6.1.1 创建套接字
int socket(int domain, int type, int protocol)
参数:
地址族 AF_INET //表示ipv4
套接字类型为tcp SOCK_DGRAM //流式套接字协议类型为UDP
协议 默认0//TCP协议无子协议
返回值:
成功: 套接字文件描述符
失败: -1
6.1.2 绑定-bind函数
6.1.3 交互-recvfrom函数
while(1){
...
}//实现多次读写操作
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
参数:recv参数+accept参数
recv参数:
指定客户端发送,服务器接收,则为客户端套接字描述符;//从哪收到
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数指flags读的方式 一般置0,和read使用方式一样。
第五个参数
accept参数:
客户端返回的信息//不想接收客户端信息填NULL
客户端结构体大小//可以填NULL
6.1.4 示例
服务器
{
1.创建套接字 SOCK_DGRAM
2.绑定套接字信息
while(1)
{
交互
}
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
}
服务器接收到“time”,给客户端返回时间信息;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1){
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));//结构体清零
addr.sin_family = AF_INET;
addr.sin_port = htons(argv[1]);//转化为网络字节序
addr.sin_addr.s_addr = inet_addr("0");//本机回环地址
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))//不是所有的地址都可以强转
//要求通用结构体类型,用强转;
{
perror("bind");
exit(-1);
}
char buf[20];
while(1){
int ret = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
if(-1 == ret)
{
perror("recv");
return -1;
}
buf[ret] = 0;//只有接收数据前才需要组装字符串
puts(buf);
}
6.2 客户端
6.2.1 创建套接字
同6.1.1 创建套接字
6.2.2 绑定套接字(可省略)
6.2.3 交互-sendto函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
send参数+accept参数
send参数:
指定客户端发送,服务器接收,则为服务器套接字描述符;//发送给谁
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
buf的长度;
flags读的方式 一般置0,此时和read使用方式一样。
accept参数:
返回的信息//不想接收客户端信息填NULL
客户端结构体大小
6.2.4 示例
客户端
{
1.创建套接字 SOCK_DGRAM
2.绑定套接字信息(可省略)
while(1)
{
交互
}
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
}
客户端发送time,服务器返回时间信息;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1){
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));//结构体清零
addr.sin_family = AF_INET;
addr.sin_port = htons(argv[1]);//转化为网络字节序
addr.sin_addr.s_addr = inet_addr("0");//本机地址
char buf[20];
fgets(buf, sizeof(buf), stdin);
while(1){
int ret = sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
if(-1 == ret)
{
perror("send");
return -1;
}
}
7. IO模型
7.1 阻塞
read fgets recv
缺点:阻塞之后没办法执行其他功能
优点:逻辑简单,资源占用低
7.2 非阻塞
7.2.1 实现函数 fcntl
write 缓存区满(阻塞)
缺点:需要循环遍历,cpu占用高
优点:不会影响其他功能执行
int fcntl(int fd, int cmd, ... /* arg */ );
参数
fd:需要操作的文件描述符;
cmd:对文件操作的命令;
不定参数:
F_GETFL (void);
//返回文件描述符的flags(权限)
F_SETFL (int);
//设置新的flags(权限)
7.2.2 示例
while(1){
int flags = fcntl(0, F_GETFL, 0);
//0和stdin都表示终端输入,0是文件描述符;stdin是流指针
flags |= O_NONBLOCK;;//加权限,原来是1还是1
fcntl(0, F_SETFL, flags);
...
}
7.3 信号驱动
7.4 IO多路复用(3种)
同时监听多个IO,哪个IO有内容就处理哪个;
文件描述符和存储位置下标一致 —0 a[0]
没加之前所有清0
FD-zero
关注的位置置1
FD_set
交给内核
select
从0开始遍历,从关注的最大的文件描述符后结束
置1位 内核拷贝到位图,进行覆盖
之后,重新清0
7.4.1 select函数
把关心的位图传递给内核,内核会拷贝一份,让内核帮忙轮循,如果有准备好的事件,就标记并且返回准备好的文件描述符个数。
32位1024;64位2048
管道文件,socket, stdin可以操作
1. 函数说明
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数:
nfds:最大文件描述符个数+1;
readfds:读事件的位图;
writefds:写事件的位图;
exceptfds:其他事件;
timeout:超时检测;//等一段事件还没准备好,就继续运行其他程序
struct timeval {
long tv_sec; //秒
long tv_usec; //微秒
};
void FD_CLR(int fd, fd_set *set);//把指定文件描述符从位图删除
int FD_ISSET(int fd, fd_set *set);//判断关注的文件是否准备好;0准备好,-1未准备好
void FD_SET(int fd, fd_set *set);//把关心的文件描述符置1
void FD_ZERO(fd_set *set);//位图全部清0
参数:文件描述符--sockfd
返回值
成功返回准备好的文件描述符个数,失败返回-1;0代表超时;
int fd1, fd2, fd3;
fd1 = open()
fd2 = open()
fd3 = open()
//用户空间(1-3G)
fd_set readfd, temp;//创建读操作的位图
FD_ZERO(&readfd);//清空位图
FD_SET(fd1, &readfd);//
FD_SET(fd2, &readfd);//
FD_SET(fd3, &readfd);
//拷贝到内核,把关心的文件描述符记录下来后,将内核中的位图清空(内核空间-3-4G),进行轮巡遍历
//当关心的IO有准备好的,此位图置1,遍历完后拷贝回用户空间(覆盖原位图),for循环判断置1位,进行读写操作
int nfds = fd3+1;
temp = readfd;//保存一份关心位置1位的位图
while(1)
{
readfd = temp;//再将temp中保存的关心位图传到内核,继续监听
select(nfds,&readfd,NULL,NULL,NULL);
for(i = 0; i < nfds; i++)
{
if(FD_ISSET(i, &readfd))
{
read()
}
}
}
2. 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//创建tcp协议套接字文件描述符,有客户端连接会有写的信息准备好
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;//ipv4协议
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");
//绑定套接字
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
//监听
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
//创建客户端信息结构体
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
//创建读操作位图
fd_set readfd, tempfd;
FD_ZERO(&readfd);//清0
FD_SET(sockfd, &readfd);//将关注的sockfd在位图中置1
int nfds = sockfd+1;//最大文件描述符+1,现在只关心sockfd
tempfd = readfd;//保存关心的位图
while(1)
{
readfd = tempfd;
if(-1 == select(nfds, &readfd, NULL, NULL, NULL))//判断select//第一次判断sockfd是否准备好
{
perror("select");
return -1;
}
//文件描述符有两种:sockfd连接、connfd通信
//sockfd相当于服务器多了一个通信管道
int i;
for(i = sockfd; i < nfds; i++)
{
//依次判断置1位是否准备好
if(FD_ISSET(i, &readfd))//判断关注的文件是否准备好
{
if(i == sockfd)//如果有sockfd准备好,用accept连接
{
int connfd = accept(i, (struct sockaddr*)&caddr, &len);
//accept返回的是客户端的sockfd == connfd
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
FD_SET(connfd, &tempfd);//将关注的connfd在位图中置1//readfd需要遍历清0操作
nfds = connfd > (nfds-1)? connfd+1:nfds;//判断客户端sockfd/connfd的位置
}
else if(i > sockfd)//客户发送消息
{
char buf[20];
int ret = recv(i, buf, sizeof(buf), 0);
printf("ret=%d\n", ret);
if(ret == 0)//客户端关闭,新连的客户端才能用它的文件描述符
{
FD_CLR(i, &tempfd);
close(i);
printf("client is left\n");
continue;
}
else if(ret == -1)
{
break;
}
buf[ret] = 0;
puts(buf);
}
}
}
}
return 0;
}
缺点:需要轮询,需要两次拷贝,长度有限
7.4.2 poll函数
1.步骤
解决select长度有限的问题(位图有限),使用数组存储文件描述符,达到长度可变。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
fds:指向一个结构体数组的首地址的指针(可以写数组名),每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件;
struct pollfd {
int fd; /* 文件描述符的值*/
short events; /* 关心文件描述符的什么事件 */
short revents; /* 实际发生的事件 */
};
POLLIN 读; POLLOUT 写;
nfds:关心文件描述符的个数(第一个参数数组元素个数)
timeout: 0非阻塞 -1阻塞 >0 超时检测(ms)
返回值:
成功返回不为0的文件描述符个数 失败-1 超时0
2. 示例
同时监听鼠标轨迹和终端输入
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
int main(int argc, char *argv[])
{
//fd打开的鼠标坐标文件
int fd = open("/dev/input/mice", O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
//创建结构体数组
struct pollfd fds[1024];
memset(fds,0,sizeof(fds));
int pos = -1;//定义数组下标
//关注鼠标轨迹
fds[++pos].fd = fd;
fds[pos].events = POLLIN;
fds[++pos].fd = 0;//标准输入流,文件描述符为0
fds[pos].events = POLLIN;
while(1)
{
//判断poll函数是否创建成功
if(-1 == poll(fds, pos+1, 0))
{
perror("poll");
return -1;
}
int i;//定义下标来遍历结构体数组
for(i = 0; i < pos+1; i++)
{
if(fds[i].revents & POLLIN)//如果返回的结果是pollin事件
{
if(fds[i].fd == 0)//终端输入
{
char buf[20];
int ret = read(fds[i].fd, buf, sizeof(buf));
buf[ret] = 0;
puts(buf);
}
else if(fds[i].fd == fd)//鼠标轨迹
{
char buf[20];
int ret = read(fds[i].fd, buf, sizeof(buf));
printf("%d %d %d \n", buf[0], buf[1], buf[2]);
}
}
}
}
return 0;
}
示例:多路复用
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
//fd_set readfd, tempfd;
struct pollfd fds[1042];
//FD_ZERO(&readfd);
int i;
for(i = 0; i < 1042; i++)
{
fds[i].fd = -1;
}
//FD_SET(sockfd, &readfd);
int pos = -1;
fds[++pos].fd = sockfd;
fds[pos].events = POLLIN;
while(1)
{
if(-1 == poll(fds, pos+1, 0))
{
perror("poll");
return -1;
}
int i;
for(i = 0; i < pos+1; i++)
{
if(/*FD_ISSET(i, &readfd)*/fds[i].revents & POLLIN)
{
if(fds[i].fd == sockfd)
{
int connfd = accept(fds[i].fd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
//FD_SET(connfd, &tempfd);
//nfds = connfd > (nfds-1)? connfd+1:nfds;
int j;
for(j = 0; j < pos+1; j++)
{
if(fds[j].fd == -1)
{
fds[j].fd = connfd;
fds[j].events = POLLIN;
break;
}
}
if(j == pos+1)
{
fds[++pos].fd = connfd;
fds[pos].events = POLLIN;
}
}
else if(fds[i].fd > sockfd)
{
char buf[20];
int ret = recv(fds[i].fd, buf, sizeof(buf), 0);
printf("ret=%d\n", ret);
if(ret == 0)
{
// FD_CLR(i, &tempfd);
fds[i].fd = -1;
close(fds[i].fd);
printf("client is left\n");
continue;
}
else if(ret == -1)
{
break;
}
buf[ret] = 0;
puts(buf);
}
}
}
}
return 0;
}
7.4.3 epoll函数
1.步骤
linux中并没有一个epoll函数,而是提供了三个以epoll开头的函数:
- epoll_create
- epoll_ctl
- epoll_wait
不需要轮询,用红黑树进行查找,只拷贝一次,内核->用户端
需要告诉内核关心的文件描述符
1.创建
int epoll_create(int size);
创建一个epoll句柄(红黑树)
参数:可处理的文件的范围,必须设置一个大于0的值。(1024)
返回值:成功返回文件描述符,失败-1
*************************************************************************************
2.插入监听
int epoll_ctl(int epfd, int op, int fd,
struct epoll_event *event);
参数:
epfd: epoll的文件描述符
op: 操作的命令
//操作epoll
EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除
//最后一个参数可以为NULL
fd: 操作的文件描述符
epoll_event: 操作的具体内容
struct epoll_event {
uint32_t events; //操作的具体内容EPOLLIN/EPOLLOUT/EPOLLRDHUP
epoll_data_t data; //操作的文件描述符
};
typedef union epoll_data {
void *ptr;
int fd;//一般只需要定义联合体中fd的值
uint32_t u32;
uint64_t u64;
} epoll_data_t;
//eg:
event.events = EPOLLIN;//监听读操作
event.data.fd = fd;
返回值:成功返回0;失败返回-1;
*************************************************************************************
3.给内核处理
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
等待内核返回准备好的文件描述符
参数:
epoll 文件描述符
events 把准备好的文件描述符返回给用户
maxevents 数组大小
timeout 0 非阻塞 -1 阻塞 >0 超时检测ms
返回值:成功返回准备好的个数,失败-1,超时0
*************************************************************************************
4.具体操作
revents[i].events & EPOLLIN//判断
2. 示例
同时监听鼠标轨迹和终端输入
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <sys/epoll.h>
int main(int argc, char *argv[])
{
int fd = open("/dev/input/mice", O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
//1.创建
//struct pollfd fds[1024];
//memset(fds,0,sizeof(fds));
int efd = epoll_create(1024);
if(-1 == efd)
{
perror("create");
return -1;
}
/*
int pos = -1;
fds[++pos].fd = fd;
fds[pos].events = POLLIN;
fds[++pos].fd = 0;
fds[pos].events = POLLIN;
*/
//2.插入监听
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = fd;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD,fd,&event))
{
perror("add");
return -1;
}
event.events = EPOLLIN;
event.data.fd=0;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD,0,&event))
{
perror("add");
return -1;
}
//创建第三步需要的结构体
struct epoll_event revents[100];
int count;
while(1)
{
//3.给内核处理
if(-1 == (count=epoll_wait(efd, revents,100, 0)))
{//内核自动把准备好的文件描述符拷贝到结构体
//count == 准备好的文件描述符个数
perror("poll");
return -1;
}
int i;
for(i = 0; i < count; i++)
{//如果内核返回的是EPOLLIN事件
if(revents[i].events & EPOLLIN)
{//判断是监听的哪个IO
if(revents[i].data.fd == 0)
{
char buf[20];
int ret = read(revents[i].data.fd, buf, sizeof(buf));
buf[ret] = 0;
puts(buf);
}
else if(revents[i].data.fd == fd)
{
char buf[20];
int ret = read(revents[i].data.fd, buf, sizeof(buf));
printf("%d %d %d \n", buf[0], buf[1], buf[2]);
}
}
}
}
return 0;
}
示例:同时监听客户端的连接和发送
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
int main(int argc, char *argv[])
{
int connfd = -1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
int efd = epoll_create(1024);
if(-1 == efd)
{
perror("epoll_create");
return -1;
}
struct epoll_event fds;
fds.data.fd = sockfd;
fds.events = EPOLLIN;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &fds))
{
perror("epoll_ctl");
return -1;
}
struct epoll_event revents[100];
int pos;
while(1)
{
if(-1 == (pos=epoll_wait(efd, revents, 100, 0)))
{
perror("epoll");
return -1;
}
int i;
for(i = 0; i < pos; i++)
{
if(revents[i].events & EPOLLIN)
{
if(revents[i].data.fd == sockfd)
{
connfd = accept(revents[i].data.fd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
fds.data.fd = connfd;
fds.events = EPOLLIN;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &fds))
{
perror("epoll_ctl");
return -1;
}
}
else if(revents[i].data.fd > sockfd)
{
char buf[20];
int ret = recv(revents[i].data.fd, buf, sizeof(buf), 0);
printf("ret=%d\n", ret);
if(ret == 0)
{
if(-1 == epoll_ctl(efd, EPOLL_CTL_DEL, revents[i].data.fd, NULL))
{
perror("epoll_ctl");
return -1;
}
close(revents[i].data.fd);
printf("client is left\n");
continue;
}
else if(ret == -1)
{
break;
}
buf[ret] = 0;
puts(buf);
}
}
}
}
return 0;
}
7.5 异步IO
在系统注册回调,控制权在内核,如果有文件描述符准备好,控制权返回用户,由用户进行操作;
7.6 超时检测
超时检测的必要性:避免进程在没有数据时无限制地阻塞,当设定的时间到时,进程从原操作返回继续运行。
1. io多路复用(三种都可以设置)
超时检测一般放在客户端(写事件)
//超时多少秒后退出
if(-1 == (count=epoll_wait(efd, revents, 100, 5000)))
{
perror("epoll_wait");
return -1;
}
else if(0 == count)
{
printf("timeout\n");
exit(-1)
}
示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
int main(int argc, char *argv[])
{
int connfd = -1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
int efd = epoll_create(1024);
if(-1 == efd)
{
perror("epoll_create");
return -1;
}
struct epoll_event fds;
fds.data.fd = sockfd;
fds.events = EPOLLIN;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &fds))
{
perror("epoll_ctl");
return -1;
}
struct epoll_event revents[100];
int pos;
while(1)
{
if(-1 == (pos=epoll_wait(efd, revents, 100, 2000)))
{
perror("epoll");
return -1;
}
else if(0 == pos)
{
printf("timeout\n");
exit(-1);
}
int i;
for(i = 0; i < pos; i++)
{
if(revents[i].events & EPOLLIN)
{
if(revents[i].data.fd == sockfd)
{
connfd = accept(revents[i].data.fd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
fds.data.fd = connfd;
fds.events = EPOLLIN;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &fds))
{
perror("epoll_ctl");
return -1;
}
}
else if(revents[i].data.fd > sockfd)
{
char buf[20];
int ret = recv(revents[i].data.fd, buf, sizeof(buf), 0);
printf("ret=%d\n", ret);
if(ret == 0)
{
if(-1 == epoll_ctl(efd, EPOLL_CTL_DEL, revents[i].data.fd, NULL))
{
perror("epoll_ctl");
return -1;
}
close(revents[i].data.fd);
printf("client is left\n");
continue;
}
else if(ret == -1)
{
break;
}
buf[ret] = 0;
puts(buf);
}
}
}
}
return 0;
}
2.信号和闹钟设置
kill -l 查看 SIGALARM的号码
#include <signal.h>
void func(int arg)
{
printf("timeout\n");
}
signal(14, func);
alarm(4);
2.2 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
void func(int arg)
{
printf("timeout\n");
}
int main(int argc, char *argv[])
{
int connfd = -1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
int efd = epoll_create(1024);
if(-1 == efd)
{
perror("epoll_create");
return -1;
}
struct epoll_event fds;
fds.data.fd = sockfd;
fds.events = EPOLLIN;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &fds))
{
perror("epoll_ctl");
return -1;
}
struct epoll_event revents[100];
int pos;
signal(14, func);
while(1)
{
alarm(4);
if(-1 == (pos=epoll_wait(efd, revents, 100, -1)))
{
perror("epoll");
return -1;
}
else if(0 == pos)
{
printf("timeout\n");
exit(-1);
}
int i;
for(i = 0; i < pos; i++)
{
if(revents[i].events & EPOLLIN)
{
if(revents[i].data.fd == sockfd)
{
connfd = accept(revents[i].data.fd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
fds.data.fd = connfd;
fds.events = EPOLLIN;
if(-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &fds))
{
perror("epoll_ctl");
return -1;
}
}
else if(revents[i].data.fd > sockfd)
{
char buf[20];
int ret = recv(revents[i].data.fd, buf, sizeof(buf), 0);
printf("ret=%d\n", ret);
if(ret == 0)
{
if(-1 == epoll_ctl(efd, EPOLL_CTL_DEL, revents[i].data.fd, NULL))
{
perror("epoll_ctl");
return -1;
}
close(revents[i].data.fd);
printf("client is left\n");
continue;
}
else if(ret == -1)
{
break;
}
buf[ret] = 0;
puts(buf);
}
}
}
}
return 0;
}
3.设置套接字属性
setsockopt:设置套接字属性; getsockopt:获取套接字属性
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
level:设置那一层协议
optname:具体指令
optval:根据不同指令传递的值
optlen:结构体大小
设置属性表:level
SOL_SOCKET
---------------------------------------------------
参数optname 宏的作用 对应参数optval的类型
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 循序调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获的套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDWAIT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSEADDR 允许重用本机地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
===================================================
IPPROTO_IP
------------------------------------------------------
IP_ADD_MEMBERSHIP 加入到组播组中 struct ip_mreq
IP_MULTICAST_IF 允许开启组播报文的接口 struct ip_mreq
示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int connfd = -1;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
if(listen(sockfd, 4))
{
perror("listen");
return -1;
}
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
struct timeval timeout = {4,0};
if(-1 == setsockopt(sockfd, SOL_SOCKET,SO_RCVTIMEO,&timeout, sizeof(timeout)))
{
perror("setsockopt");
return -1;
}
while(1)
{
connfd = accept(sockfd, (struct sockaddr*)&caddr, &len);
if(-1 == connfd)
{
if(errno == 11)
{
printf("timeout\n");
}
else
{
perror("accept");
return -1;
}
}
printf("ip = %s port=%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
}
return 0;
}
8. 套接字分类
8.1 流式套接字 tcp
8.2 报式套接字 udp
前两个可以进行不同组机的通信
8.3 unix域(原始)套接字
//只能进行本机通信 AF_UNIX man 7 unix;作用:传递文件描述符,传递凭证
1.socket
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); //流式Unix域套接字
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); //数据报式套接字
//最后一个参数,表示协议,对于Unix域套接字来说,其一定是被设置成0
2.创建unix专属结构体
struct sockaddr_un {
sa_family_t sun_family; //必须要设置成“AF_UNIX”
char sun_path[108]; //路径名
};
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "./unix");//或者直接写当前路径文件名,数组不能直接赋值,unix文件不能存在。
//需要选择tcp或udp
//bind绑定文件名,别的都和tcp/udp一样
字节对齐?
1. 服务器
1.1 步骤
1.socket;
2.创建unix专属结构体;
3.bind;
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
4.listen;
int listen(int sockfd, int backlog);
5.accept;
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
6.while(1){
read()
}
1.2 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/un.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//1.
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "./unix");
//3.
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
//4.
if(-1 == listen(sockfd, 4))
{
perror("listen");
return -1;
}
printf("wait for a client\n");
//5.
int connfd = accept(sockfd, NULL, NULL);
if(-1 == connfd)
{
perror("accept");
return -1;
}
//6.
while(1)
{
char buf[20];
int ret = read(connfd, buf, sizeof(buf));
buf[ret] = 0;
puts(buf);
}
return 0;
}
2.客户端
2.1 步骤
1.socket;
2.创建unix专属结构体;//此结构体和服务器中结构体相同
3.int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
4.while(1){
read()
}
2.2 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/un.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//1.
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "./unix");
//3.
int ret = connect(sockfd, (struct sockaddr*)&addr,sizeof(addr));
//强转类型必须为(struct sockaddr*)而非
if(-1 == ret)
{
perror("accept");
return -1;
}
printf("connet a ser\n");
//4.
while(1)
{
char buf[20];
fgets(buf,sizeof(buf), stdin);
int ret = write(sockfd, buf, sizeof(buf));
}
return 0;
}
9. IP地址的分类
A:一个字节为网络号,三个字节为主机号 多用于学校,国企
以0开始:0.0.0.0 — 127.255.255.255 0.0.0.0 本机地址 127.0.0.1回环 以.255结尾为广播特定地址
B:两个字节为网络号,两个字节为主机号
私有地址;以10开始:128.0.0.0 — 191.255.255.255
C:三个字节为网络号,一个字节为主机号
私有地址;以110开始:192.0.0.0 — 223.255.255.255
D:组播地址
以1110开始:224.0.0.0 — 239.255.255.255
E:保留地址
广播和组播用udp(报式套接字)实现
9.1 广播
客户端(发送端)的Bind(绑定)都可以省略
发送方要设置套接字允许发送广播信息;接收方不需要设置;
9.1.1 发送端
1.步骤
1.socket;
2.设置允许发送广播选项参数
int opval = 1;//1表示同意发送
setsockopt(sockfd, SOL_SOCKET,SO_BROADCAST, &opval, sizeof(opval));
3.创建结构体存储地址信息
struct sockaddr_in addr;
//sendto的目标结构体
//向广播地址发送消息---规定广播ip地址以255结尾
4.while(1)
{
sendto();
}
2.示例
//1.socket;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
//2.设置参数
int opval = 1;
setsockopt(sockfd, SOL_SOCKET,SO_BROADCAST, &opval, sizeof(opval))
//3.创建结构体存储地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("192.168.7.255");
//4.
while(1){
char buf[20];
fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
}
9.1.2 接收端
1.步骤
1.socket;
2.创建结构体存储地址信息
struct sockaddr_in addr;
//bind要绑定的结构体
bind
//绑定的地址需要是一个广播地址或者是0地址(0表示本机地址)
3.while(1)
{
recvfrom();
}
2.示例
//1.socket;
int sockfd = socket(AF_INET, SOCK_GRAM, 0);
//2.创建结构体存储地址信息
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = inet_addr("0");//inet_addr("192.168.7.27");
if(-1 == bind(sockfd,(struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
//3.
while(1)
{
char buf[20];
int ret = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
buf[ret] = 0;
puts(buf);
}
9.2 组播
客户端(发送端)的Bind(绑定)都可以省略
需要加入组播地址才能收到发给组播地址的信息
9.2.1 创建组播(发送端)
1.步骤
1.sockfd;
2.创建sendto的目标结构体;
struct sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr("239.2.2.2");
//地址:给哪个组播组发消息
3.创建setsockopt绑定的结构体
struct ip_mreq m;
{
m.imr_multiaddr.s_addr = inet_addr("239.2.2.2");//创建的组播ip
m.imr_interface.s_addr = inet_addr("192.168.7.29本机地址");//将本机地址加入到组播组中
}
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &m, sizeof(m)));
4.while(1)
{
sendto((struct sockaddr*)&addr);
}
2.示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//1.
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
addr.sin_addr.s_addr = inet_addr("239.9.2.2");
//3.
struct ip_mreq m;
m.imr_multiaddr.s_addr = inet_addr("239.9.2.2");
m.imr_interface.s_addr = inet_addr("192.168.7.29");
if(-1 == setsockopt(sockfd, IPPROTO_IP,IP_MULTICAST_IF, &m, sizeof(m)))
{
perror("setopt");
return -1;
}
//4.
while(1)
{
char buf[20];
fgets(buf,sizeof(buf), stdin);
int ret = sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
}
return 0;
}
9.2.2 加入组播(接收端)
加入组播地址才能收到发给组播地址的信息
1.步骤
1.socket;
2.绑定;
struct sockaddr_in addr;
//bind要绑定的结构体(协议、端口、本机地址信息)
bind((struct sockaddr*)&addr);
//将socket与本机地址绑定
3.加入组播地址;
//创建结构体存储地址信息
struct ip_mreq m;
{
m.imr_multiaddr.s_addr = inet_addr("239.2.2.2");//要加入的组播地址
m.imr_interface.s_addr = inet_addr("192.168.7.本机地址");//将本机地址加入到组播组中才能从组播组中收到消息
}
//用setsockopt将socket绑定的本机地址加入到组播
setsockopt(sockfd, IPPROTO_IP,IP_ADD_MEMBERSHIP, &m, sizeof(m)));
4.while(1)
{
recvfrom(NULL);
}
2.示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//1.socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6666);
addr.sin_addr.s_addr = inet_addr("0");
if(-1 == bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)))
{
perror("bind");
return -1;
}
//3.加入组播地址;
struct ip_mreq m;
m.imr_multiaddr.s_addr = inet_addr("239.9.2.2");
m.imr_interface.s_addr = inet_addr("192.168.7.29");
if(-1 == setsockopt(sockfd, IPPROTO_IP,IP_ADD_MEMBERSHIP, &m, sizeof(m)))
{
perror("setopt");
return -1;
}
//4.
while(1)
{
char buf[20];
int ret = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
buf[ret] = 0;
puts(buf);
}
return 0;
}
10. 数据库
sqlite3
1. 打开数据库文件(终端调用sqlite函数)
sqlite3+打开或新建的文件名.db
//输入命令:.命令
命令后面无分号
查看表信息 : .show
查看表名 : .table
退出 : .quit
//输入sql语句;
//创建表:
create table 表名(表的元素);
create table student("name" char, age int, "number" char);
//插入表:
insert into 表名 values(对应创建表中类型);
insert into student values("xiaoming",18,"110");
//删除表中数据:
delete from student where name="xiaoming";
根据条件删除:
delete from student where age<18;
删除多条件: and逻辑与;or逻辑或
delete from student where age<18 or name="xiaoming";
//更改表中数据
update student set name="xiaohong" where name="xiaoming";
//查询表
select *from student;
加条件查询
select *from student where age=18;
select *from student where age=18 and name="xiaoming";
2. 操作数据库文件(写代码)
#include <sqlite3.h>
1.打开数据库
int sqlite_open(char* path, sqlite3**db);
参数:
path:数据库文件名
db:数据库的句柄
返回值:成功0;失败返回非0
2.操作函数库
int sqlite3_exec(sqlite3* db, char* sql, int(*caback)(void*, int, char**, char**), void* arg,char** errmsg);
参数:
db:数据库的句柄
sql:10.1中的sql语句都可以进行的操作
caback:函数指针(回调函数)//只有查询时才需要用;否则填null
{
arg:通过exec传递的第四个参数
n:查到几条信息
key:查到的关键字的值
val:create设置的关键字
}
arg:回调函数的参数//caback的第一个参数
//两个arg相同
err:错误信息
返回值:成功0;失败返回非0
编译时要链接库函数 gcc my.db -lsqlite3
2.1 无查询时
#include <stdio.h>
#include <sqlite3.h>
int main(int argc, char *argv[])
{
sqlite3 *db;
if(0 != sqlite3_open("my.db", &db))
{
printf("sqlite3 is fault\n");
return -1;
}
char name[20];
int age;
char number[20];
char *sql1 = "create table student(\"name\" char, age int, \"number\" char);";
char *err = NULL;//err参数可与后面exec函数共享
if( 0!= sqlite3_exec(db, sql1, NULL, NULL,&err))
{
puts(err);
return -1;
}
#if 0
char sql[1024];
sprintf(sql,"insert into student values(\"%s\", %d, \"%s\");", name,age,number);
#endif
char *sql = "insert into student values(\"xiaolv\", 19, \"119\");";
if( 0!= sqlite3_exec(db, sql, NULL, NULL,&err))
{
puts(err);
return -1;
}
return 0;
}
//数据已插入
hqyj@ubuntu:~/net/day6$ sqlite3 my.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> select *from student;
xiaolv|19|119
sqlite>
2.2 有查询时
//查询到一条数据就会进入一次caback,无数据就不会进入
#include <stdio.h>
#include <sqlite3.h>
int func(void* arg, int n, char **val, char **key)
{
printf("n=%d\n", n);
int i,j;
for(i = 0; i < n; i++)
{
printf("key=%s val=%s \n", key[i], val[i]);
}
return 0;
}
int main(int argc, char *argv[])
{
sqlite3 *db;
if(0 != sqlite3_open("my.db", &db))
{
printf("sqlite3 is fault\n");
return -1;
}
char name[20];
int age;
char number[20];
char *sql2 = "create table student(\"name\" char, age int, \"number\" char);";
char *err = NULL;
if( 0!= sqlite3_exec(db, sql2, NULL, NULL,&err))
{
puts(err);
return -1;
}
#if 0
char sql[1024];
sprintf(sql,"insert into student values(\"%s\", %d, \"%s\");", name,age,number);
#endif
char *sql = "insert into student values(\"xiaolv\", 19, \"119\");";
if( 0!= sqlite3_exec(db, sql, NULL, NULL,&err))
{
puts(err);
return -1;
}
char *sql1 = "select *from student where age=19;";
if(0 != sqlite3_exec(db, sql1, func, NULL, &err))
{
puts(err);
return -1;
}
return 0;
}
件名
db:数据库的句柄
返回值:成功0;失败返回非0
2.操作函数库
int sqlite3_exec(sqlite3* db, char* sql, int(caback)(void, int, char**, char**), void* arg,char** errmsg);
参数:
db:数据库的句柄
sql:10.1中的sql语句都可以进行的操作
caback:函数指针(回调函数)//只有查询时才需要用;否则填null
{
arg:通过exec传递的第四个参数
n:查到几条信息
key:查到的关键字的值
val:create设置的关键字
}
arg:回调函数的参数//caback的第一个参数
//两个arg相同
err:错误信息
返回值:成功0;失败返回非0
==编译时要链接库函数 gcc my.db -lsqlite3==
###### 2.1 无查询时
```c
#include <stdio.h>
#include <sqlite3.h>
int main(int argc, char *argv[])
{
sqlite3 *db;
if(0 != sqlite3_open("my.db", &db))
{
printf("sqlite3 is fault\n");
return -1;
}
char name[20];
int age;
char number[20];
char *sql1 = "create table student(\"name\" char, age int, \"number\" char);";
char *err = NULL;//err参数可与后面exec函数共享
if( 0!= sqlite3_exec(db, sql1, NULL, NULL,&err))
{
puts(err);
return -1;
}
#if 0
char sql[1024];
sprintf(sql,"insert into student values(\"%s\", %d, \"%s\");", name,age,number);
#endif
char *sql = "insert into student values(\"xiaolv\", 19, \"119\");";
if( 0!= sqlite3_exec(db, sql, NULL, NULL,&err))
{
puts(err);
return -1;
}
return 0;
}
//数据已插入
hqyj@ubuntu:~/net/day6$ sqlite3 my.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> select *from student;
xiaolv|19|119
sqlite>
2.2 有查询时
//查询到一条数据就会进入一次caback,无数据就不会进入
#include <stdio.h>
#include <sqlite3.h>
int func(void* arg, int n, char **val, char **key)
{
printf("n=%d\n", n);
int i,j;
for(i = 0; i < n; i++)
{
printf("key=%s val=%s \n", key[i], val[i]);
}
return 0;
}
int main(int argc, char *argv[])
{
sqlite3 *db;
if(0 != sqlite3_open("my.db", &db))
{
printf("sqlite3 is fault\n");
return -1;
}
char name[20];
int age;
char number[20];
char *sql2 = "create table student(\"name\" char, age int, \"number\" char);";
char *err = NULL;
if( 0!= sqlite3_exec(db, sql2, NULL, NULL,&err))
{
puts(err);
return -1;
}
#if 0
char sql[1024];
sprintf(sql,"insert into student values(\"%s\", %d, \"%s\");", name,age,number);
#endif
char *sql = "insert into student values(\"xiaolv\", 19, \"119\");";
if( 0!= sqlite3_exec(db, sql, NULL, NULL,&err))
{
puts(err);
return -1;
}
char *sql1 = "select *from student where age=19;";
if(0 != sqlite3_exec(db, sql1, func, NULL, &err))
{
puts(err);
return -1;
}
return 0;
}