网络编程华清学习笔记

华清远见学习笔记

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.绑定套接字信息
		while1{
			交互
		}
		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.绑定套接字信息(可省略)
		while1{
				交互
		}
		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
    
返回值	
	成功返回准备好的文件描述符个数,失败返回-10代表超时;

  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;
} 

版权声明:本文为weixin_55953651原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。