C语言之网络编程(必背知识点)

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

     一、认识网络

    1、网络发展史

    网络的来历_百度知道

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

    2、局域网和广域网

    局域网LAN

    局域网的缩写是LANlocal area network顾名思义是个本地的网络只能实现小范围短距离的网络通信。我们的家庭网络是典型的局域网。电脑、手机、电视、智能音箱、智能插座都连在路由器上可以互相通信。局域网就像是小区里的道路分支多连接了很多栋楼。

    广域网Wan

    广域网Wide Area Network是相对局域网来讲的局域网的传输距离比较近只能是一个小范围的。如果需要长距离的传输比如某大型企业总部在北京分公司在长沙局域网是无法架设的。广域网就像是大马路分支可能少但类型多像国道、省道、高速、小道等连接了很多大的局域网。

    这时需要其它的解决方案。

    第一通过因特网只需要办一根宽带就实现了通信非常方便现在的宽带价格也比较便宜。

    第二通过广域网专线。

    所以为了数据安全不能连接因特网需要用一条自己的专用线路来传输数据这条线路上只有自己人不会有其他人接入且距离很远这个网络就叫 “广域网”。

    3、光猫

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

    将光线插入左侧的灰色口右侧网口接网线到路由器即可。

    4、交换机与路由器

    交换机二层用于局域网内网的数据转发路由器三层用于连接局域网和外网

    路由器有交换机的功能反之不成立交换机没有IP分配和IP寻址的功能。

    交换机各个口是平等的所有接入的设备需要自己配置IP然后组成局域网。

    路由器需要区分WAN口和LAN口WAN口是接外网的从Modem出来的或者从上一级路由器出来的LAN口是接内网的现在路由器都带无线功能本质上无线接入就是LAN。

    5、网线

    背过一种线序了解网线的制作流程。

    网线线序

    网线制作教程

    6、IP地址

    6.1 基本概念

    • IP地址是Internet中主机的标识
    • Internet中的主机要与别的机器通信必须具有一个IP地址
    • IP地址为32位IPv4或者128位IPv6
    • 表示形式常用点分形式如202.38.64.10最后都会转换为一个32位的无符号整数。

    6.2 网络号/主机号

    6.2.1 地址划分

    主机号的第一个和最后一个都不能被使用第一个作为网段号最后一个最为广播地址。

    A类1.0.0.1~126.255.255.254
    B类128.0.0.1~~191.255.255.254
    C类192.0.0.1~~223.255.255.254
    D类组播地址224.0.0.1~~239.255.255.254
    

    6.2.2 特殊地址

    0.0.0.0在服务器中0.0.0.0指的是本机上的所有IPV4地址如果一个主机有两个IP地址192.168.1.1 和 10.1.2.1并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。

    127.0.0.1回环地址/环路地址所有发往该类地址的数据包都应该被loop back。

    6.3 子网掩码

    IP地址=网络号+主机号使用子网掩码来进行区分

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

    主机号标识在本网段内的ID同一局域网不能重复

    • 子网掩码是一个32位的整数作用是将某一个IP划分成网络地址和主机地址
    • 子网掩码长度是和IP地址长度完全一样
    • 网络号全为1主机号全为0
    • 公式网络号=IP & MASK

    思考一上图中B类地址的子网掩码怎么写

    思考二B类地址同一网段最多可以连接多少个主机

    思考三已知一个子网掩码号为255.255.255.192问最多可以连接多少台主机

    7、网络模型

    7.1 网络的体系结构

    1. 网络采用分而治之的方法设计将网络的功能划分为不同的模块以分层的形式有机组合在一起。
    2. 每层实现不同的功能其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务同时使用下层提供的服务
    3. 网络体系结构即指网络的层次结构和每层所使用协议的集合
    4. 两类非常重要的体系结构OSI与TCP/IP

    7.2 OSI模型

    1. OSI模型是一个理想化的模型尚未有完整的实现
    2. OSI模型共有七层
    3. OSI现阶段只用作教学和理论研究

    7.3 TCP/IP模型

    网络接口和物理层屏蔽硬件差异驱动向上层提供统一的操作接口。

    网络层提供端对端的传输可以理解为通过IP寻址机器。

    传输决定数据交给机器的哪个任务进程去处理通过端口寻址

    应用层应用协议和应用程序的集合

    OSI和TCP/IP模型对应关系图

    7.4 常见网络协议

    网络接口和物理层:
    	ppp拨号协议老式电话线上网方式
    	ARP地址解析协议  IP-->MAC
    	RARP:反向地址转换协议 MAC-->IP
    网络层:	
    	IP(IPV4/IPV6):网间互连的协议
    	ICMP网络控制管理协议ping命令使用
    	IGMP网络分组管理协议广播和组播使用
    传输层
    	TCP传输控制协议
    	UDP用户数据报协议
    应用层
    	SSH:加密协议
    	telnet远程登录协议
    	FTP文件传输协议
    	HTTP超文本传输协议
    	DNS地址解析协议
    	SMTP/POP3邮件传输协议

    注意TCP和IP是属于不同协议栈层的只是这两个协议属于协议族里最重要的协议所以协议栈或者模型以之命名了。

    8. TCP/UDP

    TCP

    TCP即传输控制协议是一种面向连接的传输层协议它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。

    适用场景

    适合于对传输质量要求较高的通信

    在需要可靠数据传输的场合通常使用TCP协议

    MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议

    UDP

    UDPUser Datagram Protocol用户数据报协议是不可靠的无连接的协议。在数据发送前因为不需要进行连接所以可以进行高效率的数据传输。

    适用场景

    发送小尺寸数据如对DNS服务器进行IP地址查询时

    适合于广播/组播式通信中。

    MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议

    9. 编程预备知识

    9.1 socket定义

    9.2 socket类型

    流式套接字(SOCK_STREAM)   TCP

    提供了一个面向连接、可靠的数据传输服务数据无差错、无重复的发送且按发送顺序接收。内设置流量控制避免数据流淹没慢的接收方。数据被看作是字节流无长度限制。

    数据报套接字(SOCK_DGRAM)  UDP

    提供无连接服务。数据包以独立数据包的形式被发送不提供无差错保证数据可能丢失或重复顺序发送可能乱序接收。

    原始套接字(SOCK_RAW)

    可以对较低层次协议如IP、ICMP直接访问。

    9.4 端口号

    • 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理使用端口号来区
    • TCP端口号与UDP端口号独立
    • 端口号一般由IANA (Internet Assigned Numbers Authority) 管理
    • 端口用两个字节来表示
    众所周知端口1~10231~255之间为众所周知端口256~1023端口通常由UNIX系统占用
    已登记端口1024~49151
    动态或私有端口49152~65535
    
    

    9.5 字节序

       小端序little-endian - 低序字节存储在低地址

       大端序big-endian- 高序字节存储在低地址

       网络中传输的数据必须使用网络字节序即大端字节序

    面试题写一个函数判断当前主机的字节序

    int checkCPU()
    {
    	union w{
    		int a;
    		char b;
    	}c;
    	c.a = 1;
    	return (c.b == 1);
    }

    主机字节序到网络字节序

    u_long htonl (u_long hostlong);
    u_short htons (u_short short);  //掌握这个
    
    

    网络字节序到主机字节序

    u_long ntohl (u_long hostlong);
    u_short ntohs (u_short short);
    

    9.6 IP地址转换

    typedef uint32_t in_addr_t;
    
    struct in_addr {
        in_addr_t s_addr;
    };
    
    in_addr_t inet_addr(const char *cp);  //从人看的ip地址转为机器使用的32位无符号整数
    char *inet_ntoa(struct in_addr in);  //从机器到人
    
    

    示例

    int main()
    {
    	in_addr_t  addr;
    	addr = inet_addr("192.168.1.222");
    	printf("addr=0x%x\n", addr);
    
    	struct in_addr tmp;
    	tmp.s_addr = addr;
    	printf("ip=%s\n", inet_ntoa(tmp));
    }
    

    10. 复习

    历史

       阿帕网不能互联不同的主机、不同操作系统没有纠错功能 

       TCP/IP

          IP  

      TCP  

       IP  A首位固定为0。1byte网络号3byte主机号

              0000 0000 - 0111 1111 >0-127

                 123.0000...000 - 1111...111

                 255.0.0.0  

             B:首位固定为10。2byte网络号2byte主机号

       128.0 - 191.255.

       172.125

       255.255.0.0

     C:首位固定为110。3byte网络号1byte主机号

       192.0.0-223.255.255

       192.168.1

       0-255 =》 254

       255.255.255.0

       子网掩码网络全为1主机号全为0.

       

       22位网络号  10位主机号

       10 00...00 10 11...11 

               子网掩码255.255.111111 00 .0000 0000

                  255.255.252.0    

         

         ip=网络号+主机号

     网络号是否处于同一网段

     主机号: 唯一分配给主机的id

           D(组播)E    

       

      port端口。标识进程  udp 和 TCP端口独立

        1-1023 

    >1023

        socket -  TCP/TP

        IO-C b 

    网络设备 — socket - > fd

    TCP流程

      服务器

    1.创建流式套接字socket .返回连接文件描述符  

    2.绑定(填充通信结构体)bind

    3.监听。主动套接字变为被动套接字listen

    4.阻塞等待客户端连接accept .返回通信文件描述符

    5.收发消息

    6.关闭套接字

       客户端

      1.创建流式套接字socket 

      2.填充服务器的通信结构体

      3.请求连接connect

      4.收发消息

      5.关闭套接字

    【1】
        基础理论ip  port  socket   套接字类型  OSI  TCP/IP udp TCP
        核心编程框架TCP UDP
        UDP可以直接实现并发服务器
        
        TCP-循环服务器
        
        *TCP实现并发服务器。
          引入linux IO模型4种
             1.阻塞IO:
         特点-最常用、不能处理多路IO效率低不需要轮询不浪费资源
             2.非阻塞特点-不常用、能处理多路IO需要轮询耗费CPU
             3.信号驱动IO异步IO需要底层驱动支持
             4.IO多路复用 - 能实现TCP并发
              select  poll  epoll   
              
    【2】UPD
      服务器
         创建套接字数据报套接字
         填充服务器的通信结构体
         绑定
         发收
           sendto
      客户端  
         创建套接字socket
         填充服务器的通信结构体
         发送
           sendto(sockfd,buf,size,0,(struct sockaddr*)&saddr,
           sizeof(saddr));
           len=sizeof(caddr);
           recvfrom(sockfd,buf,size,0,(struct sockaddr*)&caddr,&len);


        非阻塞
          函数自带参数设置
          fcntl(fd,功能选择,属性值(int))
                   
                   F_GETFL  F_SETFL F_SETOWN
                   
        IO多路复用select 
          编程流程
            1.创建表
              fd_set readfds,tempfds;
              FD_ZERO(&readfds);
            2.添加关心文件描述符到表中
               FD_SET(0,&readfds);
            // FD_SET(sockfd,&reafds);
               ...
            3.调用函数检测
               tempfds=readfds;
              select(maxfd+1,&tempfds,NULL,NULL,NULL);
            4.一个或多个文件描述符有事件产生返回
            5.判断是那个文件描述符产生事件
            if(FD_ISSET(0,&tempfds))
            6.处理事件
            {
               fgets(buf,sizeof(buf),stdin);
            }
            if(FD_ISSET(sockfd,&tempfds))
            {
               acceptfd=accept();
            }
            
        select(检测文件描述符个数,读、写、异常超时检测)
        FD_SET添加文件描述符到表中
        FD_ZERO:清空表
        FD_ISSET:判断对应文件描述符是否在表中
        FD_CLR:从表中清除指定文件描述符
             
      
        poll(表-结构体数组,数组有效元素的个数-检测文件描述符个数,-1->阻塞);
        结构体
           fd 
           events:检测事件-POLLIN读 POLLOUT 
           revents:函数poll返回自动填充
             如果对应fd有对应事件产生将revents=events
            如果对应fd没有对应事件产生revents=0;
        
            
        epoll:
          int epfd=epoll_create(>0)
          epoll_ctl(epfd,功能选择,fd,event-事件结构体)
          功能选择EPOLL_CTL_ADD 添加
                    EPOLL_CTL_MOD 修改已经添加事件
                    EPOLL_CTL_DEL 删除
         event结构体
           data.fd  
           events: EPOLLIN|EPOLLET读          
                   EPOLLOUT|EPOLLET 写  
        
         epoll_wait(epfd,事件存放的位置-事件结构体,数组元素个数,-1->阻塞)    
         
         多进程和多线程实现并发服务器思想
           每有一个客户端连接创建一个子进程或线程和这个
          客户端通信父进程或主线程阻塞等待下一个客户端
          连接。
         
         fork创建进程的特点
           1.fork创建的子进程几乎拷贝了父进程所有的内容
              三个段正文、堆栈、数据段
           2.fork之后父进程中返回子进程的PID子进程中
             返回0.
           3.父进程先退出子进程孤儿进程子进程先退出
           父进程没有回收资源子进程僵尸进程。
           4.fork之前的代码被复制不会重新执行fork之后 
             的代码会被复制并执行。
           5.fork之前打开的文件fork之后拿到的是同一个文件
           描述符操作同一个文件指针。
           6.fork创建进程之后两个进程就相互独立。
           7.子进程状态发生改变会给父进程发送一个SIGCHLD信号

    二、TCP编程

    1.流程

    服务器
      socket创建一个用与链接的套接字
      bind绑定自己的ip地址和端口
      listen监听将主动套接字转为被动套接字
      accept阻塞等待客户端链接链接成功返回一个用于通信套接字
      recv接收消息
      send发送消息
      close关闭文件描述符
    客户端
       socket创建一个套接字
       填充结构体填充服务器的ip和端口
       connect阻塞等待链接服务器
       recv/send:接收/发送消息
       close关闭  
    

    2.函数接口

    1.socket 

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

    2.bind

    int bind(int sockfd, const struct sockaddr *addr,
             socklen_t addrlen);
    功能绑定   ipv4  ip和端口
    参数
       sockfd文件描述符
       addr通用结构体根据socket第一个参数选择的通信方式最终确定这需要真正填充传递的结构体是那个类型。强转后传参数。
       addrlen:填充的结构体的大小   
    返回值0 失败-1、更新errno
    
    通用结构体相当于预留一个空间
    struct sockaddr {
        sa_family_t sa_family;
        char        sa_data[14];
    }
    
    ipv4的结构体
     struct sockaddr_in {
         sa_family_t    sin_family;  //协议族AF_INET
         in_port_t      sin_port;  //端口
         struct in_addr sin_addr;   
     };
      struct in_addr {
         uint32_t       s_addr;   //IP地址  
     };
     
     本地址通信结构体
      struct sockaddr_un {
         sa_family_t sun_family;  //AF_UNIX  
         char        sun_path[108]; //在本地创建的套接字文件的路径及名字
     };
     
    ipv6通信结构体
    struct sockaddr_in6 {
        sa_family_t     sin6_family;   
        in_port_t       sin6_port;     
        uint32_t        sin6_flowinfo; 
        struct in6_addr sin6_addr;     
        uint32_t        sin6_scope_id; 
    };
    struct in6_addr {
        unsigned char   s6_addr[16];   
    };
    

    3.listen

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

    4.accept

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

    5.recv

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

    6.connect

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

    7.send

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    功能发送数据
    参数
        sockfd:socket函数的返回值
        buf发送内容存放的地址
        len发送内存的长度
        flags如果填0相当于write();
    

    3.代码实现

    优化代码

    1.去掉fgets获取的多余的'\n'.
       if(buf[strlen(buf)-1] == '\n')//去掉fgets获取的'\n'
             buf[strlen(buf)-1] ='\0';
    2.端口和ip地址通过命令行传参到代码中。
    3.设置客户端退出服务器结束循环接收。
        通过recv返回值为0判断客户端是否退出
    4.设置来电显示功能获取到请求链接服务器的客户端的ip和端口。
    5.设置服务器端自动获取自己的ip地址。
       INADDR_ANY  "0.0.0.0"
    6.实现循环服务器服务器不退出当链接服务器的客户端退出服务器等到下一个客户端链接。
    
    

    server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr,caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        //saddr.sin_addr.s_addr = inet_addr(argv[1]);
        // saddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY   0.0.0.0
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
            printf("client:ip=%s port=%d\n",inet_ntoa(caddr.sin_addr),\
            ntohs(caddr.sin_port));//inet_ntoa
            // 5.收发消息
            char buf[64];
            int recvbyte;
            while (1)
            {
                recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("client exit.\n");
                    break;
                }
                else
                {
                    printf("buf:%s\n", buf);
                }
            }
            // 6.关闭套接字
           close(acceptfd);
        }
        close(sockfd);
        return 0;
    }
    

    client.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
        if(argc != 3)
        {
            printf("please input %s <ip> <port>\n",argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4 服务器
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));//"8888"
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
        if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        char buf[64];
        while(1)
        {
            fgets(buf,sizeof(buf),stdin);//10  20
            //最多读size-1,自动补'\0',读到'\n'
            if(buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
            send(sockfd,buf,sizeof(buf),0);
            
        }
        // 6.关闭套接字
        close(sockfd);
        return 0;
    }
    

    4.tcp实现ftp功能

    模拟FTP核心原理客户端连接服务器后向服务器发送一个文件。文件名可以通过参数指定服务器端接收客户端传来的文件文件名随意如果文件不存在自动创建文件如果文件存在那么清空文件然后写入。

    项目功能介绍
       均有服务器和客户端代码基于TCP写的。
       在同一路径下将客户端可执行代码复制到其他的路径下接下来再不同的路径下运行服务器和客户端。
          相当于另外一台电脑在访问服务器。
    客户端和服务器链接成功后出现以下提示四个功能
    ***************list************** //列出服务器所在目录下的文件名(除目录不显示)
    ***********put filename********** //上传一个文件
    ***********get filename********** //重服务器所在路径下载文件
    **************quit*************** //退出(可只退出客户端服务器等待下一个客户端链接)
    

    server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <dirent.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <string.h>
     #include <sys/types.h>
     #include <sys/stat.h>
     #include <fcntl.h>
    
    void put_server(int acceptfd, char *buf, int size);
    void list_server(int acceptfd, char *buf, int size);
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        //saddr.sin_addr.s_addr = inet_addr(argv[1]);
        // saddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY   0.0.0.0
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
            printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                   ntohs(caddr.sin_port)); //inet_ntoa
            // 5.收发消息
            char buf[64];
            int recvbyte;
            while (1)
            {
                recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("client exit.\n");
                    break;
                }
                else
                {
                    printf("buf:%s\n", buf);
                    if (strncmp(buf, "list", 4) == 0)
                    {
                        list_server(acceptfd, buf, sizeof(buf));
                    }
                    else if (strncmp(buf, "put ", 4) == 0)
                    {
                        put_server(acceptfd, buf, sizeof(buf));
                    }
                }
            }
            // 6.关闭套接字
            close(acceptfd);
        }
        close(sockfd);
        return 0;
    }
    
    //1.list:打开当前目录读目录中的文件判断文件是否是普通文件
    //是普通文件发送文件名给客户端
    void list_server(int acceptfd, char *buf, int size)
    {
        struct dirent *file = NULL;
        struct stat st;
        DIR *dir = opendir("./");
        if (NULL == dir)
        {
            perror("opendir err.");
            return;
        }
        while ((file = readdir(dir)) != NULL)
        {
            if (file->d_name[0] == '.')
                continue; //opendir(".")
            //获取文件属性判断是普通文件发送给客户端
            stat(file->d_name, &st);
            if (S_ISREG(st.st_mode))
            {
                send(acceptfd, file->d_name, size, 0);
            }
        }
        //发送结束标志
        strcpy(buf, "send ok");
        send(acceptfd, buf, size, 0);
    }
    
    //put:新建打开文件接收写文件
    void put_server(int acceptfd, char *buf, int size)
    {
        int fd = open(buf + 4, O_WRONLY | O_CREAT | O_TRUNC, 0666) ;
        if (fd < 0)
        {
            perror("open err.");
            return;
        }
        while (1)
        {
            if (recv(acceptfd, buf, size, 0) < 0)//
            {
                perror("recv err.");
                return;
            }
            if (strncmp(buf, "send ok", 7) == 0)
                break;
            write(fd, buf, strlen(buf));
            //hello world\n
            //welcome\n
            //hi\n\0\0
         
        }
    }
    

    client.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
     #include <sys/types.h>
     #include <sys/stat.h>
     #include <fcntl.h>
    
    
    void show(void);
    void put_client(int sockfd,char *buf,int size);
    void list_client(int sockfd,char *buf,int size);
    int main(int argc, char const *argv[])
    {
        if(argc != 3)
        {
            printf("please input %s <ip> <port>\n",argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4 服务器
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));//"8888"
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
        if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        char buf[64];
        while(1)
        {
            show();
            fgets(buf,sizeof(buf),stdin);//10  20
            //最多读size-1,自动补'\0',读到'\n'
            if(buf[strlen(buf)-1] == '\n')
                buf[strlen(buf)-1] = '\0';
             send(sockfd,buf,sizeof(buf),0);
            if(strncmp(buf,"list",4)==0)
            {
                //函数
                list_client(sockfd,buf,sizeof(buf));
            }else if(strncmp(buf,"put ",4)==0)
            {
                //函数打开本地文件读文件内容发送给服务器
                put_client(sockfd,buf,sizeof(buf));
            }else if(strncmp(buf,"get ",4)==0)
            {
                //函数新建打开文件接收写文件
            }     
            
        }
        // 6.关闭套接字
        close(sockfd);
        return 0;
    }
    
    void show(void)
    {
        printf("--------------list------------------\n");
        printf("--------------put filename----------\n");
        printf("--------------get filename----------\n");
        printf("--------------quit------------------\n");
    }
    
    //1.list:循环接收服务器发的文件名
    void list_client(int sockfd,char *buf,int size)
    {
        while(1)
        {
            if(recv(sockfd,buf,size,0)<0)
            {
                perror("recv err.");
                return ;
            }
            if(strncmp(buf,"send ok",7)==0)
            {
                break;
            }
            printf("%s\n",buf);
        }
    }
    
    //put函数打开本地文件读文件内容发送给服务器
    void put_client(int sockfd,char *buf,int size)
    {
        //1.打开文件
        int fd=open(buf+4,O_RDONLY);//put test.c
        if(fd < 0)
        {
            perror("open err.");
            return ;
        } 
        //hello world\n
        //welcome\n
        //hi\n
       int ret;
        while( ret=read(fd,buf,size-1))//read   size=10    hello wor\0  l   d\nwelcome\n  
        //hi\n\0\0\0\0\0\0\0
        {
            buf[ret]='\0';
            send(sockfd,buf,size,0);
        }
        strcpy(buf,"send ok");
        send(sockfd,buf,size,0);
    }
    

    三、UDP编程

    1.通信流程

    udp流程(类似发短信)
    server:
    创建数据报套接字socket(,SOCK_DGRAM,)----->有手机
    绑定网络信息bind()---------------------->绑定号码发短信知道发给谁
    接收信息recvfrom()--------------------->接收短信
    关闭套接字close()----------------------->接收完毕
    
    client:
    创建数据报套接字(socket())----------------------->有手机
    指定服务器的网络信息------------------------------>有对方号码
    发送信息sendto()---------------------------->发送短信
    关闭套接字close()--------------------------->发送完
    

    2.函数接口

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

    3.实现

     server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
        //1.创建数据报套接字
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //填充结构体
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        //2绑定
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        //3.循环收消息
        char buf[64];
        while (1)
        {
            if (recvfrom(sockfd, buf, sizeof(buf), 0,
                         (struct sockaddr *)&caddr, &len) < 0)
            {
                perror("recv err.");
                return -1;
            }
            printf("%s %d:%s\n", inet_ntoa(caddr.sin_addr),
                   ntohs(caddr.sin_port), buf);
        }
        close(sockfd);
        return 0;
    }
    

    client.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    int main(int argc, char const *argv[])
    {
        //1.创建数据报套接字
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //填充结构体
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
        socklen_t len = sizeof(caddr);
    
        //3.循环发消息
        char buf[64];
        while (1)
        {
            fgets(buf,sizeof(buf),stdin);
            if(buf[strlen(buf)-1]=='\n')
               buf[strlen(buf)-1]='\0';
            sendto(sockfd,buf,sizeof(buf),0,\
            (struct sockaddr *)&saddr,sizeof(saddr));
        }
        close(sockfd);
        return 0;
    }
    

    4.  练习实现如客户端发送"hello"给服务器端服务器接着给客户端回"recv:hello!!!!!"。

    注意
    1、对于TCP是先运行服务器客户端才能运行。
    2、对于UDP来说服务器和客户端运行顺序没有先后因为是无连接所以服务器和客户端谁先开始没有关系
    3、一个服务器可以同时连接多个客户端。想知道是哪个客户端登录可以在服务器代码里面打印IP和端口号。
    4、UDP客户端当使用send的时候上面需要加connect这个connect不是代表连接的作用而是指定客户端即将要发送给谁数据。这样就不需要使用sendto而用send就可以。
    5、在TCP里面也可以使用recvfrom和sendto使用的时候将后面的两个参数都写为NULL就OK。
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr,caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        //saddr.sin_addr.s_addr = inet_addr(argv[1]);
        // saddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY   0.0.0.0
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
            printf("client:ip=%s port=%d\n",inet_ntoa(caddr.sin_addr),\
            ntohs(caddr.sin_port));//inet_ntoa
            // 5.收发消息
            char buf[64];
            int recvbyte;
            while (1)
            {
                recvbyte = recv(acceptfd, buf, sizeof(buf), MSG_DONTWAIT);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                   // return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("client exit.\n");
                    break;
                }
                else
                {
                    printf("buf:%s\n", buf);
                }
            }
            // 6.关闭套接字
           close(acceptfd);
        }
        close(sockfd);
        return 0;
    }
    

    5.项目-网络聊天室

    5.1 项目要求

    利用UDP协议实现一套聊天室软件。服务器端记录客户端的地址客户端发送消息后服务器群发给各个客户端软件。

    问题思考

    • 客户端会不会知道其它客户端地址

    UDP客户端不会直接互连所以不会获知其它客户端地址所有客户端地址存储在服务器端。

    • 有几种消息类型
    • 登录服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。
    • 聊天服务器只需要把某个客户端的聊天消息转发给所有其它客户端。
    • 退出服务器删除退出客户端的地址并把退出消息发送给其它客户端。
    • 服务器如何存储客户端的地址

    数据结构可以选择线性数据结构

    链表节点结构体
    struct node{
    	struct sockaddr_in addr;//data   memcmp
    	struct node *next;
    };
    
    消息对应的结构体(同一个协议)
    typedef struct msg_t
    {
        int type;//'L' C  Q    enum un{login,chat,quit};
        char name[32];//用户名
        char text[128];//消息正文
    }MSG_t;
    
    int memcmp(void *s1,void *s2,int size)
    

    • 客户端如何同时处理发送和接收

    客户端不仅需要读取服务器消息而且需要发送消息。读取需要调用recvfrom发送需要先调用gets两个都是阻塞函数。所以必须使用多任务来同时处理可以使用多进程或者多线程来处理。

    5.2 程序流程图

    服务器端

    客户端

    客户端

    server.c代码

    #include <stdio.h>
    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    #include "head.h"
    //链表节点结构体
    typedef struct node_t
    {
        struct sockaddr_in addr;
        struct node_t *next;
    } link_t;
    
    void login_s(int sockfd, MSG_t msg, link_t *p, struct sockaddr_in caddr);
    void quit_s(int sockfd, MSG_t msg, link_t *p, struct sockaddr_in caddr);
    void chat_s(int sockfd, MSG_t msg, link_t *p, struct sockaddr_in caddr);
    link_t *createLink(void);
    
    int main(int argc, char const *argv[])
    {
        //1.创建数据报套接字
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //填充服务器端结构体
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
        MSG_t msg;
        pid_t pid = fork();
    
        if (pid > 0)
        {
            //循环响应请求
            link_t *p = createLink();
            while (1)
            {
                if (recvfrom(sockfd, &msg, sizeof(msg), 0,
                             (struct sockaddr *)&caddr, &len) < 0)
                {
                    perror("recvfrom err.");
                    return -1;
                }
                switch (msg.type)
                {
                case Login:
                    login_s(sockfd, msg, p, caddr);
                    break;
                case Chat:
                    chat_s(sockfd, msg, p, caddr);
                    break;
                case Quit:
                    quit_s(sockfd, msg, p, caddr);
                    break;
                }
            }
        }
        else if (pid == 0)
        {
            msg.type = Chat;
            strcpy(msg.name, "server");
            while (1)
            {
                fgets(msg.text, sizeof(msg.text), stdin);
                if (msg.text[strlen(msg.text) - 1] == '\n')
                    msg.text[strlen(msg.text) - 1] = '\0';
                sendto(sockfd, &msg, sizeof(msg), 0,
                       (struct sockaddr *)&saddr, sizeof(saddr));
            }
        }
        return 0;
    }
    //0.创建一个空的有头单向链表
    link_t *createLink(void)
    {
        //1>malloc开辟节点空间
        link_t *p = (link_t *)malloc(sizeof(link_t));
        if (NULL == p)
        {
            perror("malloc head node err.");
            return NULL;
        }
        p->next = NULL; //空
    
        return p;
    }
    
    //1.登录函数
    //功能将谁登录发送给所有已经登录客户端
    //将新登录客户端通信结构体插入链表
    void login_s(int sockfd, MSG_t msg, link_t *p, struct sockaddr_in caddr)
    {
        //1.遍历链表转发那个客户登录
        sprintf(msg.text, "%s login.", msg.name);
        while (p->next)
        {
            p = p->next;
            sendto(sockfd, &msg, sizeof(msg), 0,
                   (struct sockaddr *)&(p->addr), sizeof(p->addr));
        }
        //2.创建新节点保存新登录客户端的信息
        link_t *pnew = (link_t *)malloc(sizeof(link_t));
        if (NULL == pnew)
        {
            perror("malloc new node err.");
            return;
        }
        //3.初始化节点
        pnew->addr = caddr;
        pnew->next = NULL;
        //4.链接到链表尾
        p->next = pnew;
    }
    //2.聊天
    //功能给所有除自己的客户端转发消息
    void chat_s(int sockfd, MSG_t msg, link_t *p, struct sockaddr_in caddr)
    {
        while (p->next != NULL)
        {
            p = p->next;
            if (memcmp(&(p->addr), &caddr, sizeof(caddr)) != 0)
            {
                sendto(sockfd, &msg, sizeof(msg), 0,
                       (struct sockaddr *)&(p->addr), sizeof(p->addr));
            }
        }
    }
    
    //3.退出
    //功能将谁退出转发给所有还登录着的客户将自己的ip和端口从
    //链表中删除。
    void quit_s(int sockfd, MSG_t msg, link_t *p, struct sockaddr_in caddr)
    {
        link_t *pdel = NULL;
        sprintf(msg.text, "%s quit", msg.name);
        while (p->next != NULL)
        {
            if (memcmp(&(p->next->addr), &caddr, sizeof(caddr)) == 0)
            {
                pdel = p->next;
                p->next = pdel->next;
    
                free(pdel);
                pdel = NULL;
            }
            else
            {
                p = p->next;
                sendto(sockfd, &msg, sizeof(msg), 0,
                       (struct sockaddr *)&(p->addr), sizeof(p->addr));
            }
        }
    }

    client.c代码

    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    #include "head.h"
    
    int main(int argc, char const *argv[])
    {
        //1.创建数据报套接字
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //填充服务器端结构体
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
        MSG_t msg; //定义一个结构体变量保存要发送的消息
        //发送请求
        //1》登录只登录一次
        msg.type = Login;
        printf("please input your name>>");
        fgets(msg.name, sizeof(msg.name), stdin);
        if (msg.name[strlen(msg.name) - 1] == '\n')
            msg.name[strlen(msg.name) - 1] = '\0';
    
        sendto(sockfd, &msg, sizeof(msg), 0,
               (struct sockaddr *)&saddr, sizeof(saddr));
        pid_t pid = fork();
        if (pid < 0)
        {
            perror("fork err.");
            return -1;
        }
        else if (pid == 0)
        {
            //循环聊天
            while (1)
            {
                fgets(msg.text, sizeof(msg.text), stdin);
                if (msg.text[strlen(msg.text) - 1] == '\n')
                    msg.text[strlen(msg.text) - 1] = '\0';
                if (strncmp(msg.text, "quit", 4) == 0)
                {
                    msg.type = Quit;
                    sendto(sockfd, &msg, sizeof(msg), 0,
                           (struct sockaddr *)&saddr, sizeof(saddr));
                    break;
                }
                else
                {
                    msg.type = Chat;
                    sendto(sockfd, &msg, sizeof(msg), 0,
                           (struct sockaddr *)&saddr, sizeof(saddr));
                }
            }
        }
        else
        {
            while (1)
            {
                if (recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL) < 0)
                {
                    perror("recv from err.");
                    return -1;
                }
                printf("%s:%s\n", msg.name, msg.text);
            }
        }
        close(sockfd);
        return 0;
    }
    

    四、linux下I/O模及特点

    1.阻塞式IO  

     特点最简单、最常用效率低

    阻塞I/O 模式是最普遍使用的I/O 模式大部分程序使用的都是阻塞模式的I/O 。
    缺省情况下及系统默认状态套接字建立后所处于的模式就是阻塞I/O 模式。
    学习的读写函数在调用过程中会发生阻塞相关函数如下
    •读操作中的read、recv、recvfrom
         读阻塞--》需要读缓冲区中有数据可读读阻塞解除
    •写操作中的write、send
         写阻塞--》阻塞情况比较少主要发生在写入的缓冲区的大小小于要写入的数据量的情况下写操作不进行任何拷贝工作将发生阻塞一旦缓冲区有足够的空间内核将唤醒进程将数据从用户缓冲区拷贝到相应的发送数据缓冲区。 
    注意sendto没有写阻塞
     1无sendto函数的原因
    sendto不是阻塞函数本身udp通信不是面向链接的udp无发送缓冲区即sendto没有发送缓冲区send是有发送缓存区的即sendto不是阻塞函数。
     2UDP不用等待确认没有实际的发送缓冲区所以UDP协议中不存在缓冲区满的情况在UDP套接字上进行写操作永远不会阻塞。
    •其他操作accept、connect
    

     2. 非阻塞式IO 

         特点可以处理多路IO需要轮询浪费CPU资源

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

    2. 通过函数自带的参数设置非阻塞

    .2 通过设置文件描述符属性设置非阻塞fcntl

    int fcntl(int fd, int cmd, ... /* arg */ );
    功能设置文件描述符属性
    参数
       fd:文件描述符
       cmd设置方式 - 功能选择
            F_GETFL  获取文件描述符的状态信息     第三个参数化忽略
            F_SETFL  设置文件描述符的状态信息     通过第三个参数设置
                    O_NONBLOCK  非阻塞
                    O_ASYNC      异步
                    O_SYNC      同步
      arg:设置的值  in
    返回值
          特殊选择返回特殊值 - F_GETFL  返回的状态值(int)
            其他成功0  失败-1更新errno
            
    使用0为例
      0-原本阻塞、读权限  修改或添加非阻塞
      int flags=fcntl(0,F_GETFL);//1.获取文件描述符原有的属性信息
      flags = flags | O_NONBLOCK;//2.修改添加权限
      fcntl(0,F_SETFL,flags);    //3.将修改好的权限设置回去
    
    
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
        //设置0文件描述符的非阻塞
        //1.获取原属性
        int flags;
        flags = fcntl(0, F_GETFL);
        //2.修改属性
        flags = flags | O_NONBLOCK;
        //3.设置回去
        fcntl(0, F_SETFL, flags);
    
        char buf[32];
        while (1)
        {
            sleep(1);
            if (fgets(buf, sizeof(buf), stdin) == NULL)
            {
                perror("fgets err.");
            }
            printf("buf:%s\n", buf);
        }
        return 0;
    }
    

    3.信号驱动IO (异步IO模型 非重点)

    特点异步通知模式需要底层驱动的支持

    •  通过信号方式当内核检测到设备数据后会主动给应用发送信号SIGIO。
    • 应用程序收到信号后做异步处理即可。
    • 应用程序需要把自己的进程号告诉内核并打开异步通知机制。

    标准模板

    //将APP进程号告诉驱动程序
    fcntl(fd, F_SETOWN, getpid());
    
    //使能异步通知
    int flag;
    flag = fcntl(fd, F_GETFL);
    flag |= O_ASYNC;  //也可以用FASYNC标志
    fcntl(fd, F_SETFL, flag);
    
    signal(SIGIO, handler);
    

    示例用非阻塞方式监听鼠标的数据操作鼠标需要增加sudo权限

    查看自己使用的鼠标/dev/input    

     检查鼠标设备sudo cat /dev/input/mouse0

    #include 
    #include 
    #include 
    #include 
    #include 
    
    int fd;
    void handler(int sig)
    {
        char buf[32] = "";
        int ret = read(fd, buf, sizeof(buf) - 1);
        buf[ret] = '\0';
        printf("mouse:%s\n", buf);
    }
    
    int main(int argc, char const *argv[])
    {
        fd = open("/dev/input/mouse0", O_RDONLY);
        if (fd < 0)
        {
            perror("open mouse err.");
            return -1;
        }
        //1.将文件描述符、进程ID告诉底层驱动
        fcntl(fd, F_SETOWN, getpid());
    
        //2.设置fd文件描述符的异步通知属性
        int flags;
        flags = fcntl(fd, F_GETFL);
        flags |= O_ASYNC;
        fcntl(fd, F_SETFL, flags);
        //3.捕捉信号
        signal(SIGIO, handler);
        while (1)
        {
            sleep(1);
            printf("hello world.\n");
        }
        return 0;
    }
    

    前三种使用场景假设总结

    假设妈妈有一个孩子孩子在房间里睡觉妈妈需要及时获知孩子是否醒了如何做

    1. 进到房间陪着孩子一起睡觉孩子醒了会吵醒妈妈不累但是不能干别的了
    2. 时不时进房间看一下简单空闲时间还能干点别的但是很累
    3. 妈妈在客厅干活小孩醒了他会自己走出房门告诉妈妈互不耽误

    4. IO多路复用

    4.1 IO多路复用场景假设

    假设妈妈有三个孩子分别不同的房间里睡觉需要及时获知每个孩子是否醒了如何做

    1. 不停进每个房间看一下简单空闲时间还能干点别的但是很累
    2. 把三个房间的门都打开在客厅睡觉同时监听所有房间的哭声如果被哭声吵醒那么能准确定位某个房间及时处理即可既能得到休息也能及时获知每个孩子的状态

    4.2 IO多路复用机制

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

    基本流程
    1. 先构造一张有关文件描述符的表(集合、数组); 
    2. 将你关心的文件描述符加入到这个表中;
    3. 然后调用一个函数。 select / poll 
    4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候
    该函数才返回(阻塞)
    5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);
    6. 做对应的逻辑处理;

    5. 实现IO多路复用的方式

    5.1 select

     int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);
       功能select用于监测是哪个或哪些文件描述符产生事件;
       参数nfds    监测的最大文件描述个数
            这里是个数使用的时候注意与文件中最后一次打开的文件
              描述符所对应的值的关系是什么
        readfds  读事件集合; //读用的多
         writefds 写事件集合;  //NULL表示不关心
         exceptfds异常事件集合;  
         timeout超时检测 1
       如果不做超时检测传 NULL 
       select返回值  <0 出错
                   >0 表示有事件产生;
       如果设置了超时检测时间&tv
          select返回值
             <0 出错
            >0 表示有事件产生;
            ==0 表示超时时间已到;
    
         struct timeval {
                   long    tv_sec;         /* seconds */
                   long    tv_usec;        /* microseconds */
               };
     void FD_CLR(int fd, fd_set *set);//将fd从表中清除
     int  FD_ISSET(int fd, fd_set *set);//判断fd是否在表中
     void FD_SET(int fd, fd_set *set);//将fd添加到表中
     void FD_ZERO(fd_set *set);//清空表1
    
    

    总结select实现IO多路复用特点*

    1. 一个进程最多只能监听1024个文件描述符 千级别
    2. select被唤醒之后需要重新轮询一遍驱动的poll函数效率比较低消耗CPU资源;
    3. select每次会清空表每次都需要拷贝用户空间的表到内核空间效率低一个进程0~4G0~3G是用户态3G~4G是内核态拷贝是非常耗时的;
    

    练习1检测终端输入事件(键盘 0)鼠标输入事件。

    //鼠标设备的路径/dev/input/mouse0  

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    /* According to earlier standards */
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
        //鼠标 /dev/input/mouse0
        //键盘0 - stdin
        int fd_mouse = open("/dev/input/mouse0", O_RDONLY);
        if (fd_mouse < 0)
        {
            perror("open mouse err.");
            return -1;
        }
        //引入IO多路复用机制 select  0 fd_mouse-检测读事件
        //1.创建表
        fd_set readfds, tempfds;
        FD_ZERO(&readfds); //清空表
        //2.将关心文件描述符添加到表中
        FD_SET(0, &readfds);
        FD_SET(fd_mouse, &readfds);
        
        int maxfd = fd_mouse;
        char buf[32] = "";
        while (1)
        {
            tempfds = readfds;
            //3.调用select函数检测
            int ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
            if (ret < 0)
            {
                perror("select err.");
                return -1;
            }
            //4.当有一个或多个事件产生select函数返回
            //5.判断是那个或那几个产生事件
            if (FD_ISSET(0, &tempfds))
            {
                //6.处理事件
                //键盘
                fgets(buf, sizeof(buf), stdin);
                printf("key:%s\n", buf);
            }
            //鼠标
            if (FD_ISSET(fd_mouse, &tempfds))
            {
                int ret = read(fd_mouse, buf, sizeof(buf) - 1);
                buf[ret] = '\0';
                printf("mouse:%s\n", buf);
            }
        }
        close(fd_mouse);
        return 0;
    }
    

    练习尝试用select检测0 和sockfd (TCP)实现一个服务器响应多个客户端的连接写完的提交群里一下。

    server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    int main(int argc, char const *argv[])
    {
        //1.创建套接字   socket  TCP
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1])); //"8888"
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
        //2.绑定bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
        //listen监听 将主动套接字变被动
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        //引入select 0 sockfd  - >读事件
        //1.创建表
        fd_set readfds, tempfds;
        FD_ZERO(&readfds);
        //2.添加关心文件描述符
        FD_SET(0, &readfds);
        FD_SET(sockfd, &readfds);
    
        int maxfd = sockfd;
        char buf[128];
        while (1)
        {
            tempfds = readfds;
            int ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
            if (ret < 0)
            {
                perror("select err.");
                return -1;
            }
            if (FD_ISSET(0, &tempfds))
            {
                fgets(buf, sizeof(buf), stdin);
                printf("key:%s\n", buf);
                for(int i=4;i<=maxfd;i++)
                {
                    if(FD_ISSET(i,&readfds))
                     send(i,buf,sizeof(buf),0);
                }
            }
            if (FD_ISSET(sockfd, &tempfds))
            {
                int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                if (acceptfd < 0)
                {
                    perror("accept err.");
                    return -1;
                }
                printf("client: ip:%s port:%d\n", inet_ntoa(caddr.sin_addr),
                       ntohs(caddr.sin_port));
                FD_SET(acceptfd, &readfds);
                if (maxfd < acceptfd)
                    maxfd = acceptfd;
            }
            for (int i = 4; i <= maxfd; i++)
            {
                if (FD_ISSET(i, &tempfds))
                {
                    int ret=recv(i, buf, sizeof(buf), 0);
                    if(ret < 0)
                    {
                        perror("recv err.");
                        return -1;
                    }else if(ret == 0)
                    {
                        printf("%d client exit.\n",i);
                        FD_CLR(i,&readfds);//5
                        close(i);
                        if(maxfd==i)
                          maxfd--;
                    }else 
                    {
                        printf("%d :%s\n",i,buf);
                    }
                }
            }
        }
        return 0;
    }
    

    client.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
        if(argc != 3)
        {
            printf("please input %s <ip> <port>\n",argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4 服务器
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));//"8888"
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
        if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
    		char buf[64];
    	pid_t pid=fork();
    	if(pid < 0)
    	{
    		perror("fork err");
    		return -1;
    	}else if(pid ==0)
    	{
    		while(1)
    		{
    			recv(sockfd,buf,sizeof(buf),0);
    			printf("buf:%s\n",buf);
    		}
    
    	}
    	else{
    		while(1)
    		{
    			fgets(buf,sizeof(buf),stdin);//10  20
    			//最多读size-1,自动补'\0',读到'\n'
    			if(buf[strlen(buf)-1] == '\n')
    				buf[strlen(buf)-1] = '\0';
    			send(sockfd,buf,sizeof(buf),0);
    
    		}
    	}	
    	// 6.关闭套接字
    	close(sockfd);
    	return 0;
    }
    

    5.2 poll实现

    int poll(struct pollfd *fds, nfds_t nfds, int timeout);

       参数

       struct pollfd *fds

         关心的文件描述符数组struct pollfd fds[N];

       nfds:个数

       timeout 超时检测

        毫秒级的如果填10001秒

         如果-1阻塞

     struct pollfd {

         int   fd;         /* 检测的文件描述符 */

         short events;     /* 检测事件 */

         short revents;    /* 调用poll函数返回填充的事件poll函数一旦返回将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */

     };

        事件 POLLIN 读事件

                    POLLOUT : 写事件

                   POLLERR异常事件

    poll实现IO多路复用的特点

    1. 优化文件描述符个数的限制;(根据poll函数第一个函数的参数来定如果监听的事件为1个则结构体数组元素个数为1如果想监听100个那么这个结构体数组的元素个数就为100由程序员自己来决定)
    2. poll被唤醒之后需要重新轮询一遍驱动的poll函数效率比较低
    3. poll不需要重新构造文件描述符表只需要从用户空间向内核空间拷贝一次数据即可

    实现代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s \n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        //poll0 sockfd acceptfd ->读事件
        //1.创建表
        struct pollfd fds[200] = {};
        //2.将关心文件描述符添加到表中
        fds[0].fd = 0;
        fds[0].events = POLLIN;
    
        fds[1].fd = sockfd;
        fds[1].events = POLLIN;
    
        int last = 1;
    
        char buf[64];
        while (1)
        {
            //调用poll监测
            int ret = poll(fds, last + 1, -1); //阻塞
            if (ret < 0)
            {
                perror("poll err.");
                return -1;
            }
            for (int i = 0; i <= last; i++)
            {
                if (fds[i].revents == POLLIN)
                {
                    if (fds[i].fd == 0)
                    {
                        fgets(buf, sizeof(buf), stdin);
                        printf("key:%s\n", buf);
                        for(int j=2;i<=last;j++)
                        {
                            send(fds[j].fd,buf,sizeof(buf),0);
                        }
                    }
                    else if (fds[i].fd == sockfd)
                    {
                        // 4.阻塞等待客户端连接accept .返回通信文件描述符
                        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                        if (acceptfd < 0)
                        {
                            perror("accept err.");
                            return -1;
                        }
                        printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
                        printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                               ntohs(caddr.sin_port)); //inet_ntoa
                        //将acceptfd添加到表
                        last++;
                        fds[last].fd = acceptfd;
                        fds[last].events = POLLIN;
                    }
                    else
                    {
                        int recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0);
                        if (recvbyte < 0)
                        {
                            perror("recv err.");
                            return -1;
                        }
                        else if (recvbyte == 0)
                        {
                            printf("%d client exit\n", fds[i].fd);
                            close(fds[i].fd);
                            fds[i] = fds[last];
                            last--;
                            i--;
                        }
                        else
                        {
                            printf("%d :%s\n", fds[i].fd, buf);
                        }
                    }
                }
            }
        }
        close(sockfd);
        return 0;
    }
    

    5.3 epoll实现 (异步)

    epoll实现机制(了解)

    epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目eg1GB机器上这个上限10万个左右。
    每个fd上面有callback(回调函数)函数只有活跃的fd才有主动调用callback不需要轮询。
    
    注意
       Epoll处理高并发百万级不关心底层怎样实现只需要会调用就可以。
    

    函数接口

    #include <sys/epoll.h>
    int epoll_create(int size); 
    功能创建红黑树根节点
     参数size不作为实际意义值 >0 即可
    返回值成功时返回epoll文件描述符失败时返回-1
    
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    功能控制epoll属性
        epfd:epoll_create函数的返回句柄。
        op:表示动作类型。有三个宏 来表示
                EPOLL_CTL_ADD注册新的fdepfd
                EPOLL_CTL_MOD修改已注册fd的监听事件
                EPOLL_CTL_DELepfd中删除一个fd
        Fd:需要监听的fd
                event:告诉内核需要监听什么事件
                EPOLLIN:表示对应文件描述符可读
                EPOLLOUT:可写
                EPOLLPRI有紧急数据可读
                EPOLLERR错误
                EPOLLHUP被挂断
                EPOLLET触发方式边缘触发(默认使用边缘触发)
                 ET模式表示状态的变化
    返回值成功时返回0失败时返回-1
    
    typedef union epoll_data {
                   void* ptr;(无效)
                   int fd;
                   uint32_t u32;
                   uint64_t u64;
               } epoll_data_t;
               struct epoll_event {
                   uint32_t events / * Epoll事件* /
                   epoll_data_t data / *用户数据变量* /
    };
    //等待事件到来
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    功能等待事件的产生类似于select的用法
         epfd:句柄
         events用来保存从内核得到事件的集合
         maxevents:表示每次能处理事件最大个数
         timeout超时时间毫秒0立即返回-1阻塞
    成功时返回发生事件的文件描述个数失败时返回-1
    帮助理解
    1.epoll可以同时支持水平触发和边缘触发Edge Triggered只告诉进程哪些文件描述符刚刚变为就绪状态它只说一遍如果我们没有采取行动那么它将不会再次告知这种方式称为边缘触发理论上边缘触发的性能要更高一些但是代码实现相当复杂。
    2.epoll同样只告知那些就绪的文件描述符而且当我们调用epoll_wait()获得就绪文件描述符时 返回的不是实际的描述符而是一个代表就绪描述符数量的值你只需要去epoll指定的一个数组中 依次取得相应数量的文件描述符即可这里也使用了内存映射mmap技术这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
     3.另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中进程只有在调用一定的方法后内核才对所有监视的文件描述符进行扫描而epoll事先通过epoll_ctl()
    来注册一个文件描述符一旦基于某个文件描述符就绪时内核会采用类似callback的回调机制 迅速激活这个文件描述符当进程调用epoll_wait()时便得到通知。
    
    

    epoll实现IO多路复用的特点

    •监听的最大的文件描述符没有个数限制理论上取决与你自己的系统
    •异步I/OEpoll当有事件产生被唤醒之后文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符不需要轮询效率高
    epoll不需要重新构造文件描述符表只需要从用户空间向内核空间拷贝一次数据即可.
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/epoll.h>
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        //epoll0 sockfd acceptfd ->读事件
        struct epoll_event event;      //暂时保存添加的事件
        struct epoll_event revent[10]; //暂时保存从链表中拿出来的事件
            //1.创建树
            int epfd = epoll_create(1);
        //2.将关心文件描述符添加到数上
        event.data.fd = 0;
        event.events = EPOLLIN | EPOLLET;
        epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
    
        event.data.fd = sockfd;
        event.events = EPOLLIN | EPOLLET;
        epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
    
        int last = 1;
    
        char buf[64];
        while (1)
        {
            //拿事件处理,ret实际拿到的个数
            int ret = epoll_wait(epfd, revent, 10, -1);
            if (ret < 0)
            {
                perror("epoll err.");
                return -1;
            } 
            for (int i = 0; i < ret; i++)
            {
                if (revent[i].data.fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    printf("key:%s\n", buf);
                }
                else if (revent[i].data.fd == sockfd)
                {
                    // 4.阻塞等待客户端连接accept .返回通信文件描述符
                    int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                    if (acceptfd < 0)
                    {
                        perror("accept err.");
                        return -1;
                    }
                    printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
                    printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                           ntohs(caddr.sin_port)); //inet_ntoa
                    //将acceptfd添加到表树上
                    event.data.fd=acceptfd;
                    event.events=EPOLLIN|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,acceptfd,&event);               
                }
                else
                {
                    int recvbyte = recv(revent[i].data.fd, buf, sizeof(buf), 0);
                    if (recvbyte < 0)
                    {
                        perror("recv err.");
                        return -1;
                    }
                    else if (recvbyte == 0)
                    {
                        printf("%d client exit\n", revent[i].data.fd);
                        close(revent[i].data.fd);
                        epoll_ctl(epfd,EPOLL_CTL_DEL,revent[i].data.fd,NULL); 
                    }
                    else
                    {
                        printf("%d :%s\n",revent[i].data.fd, buf);
                    }
                }
            }
        }
        close(sockfd);
        return 0;
    }
    

    五、服务器模型

    • 在网络程序里面,通常都是一个服务器处理多个客户机。
    • 为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。

    1.循环服务器模型

    同一个时刻只能响应一个客户端的请求伪代码如下

    socket()
    bind();
    listen();
    while(1)
    {
    	accept();
    	while(1)
    	{
    		process(); //处理
    	}
    	close();
    }
    

    2. 并发服务器模型

    同一个时刻可以响应多个客户端的请求常用的模型有多进程模型/多线程模型/IO多路复用模型。 

    多进程和多线程实现并发服务器思想
           每有一个客户端连接创建一个子进程或线程和这个客户端通信父进程或主线程阻塞等待下一个客户端连接。

    2.1 多进程模型

    每来一个客户端连接开一个子进程来专门处理客户端的数据实现简单但是系统开销相对较大更推荐使用线程模型。伪代码如下

    socket()
    bind();
    listen();
    while(1)
    {
    	accept();	if(fork() == 0)  //子进程
    	{
    		while(1)
    		{
    			process();
    		}
    		close(client_fd);
    		exit();
    	}
    }
    
    

    注意收到客户端消息后打印下是来自哪个客户端的数据来电显示

    使用SIGCHLD来处理子进程结束的信号信号函数中回收进程资源。

    fork.c代码

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    
    int main(int argc, char const *argv[])
    {
    	int a=100;
    	char buf[32];
    	int ret;
    	printf("88888888888888888888888\n");
    
    	int fd=open("./fork.c",O_RDONLY);
    
    	pid_t pid=fork();
    	if(pid < 0)
    	{
    		perror("fork err.");
    		return -1;
    	}else if(pid == 0)
    	{
    		a=10000;
    		printf("my child. %d %d\n",a,fd);
    		ret=read(fd,buf,sizeof(buf)-1);
    		buf[ret]='\0';
    		printf("buf:%s\n",buf);
    		close(fd);
    
    	}else
    	{
    		sleep(1);
    		printf("my father %d %d\n",a,fd);
    		ret=read(fd,buf,sizeof(buf)-1);
    		buf[ret]='\0';
    		printf("buf:%s\n",buf);
    
        }
    
        printf("------------------------%d\n",a);
        return 0;
    }
    

     fork创建进程的特点
           1.fork创建的子进程几乎拷贝了父进程所有的内容
              三个段正文、堆栈、数据段
           2.fork之后父进程中返回子进程的PID子进程中
             返回0.
           3.父进程先退出子进程孤儿进程子进程先退出
           父进程没有回收资源子进程僵尸进程。
           4.fork之前的代码被复制不会重新执行fork之后 
             的代码会被复制并执行。
           5.fork之前打开的文件fork之后拿到的是同一个文件
           描述符操作同一个文件指针。
           6.fork创建进程之后两个进程就相互独立。
           7.子进程状态发生改变会给父进程发送一个SIGCHLD信号

    pthread_server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>
    
    void handler(int sig)
    {
        waitpid(-1, NULL, WNOHANG);
    }
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        //注册信号
        signal(SIGCHLD, handler); //void (*handler)(int )
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
            printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                   ntohs(caddr.sin_port)); //inet_ntoa
    
            pid_t pid = fork();
            if (pid < 0)
            {
                perror("fork err.");
                return -1;
            }
            else if (pid == 0)
            {
                close(sockfd);
                // 5.收发消息
                char buf[64];
                int recvbyte;
                while (1)
                {
                    recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
                    if (recvbyte < 0)
                    {
                        perror("recv err.");
                        return -1;
                    }
                    else if (recvbyte == 0)
                    {
                        printf("client exit.\n");
                        break;
                    }
                    else
                    {
                        printf("buf:%s\n", buf);
                    }
                }
                close(acceptfd);
                exit(-1); //结束子进程
            }
            // 6.关闭套接字
            close(acceptfd);
        }
        close(sockfd);
        return 0;
    }
    

    2.2 多线程模型

    每来一个客户端连接开一个子线程来专门处理客户端的数据实现简单占用资源较少属于使用比较广泛的模型

    socket()
    bind();
    listen();
    while(1)
    {
    	accept();
    	pthread_create();
    }
    

    signal.c 代码

    #include <stdio.h>
    #include <signal.h>
    #include <stdlib.h>
    
    void handler(int sig)
    {
    	printf("--------------ctrt+c\n");
    }
    
    int main(int argc, const char *argv[])
    {
    
        signal(SIGINT,handler);
    
    	while(1)
    	{
    		sleep(5);
    		printf("hello world.\n");
    	}
    	return 0;
    }
    

    pthread_server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <pthread.h> 
    
    //void *(*thread)(void *)
    void *pthread(void *arg)
    {
        int acceptfd = *((int *)arg);
        char buf[64];
        int recvbyte;
        while (1)
        {
            recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
            if (recvbyte < 0)
            {
                perror("recv err.");
                return NULL;
            }
            else if (recvbyte == 0)
            {
                printf("client exit.\n");
                break;
            }
            else
            {
                printf("%d buf:%s\n", acceptfd, buf);
            }
        }
        close(acceptfd);
        return NULL; //pthread_exit(NULL);
    }
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
            printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                   ntohs(caddr.sin_port)); //inet_ntoa
    
            pthread_t tid;
            pthread_create(&tid, NULL, pthread, &acceptfd);
            pthread_detach(tid);
        }
        close(sockfd);
        return 0;
    }
    

    2.3 IO多路复用模型

    借助select、poll、epoll机制将新连接的客户端描述符增加到描述符表中只需要一个线程即可处理所有的客户端连接在嵌入式开发中应用广泛不过代码写起了稍显繁琐。

    3. 网络超时检测

    3.1 应用场景

    • 在网络通信中很多操作会使得进程阻塞
      • TCP套接字中的recv/accept
      • UDP套接字中的recvfrom
    • 超时检测的必要性
      • 避免进程在没有数据时无限制地阻塞
      • 实现某些特定协议要求比如某些设备规定发送请求数据后如果多长时间后没有收到来自设备的回复需要做出一些特殊处理

    3.2 利用函数参数设置

    如使用select/poll/epoll函数最后一个参数可以设置超时。

    1.select设置超时
    struct timeval tm = {2, 0};//设置2s打算阻塞
    sret = select(maxfd + 1, &tempfds, NULL, NULL, &tm);
    第五个参数
     struct timeval {
         long    tv_sec;         /*秒*/
         long    tv_usec;        /*微秒*/
     };
     
     2.poll
     int poll(struct pollfd *fds, nfds_t nfds, int timeout);
       第三个参数时间单位是毫秒 -1阻塞 2000=2s
       ret = poll(event, num, 2000);//超时检测时间为2s
    
    3.epoll 设置的是epoll_wait
     int epoll_wait(int epfd, struct epoll_event *events,
                          int maxevents, int timeout);
      第四个参数时间单位是毫秒 -1阻塞 2000=2s
      ret = epoll_wait(epfd, events, 20, 2000);
    
    设置超时后的返回值都为<0  error
                            =0   超时
                            >0   正确
    

    select.c代码

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/select.h>
    
    /* According to earlier standards */
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int main(int argc, char const *argv[])
    {
        //鼠标 /dev/input/mouse0
        //键盘0 - stdin
        int fd_mouse = open("/dev/input/mouse0", O_RDONLY);
        if (fd_mouse < 0)
        {
            perror("open mouse err.");
            return -1;
        }
        //引入IO多路复用机制 select  0 fd_mouse-检测读事件
        //1.创建表
        fd_set readfds, tempfds;
        FD_ZERO(&readfds); //清空表
    
        //2.将关心文件描述符添加到表中
        FD_SET(0, &readfds);
        FD_SET(fd_mouse, &readfds);
    
        int maxfd = fd_mouse;
    
        char buf[32] = "";
        while (1)
        {
            tempfds = readfds;
            //3.调用select函数检测
    		struct timeval tv={2,0};
            int ret = select(maxfd + 1, &tempfds, NULL, NULL, &tv);
            if (ret < 0)
            {
                perror("select err.");
                return -1;
            }else if(ret == 0)
    		{
    			printf("time out --------------------\n");
    			continue;
    
    		}
            //4.当有一个或多个事件产生select函数返回
            //5.判断是那个或那几个产生事件
            if (FD_ISSET(0, &tempfds))
            {
                //6.处理事件
                //键盘
                fgets(buf, sizeof(buf), stdin);
                printf("key:%s\n", buf);
            }
            //鼠标
            if (FD_ISSET(fd_mouse, &tempfds))
            {
                int ret = read(fd_mouse, buf, sizeof(buf) - 1);
                buf[ret] = '\0';
                printf("mouse:%s\n", buf);
            }
        }
    
        close(fd_mouse);
        return 0;
    }
    

    poll.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <poll.h>
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        //poll0 sockfd acceptfd ->读事件
        //1.创建表
        struct pollfd fds[200] = {};
        //2.将关心文件描述符添加到表中
        fds[0].fd = 0;
        fds[0].events = POLLIN;
    
        fds[1].fd = sockfd;
        fds[1].events = POLLIN;
    
        int last = 1;
    
        char buf[64];
        while (1)
        {
            //调用poll监测
            int ret = poll(fds, last + 1,2000); //阻塞
    		if (ret < 0)
    		{
    			perror("poll err.");
    			return -1;
    		}//当事件产生将这个结构体元素中第二个成员的值赋值给第三个成员。
    		//没有事件第三个成员为0
    		else if(ret == 0)
    		{
    			printf("time out -----------------\n");
    			continue;	
    		}
    		for (int i = 0; i <= last; i++)
    		{
    			if (fds[i].revents == POLLIN)
    			{
                    if (fds[i].fd == 0)
                    {
                        fgets(buf, sizeof(buf), stdin);
                        printf("key:%s\n", buf);
                        for(int j=2;j<=last;j++)
                        {
                            send(fds[j].fd,buf,sizeof(buf),0);
                        }
                    }
                    else if (fds[i].fd == sockfd)
                    {
                        // 4.阻塞等待客户端连接accept .返回通信文件描述符
                        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                        if (acceptfd < 0)
                        {
                            perror("accept err.");
                            return -1;
                        }
                        printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
                        printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                               ntohs(caddr.sin_port)); //inet_ntoa
                        //将acceptfd添加到表
                        last++;
                        fds[last].fd = acceptfd;
                        fds[last].events = POLLIN;
                    }
                    else
                    {
                        int recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0);
                        if (recvbyte < 0)
                        {
                            perror("recv err.");
                            return -1;
                        }
                        else if (recvbyte == 0)
                        {
                            printf("%d client exit\n", fds[i].fd);
                            close(fds[i].fd);
                            fds[i] = fds[last];
                            last--;
                            i--;
                        }
                        else
                        {
                            printf("%d :%s\n", fds[i].fd, buf);
                        }
                    }
                }
            }
        }
        close(sockfd);
        return 0;
    }
    

    epoll.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/epoll.h>
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        //epoll0 sockfd acceptfd ->读事件
        struct epoll_event event;      //暂时保存添加的事件
        struct epoll_event revent[10]; //暂时保存从链表中拿出来的事件
        //1.创建树
        int epfd = epoll_create(1);
        //2.将关心文件描述符添加到数上
        event.data.fd = 0;
        event.events = EPOLLIN | EPOLLET;
        epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
    
        event.data.fd = sockfd;
        event.events = EPOLLIN | EPOLLET;
        epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
    
        char buf[64];
        while (1)
        {
            //拿事件处理,ret实际拿到的个数
            int ret = epoll_wait(epfd, revent, 10, 2000);
            if (ret < 0)
            {
                perror("epoll err.");
                return -1;
            }else if(ret == 0)
    		{
    			printf("timeout -----------------\n");
    			continue;
    		}
            for (int i = 0; i < ret; i++)
            {
                if (revent[i].data.fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    printf("key:%s\n", buf);
                }
                else if (revent[i].data.fd == sockfd)
                {
                    // 4.阻塞等待客户端连接accept .返回通信文件描述符
                    int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
                    if (acceptfd < 0)
                    {
                        perror("accept err.");
                        return -1;
                    }
                    printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
                    printf("client:ip=%s port=%d\n", inet_ntoa(caddr.sin_addr),
                           ntohs(caddr.sin_port)); //inet_ntoa
                    //将acceptfd添加到表树上
                    event.data.fd = acceptfd;
                    event.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &event);
                }
                else
                {
                    int recvbyte = recv(revent[i].data.fd, buf, sizeof(buf), 0);
                    if (recvbyte < 0)
                    {
                        perror("recv err.");
                        return -1;
                    }
                    else if (recvbyte == 0)
                    {
                        printf("%d client exit\n", revent[i].data.fd);
                        close(revent[i].data.fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, revent[i].data.fd, NULL);
                    }
                    else
                    {
                        printf("%d :%s\n", revent[i].data.fd, buf);
                    }
                }
            }
        }
        close(sockfd);
        return 0;
    }
    

    3.3 利用setsockopt属性设置

    Linux中socket属性

    选项名称        说明                  数据类型 
    ======================================================================== 
    SOL_SOCKET  应用层
    ------------------------------------------------------------------------ 
    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_SNDLOWAT      发送缓冲区下限              int 
    SO_RCVTIMEO      接收超时                 struct timeval 
    SO_SNDTIMEO      发送超时                 struct timeval 
    SO_REUSEADDR      允许重用本地地址和端口          int 
    SO_TYPE        获得套接字类型              int 
    SO_BSDCOMPAT     与BSD系统兼容               int 
    ==========================================================================             
             IPPROTO_IP  IP层/网络层
    ----------------------------------------------------------------------------
    IP_HDRINCL       在数据包中包含IP首部          int 
    IP_OPTINOS       IP首部选项               int 
    IP_TOS          服务类型 
    IP_TTL         生存时间                int 
    IP_ADD_MEMBERSHIP       将指定的IP加入多播组                    struct ip_mreq
    ==========================================================================            
    		IPPRO_TCP  传输层
    -----------------------------------------------------------------------------
    TCP_MAXSEG       TCP最大数据段的大小            int 
    TCP_NODELAY       不使用Nagle算法             int  
    

    API接口

    int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen)
    int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)
    
    功能获得/设置套接字属性
    参数
    	sockfd套接字描述符
    	level协议层
    		SOL_SOCKET应用层
    		IPPROTO_TCP传输层
    		IPPROTO_IP网络层 
    	optname选项名
    		SO_BROADCAST     允许发送广播数据             int 
    		SO_RCVBUF       接收缓冲区大小              int 
    		SO_SNDBUF       发送缓冲区大小              int 
    		SO_RCVTIMEO      接收超时                struct timeval 
    		SO_SNDTIMEO      发送超时                 struct timeval
    		
    	optval:选项值
    	optlen:选项值大小指针
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char const *argv[])
    {
        if (argc != 2)
        {
            printf("please input %s <port>\n", argv[0]);
            return -1;
        }
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //ipv4
        struct sockaddr_in saddr,caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
    
        //设置端口重用
        int optval=1;
        setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
            printf("client:ip=%s port=%d\n",inet_ntoa(caddr.sin_addr),\
            ntohs(caddr.sin_port));//inet_ntoa
            // 5.收发消息
            char buf[64];
            int recvbyte;
            while (1)
            {
                //设置接收超时
                struct timeval tv={2,0};
                setsockopt(acceptfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
    
                recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                   //  return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("client exit.\n");
                    break;
                }
                else
                {
                    printf("buf:%s\n", buf);
                }
            }
            // 6.关闭套接字
           close(acceptfd);
        }
        close(sockfd);
        return 0;
    }
    

    设置超时检测操作

    struct timeval {
         long    tv_sec;         /*秒*/
         long    tv_usec;        /*微秒*/
     };
    //设置接收超时
     struct timeval tm={2,0};    
     setsockopt(acceptfd,SOL_SOCKET,SO_RCVTIMEO,&tm,sizeof(tm));
     //设置超时之后时间到打断接下来的阻塞在这个文件描述符的函数直接错误返回
    
    补充
    //设置端口和地址重用
    int optval=1;  
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval));
    

    3.4 alarm定时器设置

    alarm(5) 闹钟   定时器
    //5秒之后会会有一个信号产生(SIGALRM)
    int sigaction(int signum, const struct sigaction *act,
                         struct sigaction *oldact);
     功能对接收到的指定信号处理
    	signum  信号		 
        struct sigaction {
                   void     (*sa_handler)(int);
         };
         
        //设置信号属性
        struct sigaction act;
        sigaction(SIGALRM,NULL,&act);//获取原属性
        act.sa_handler=handler;//修改属性
        sigaction(SIGALRM,&act,NULL);//将修改的属性设置回去
       
    注在recv前调用alarm函数
       alarm的 SIGALRM信号产生后会打断(终端)下面的系统调用recv;
       打断后相当于recv错误返回。
    
    #include <stdio.h>
    #include <signal.h>
    
    void handler(int sig)
    {
        printf("time out -----------------\n");
    }
    
    int main(int argc, char const *argv[])
    {
        //SIGALRM
        struct sigaction act;
        //1.获取原属性
        sigaction(SIGALRM,NULL,&act);
        //2.修改
        act.sa_handler=handler;
        //3.设置
        sigaction(SIGALRM,&act,NULL);
    
        char buf[32];
        while(1)
        {
            alarm(2);
            if(fgets(buf,sizeof(buf),stdin)== NULL)
            {
                perror("fgets err.");
            }
            printf("buf:%s\n",buf);
        }
        return 0;
    }
    

    六、广播、组播、本地套接字通信

    1. 广播

    1.1 理论

    • 前面介绍的数据包发送方式只有一个接受方称为单播
    • 如果同时发给局域网中的所有主机称为广播
    • 只有用户数据报(使用UDP协议)套接字才能广播
    • 一般被设计成局域网搜索协议
    • 广播地址
      • 以192.168.1.0 (255.255.255.0) 网段为例最大的主机地址192.168.1.255代表该网段的广播地址
      • 发到该地址的数据包被所有的主机接收

    1.2 广播发送流程

    1. 创建用户数据报套接字
    2. 缺省创建的套接字不允许广播数据包需要设置属性setsockopt
    3. 接收方地址指定为广播地址
    4. 指定端口信息
    5. 发送数据包
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    int main(int argc, char const *argv[])
    {
        //1.创建数据报套接字
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //2.设置发送广播属性
        int optval = 1;
        setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
        //3.填充广播IP和端口
        struct sockaddr_in saddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[2]));
        saddr.sin_addr.s_addr = inet_addr(argv[1]);
    
        char buf[128];
        //4.发送广播消息
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            if (buf[strlen(buf) - 1] == '\n')
                buf[strlen(buf) - 1] = '\0';
            sendto(sockfd, buf, sizeof(buf), 0,
                   (struct sockaddr *)&saddr, sizeof(saddr));
        }
        close(sockfd);
        return 0;
    }
    

    1.3 广播接收 流程

    1. 创建用户数据报套接字
    2. 绑定IP地址(广播IP或0.0.0.0)和端口
    3. 绑定的端口必须和发送方指定的端口相同
    4. 等待接收数据
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    int main(int argc, char const *argv[])
    {
        //1.创建数据报套接字
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
    
        //2.填充广播IP和端口
        struct sockaddr_in saddr, caddr;
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(atoi(argv[1]));
        saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
        socklen_t len = sizeof(caddr);
        //3.绑定
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
        char buf[128];
        //4.收广播消息
        while (1)
        {
            if (recvfrom(sockfd, buf, sizeof(buf), 0,
                         (struct sockaddr *)&caddr, &len) < 0)
            {
                perror("recvfrom err.");
                return -1;
            }
            printf("ip=%s port=%d  %s\n", inet_ntoa(caddr.sin_addr),
                   ntohs(caddr.sin_port), buf);
        }
        close(sockfd);
        return 0;
    }
    

    2. 组播

    2.1 理论

    • 单播方式只能发给一个接收方。
    • 广播方式发给所有的主机。过多的广播会大量占用网络带宽造成广播风暴影响正常的通信。
    • 组播是一个人发送加入到多播组的人接收数据。
    • 多播方式既可以发给多个主机又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

    2.2 组播地址 

    不分网络地址和主机地址第1字节的前4位固定为1110  。是D类IP

    224.0.0.1 – 239.255.255.255

    2.3 组播发送

    1. 创建用户数据报套接字
    2. 接收方地址指定为组播地址
    3. 指定端口信息
    4. 发送数据包
      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/socket.h>
      #include <sys/socket.h>
      #include <netinet/in.h>
      #include <netinet/ip.h>
      #include <unistd.h>
      #include <stdlib.h>
      #include <string.h>
      #include <arpa/inet.h>
      
      int main(int argc, char const *argv[])
      {
          int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
          if (sockfd < 0)
          {
              perror("socket err.");
              return -1;
          }
          //填充结构体 组ip
          struct sockaddr_in gaddr;
          gaddr.sin_family = AF_INET;
          gaddr.sin_port = htons(atoi(argv[2]));
          gaddr.sin_addr.s_addr = inet_addr(argv[1]); //组ip
      
          char buf[128];
          while (1)
          {
              fgets(buf, sizeof(buf), stdin);
              sendto(sockfd,buf,sizeof(buf),0,\
              (struct sockaddr *)&gaddr,sizeof(gaddr));
          }
          close(sockfd);
          return 0;
      }
      

    2.4 组播接收

    1. 创建用户数据报套接字
    2. 加入多播组
    3. 绑定IP地址(加入组的组IP或0.0.0.0)和端口
    4. 等待接收数据
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    
    int main(int argc, char const *argv[])
    {
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        //将ip地址加入多播组
        struct ip_mreq mreq;
        mreq.imr_multiaddr.s_addr=inet_addr(argv[1]); 
        mreq.imr_interface.s_addr=inet_addr("0.0.0.0"); 
    
        setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
    
        //填充结构体 组ip
        struct sockaddr_in gaddr;
        gaddr.sin_family = AF_INET;
        gaddr.sin_port = htons(atoi(argv[2]));
        gaddr.sin_addr.s_addr = inet_addr(argv[1]); //组ip
    
        //绑定
        if(bind(sockfd,(struct sockaddr *)&gaddr,sizeof(gaddr))<0)
        {
            perror("bind err.");
            return -1;
        }
    
        char buf[128];
        while (1)
        {
            recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
            printf("buf:%s\n",buf);
        }
        close(sockfd);
        return 0;
    }
    

    加入多播组核心代码

    struct ip_mreq
    {
    	struct  in_addr  imr_multiaddr;   /* 指定多播组IP */
    	struct  in_addr  imr_interface;   /* 本地网卡地址通常指定为 INADDR_ANY--0.0.0.0*/};
    }
    struct ip_mreq mreq;
    bzero(&mreq, sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
    mreq.imr_interface.s_addr = INADDR_ANY;
    setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
    

    3. 本地套接字通信

    3.1 特性

    • socket同样可以用于本地通信
    • 创建套接字时使用本地协议AF_UNIX(或AF_LOCAL)。
    • 分为流式套接字和用户数据报套接字
    • 和其他进程间通信方式相比使用方便、效率更高
    • 常用于前后台进程通信

    3.2 核心代码

    #include 
    #include 
    
    unix_socket = socket(AF_UNIX, type, 0);
    
    struct sockaddr_un {
       sa_family_t sun_family;               /* AF_UNIX */
       char        sun_path[UNIX_PATH_MAX];  /* 本地路径 */
    };
    
    struct sockaddr_un myaddr;
    bzero(&myaddr,  sizeof(myaddr));
    myaddr.sun_family = AF_UNIX; 
    strcpy(myaddr.sun_path,  "mysocket");  //可以指定路径
    

    3.3 代码实现

    server.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <string.h>
    
    int main(int argc, char const *argv[])
    {
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        struct sockaddr_un saddr;
        saddr.sun_family = AF_UNIX;
        strcpy(saddr.sun_path, "./myunix");
    
      //  system("rm ./myunix -f");
        unlink("./myunix");
    
        // 2.绑定(填充通信结构体)bind
        if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        // 3.监听。主动套接字变为被动套接字listen
        if (listen(sockfd, 5) < 0)
        {
            perror("listen err.");
            return -1;
        }
        while (1)
        {
            // 4.阻塞等待客户端连接accept .返回通信文件描述符
            int acceptfd = accept(sockfd, NULL, NULL);
            if (acceptfd < 0)
            {
                perror("accept err.");
                return -1;
            }
            printf("sockfd:%d acceptfd:%d\n", sockfd, acceptfd);
    
            // 5.收发消息
            char buf[64];
            int recvbyte;
            while (1)
            {
                recvbyte = recv(acceptfd, buf, sizeof(buf), 0);
                if (recvbyte < 0)
                {
                    perror("recv err.");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    printf("client exit.\n");
                    break;
                }
                else
                {
                    printf("buf:%s\n", buf);
                }
            }
            // 6.关闭套接字
            close(acceptfd);
        }
        close(sockfd);
        return 0;
    }
    

    client.c代码

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    int main(int argc, char const *argv[])
    {
        // 1.创建流式套接字socket .返回连接文件描述符
        int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            perror("socket err.");
            return -1;
        }
        struct sockaddr_un saddr;
        saddr.sun_family = AF_UNIX;
        strcpy(saddr.sun_path, "./myunix");
    
        if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
        {
            perror("bind err.");
            return -1;
        }
    
        char buf[64];
        while (1)
        {
            fgets(buf, sizeof(buf), stdin); //10  20
            //最多读size-1,自动补'\0',读到'\n'
            if (buf[strlen(buf) - 1] == '\n')
                buf[strlen(buf) - 1] = '\0';
            send(sockfd, buf, sizeof(buf), 0);
        }
        // 6.关闭套接字
        close(sockfd);
        return 0;
    }
    

    七、网络协议头分析了解

    1. 数据的封装与传递过程

    思考

    1. 应用层调用send后是如何把数据发送到另一台机器的某个进程的。
    2. 接收的设备收到数据包后如何处理给应用层

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

    2. 以太网帧完整帧格式

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

    粘包、拆包发生原因: 1000 - 800+200 400  200   
        发生TCP粘包或拆包有很多原因常见的几点 600   1000   400
       1、要发送的数据大于TCP发送缓冲区剩余空间大小将会发生拆包。 
       2、待发送数据大于MSS最大报文长度TCP在传输前将进行拆包。 
       3、要发送的数据小于TCP发送缓冲区的大小TCP将多次写入缓冲区的数据一次发送出去将会发生粘包。 
       4、接收数据端的应用层没有及时读取接收缓冲区中的数据将发生粘包。
       
    粘包、拆包解决办法: 
       解决问题的关键在于如何给每个数据包添加边界信息常用的方法有如下
       1、发送端给每个数据包添加包首部首部中应该至少包含数据包的长度这样接收端在接收到数据后通过读取包首部的长度字段便知道每一个数据包的实际长度了。 
       2、发送端将每个数据包封装为固定长度不够的可以通过补0填充这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。 
       3、可以在数据包之间设置边界如添加特殊符号这样接收端通过这个边界就可以将不同的数据包拆分开。 等等。
       4.延时、效率低
    

    tcp粘包与udp丢包的原因及解决

       https://www.cnblogs.com/111testing/p/12810253.html 

    3. 以太网头部

    4. IP头

    【腾讯文档】IP数据包的格式

    IP数据包的格式

    5. TCP头

    6. UDP头

    【腾讯文档】TCP数据包格式 

    TCP数据包格式 

    7. 三次握手和四次挥手TCP*

    7.1 三次握手

    在TCP/IP协议中TCP协议提供可靠的连接服务采用三次握手建立一个连接。

    服务器必须准备好接受外来的连接。这通过调用socket、 bind和listen函数来完成称为被动打开(passive open)。

    第一次握手客户通过调用connect进行主动打开(active open)。这引起客户TCP发送一个SYN表示同步分节SYN=J它告诉服务器客户将在连接中发送到数据的初始序列号。并进入SYN_SEND状态等待服务器的确认。

    第二次握手服务器必须确认客户的SYN同时自己也得发送一个SYN分节它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个字节向客户发送SYN和对客户SYN的ACK表示确认此时服务器进入SYN_RECV状态。

    第三次握手客户收到服务器的SYN+ACK。向服务器发送确认分节此分节发送完毕客户服务器进入ESTABLISHED状态完成三次握手。

    辅助了解

     

    客户端的初始序列号为J而服务器的初始序列号为K。在ACK里的确认号为发送这个ACK的一端所期待的下一个序列号。因为SYN只占一个字节的序列号空间所以每一个SYN的ACK中的确认号都是相应的初始序列号加1.类似地每一个FIN表示结束的ACK中的确认号为FIN的序列号加1.

    完成三次握手客户端与服务器开始传送数据在上述过程中还有一些重要概念。

    未连接队列在三次握手协议中服务器维护一个未连接队列该队列为每个客户端的SYN包(syn=j)开设一个条目该条目表明服务器已收到SYN包并向客户发出确认正在等待客户端确认包。这些条目所标识的连接在服务器处于SYN_RECV状态当服务器收到客户端确认包时删除该条目服务器进入ESTABLISHED状态。

    7.2 四次挥手

    TCP连接终止需四个分节。

     

    第二次握手接收到FIN的另一端执行被动关闭passive close。这个FIN由TCP确认。它的接收也作为文件结束符传递给接收端应用进程放在已排队等候应用进程接收到任何其他数据之后

    第三次握手一段时间后接收到文件结束符的应用进程将调用close关闭它的套接口。这导致它的TCP也发送一个FIN。

    第四次握手接收到这个FIN的原发送端TCP对它进行确认。

     第一次握手某个应用进程首先调用close我们称这一端执行主动关闭。这一端的TCP于是发送一个FIN分节表示数据发送完毕。

    【腾讯文档】TCP握手挥手的过程分析

    TCP握手挥手的过程分析

    8. wireshark抓包工具

    简单使用过程

    	    1. 安装  sudo apt-get update
    		sudo apt-get install wireshark
    		2. 运行
    			sudo wireshark
    		3. 过滤
    			tcp.port == 8888
    		4. 抓的是流经eth0网卡的数据
    			服务器端代码运行在ubuntu
    			客户端代码运行在windows下
    			
    		ip.addr == 192.168.1.31
    
    注抓包的过程就是抓网卡流经的一些数据。启动时不加sudo找不到网卡没有办法找到内容。
     如何抓包1.启动wireshark 。//filter-》过滤器
    	    2.想抓流经eth0网卡的数据就点击一下eth0.
    		3.想找到我想抓的数据需要用到filter
        //在这之前需要将ubuntu的ip修改固定ip和windows在同一网段。
    		4.在filter输入tcp port==8888回车查找端口号为8888的流经的数据。
    		//通过端口号进行的过滤也可通过ip(通过Expression按键可以查看过滤方式)
    

    八、数据库编程

    1. 数据库简介

    常用的数据库
    大型数据库 Oracle
    
    中型数据库 Server是微软开发的数据库产品主要支持windows平台 
    小型数据库 : mySQL是一个小型关系型数据库管理系统。开放源码 
    
    SQLite基础
     SQLite的源代码是C其源代码完全开放。它是一个轻量级的嵌入式数据库。
     SQLite有以下特性 
         	零配置一无需安装和管理配置 
         	储存在单一磁盘文件中的一个完整的数据库 
         	数据库文件可以在不同字节顺序的机器间自由共享 
         	支持数据库大小至2TB1024= 1TB足够小全部源码大致3万行c代码250KB 
            比目前流行的大多数数据库对数据的操作要快
    
    创建SQLite数据库
    手工创建 
         使用sqlite3工具通过手工输入SQL命令行完成数据库创建. 
         用户在Linux的命令行界面中输入sqlite3可启动sqlite3工具 
    代码创建 
    在代码中常动态创建数据库 在程序运行过程中当需要进行数据库操作时应用程序会首先尝试打开数据库此时如果数据库并不存在程序则会自动建立数据库然后再打开数据库 
    
    

    2. 虚拟中sqlite3安装

    sqlite3 安装
    1. sudo dpkg -*.deb  离线安装		
    2. 在线安装
     1、设置能够上网
     2、更新更新源
    	#apt-get update
    		
     3、安装软件及开发环境
       # apt-get install sqlite3		--->sqlite3数据库软件
       # apt-get install libsqlite3-dev	--->sqlite3数据库开发支持库
       # apt-get install sqlite3-doc		--->sqlite3数据库说明文档
    	--------------------------------		
       #apt-get install sqlitebrowser		
    			--->sqlite3数据库操作软件
    

    源码安装

    tar xf sqlite-autoconf-3140100.tar.gz
    ./configure
    make
    sudo make install
    

    安装完成后可以使用sqlite3 -version命令来测试是否安装成功

    $ sqlite3 -version 
    3.14.1 2016-08-11
    

    3. 基础SQL语句使用

    【腾讯文档】sqlite基础SQL语句使用

    sqlite基础SQL语句使用

    4. sqlite使用入门

    数据库 · 华清远见教学空间

    5. sqlite3编程

    API接口文档

    官方文档List Of SQLite Functions

    中文文档SQLite 命令 - SQLite 中文版 - UDN开源文档

    头文件#include <sqlite3.h>
    编译gcc sqlite1.-lsqlite3
    
    1.int sqlite3_open(char  *path, sqlite3 **db);
    
    功能打开sqlite数据库如果数据库不存在则创建它
    path 数据库文件路径
    db 指向sqlite句柄的指针
    返回值成功返回SQLITE_OK失败返回错误码(非零值)
    
    2.int sqlite3_close(sqlite3 *db);
    
    功能关闭sqlite数据库
    返回值成功返回SQLITE_OK失败返回错误码
    
    返回值返回错误信息
    
    3.执行sql语句接口
    int sqlite3_exec(
      sqlite3 *db,                                  /* An open database */
      const char *sql,                           /* SQL to be evaluated */
      int (*callback)(void*,int,char**,char**),  /* Callback function */
      void *arg,                      /* 1st argument to callback */
      char **errmsg                              /* Error msg written here */
    );
    
    功能执行SQL操作
    db数据库句柄
    sql要执行SQL语句
    callback回调函数(满足一次条件调用一次函数用于查询)
        再调用查询sql语句的时候使用回调函数打印查询到的数据
    arg:传递给回调函数的参数
    errmsg错误信息指针的地址
    返回值成功返回SQLITE_OK失败返回错误码
    
    回调函数
    typedef int (*sqlite3_callback)(void *para, int f_num, 
             char **f_value, char **f_name);
    
    功能select:每找到一条记录自动执行一次回调函数
    para传递给回调函数的参数由 sqlite3_exec() 的第四个参数传递而来
    f_num记录中包含的字段数目
    f_value包含每个字段值的指针数组列值
    f_name包含每个字段名称的指针数组列名
    返回值成功返回SQLITE_OK失败返回-1每次回调必须返回0后才能继续下次回调
    
    4.不使用回调函数执行SQL语句(只用于查询)
    int sqlite3_get_table(sqlite3 *db, const  char  *sql, 
       char ***resultp,  int *nrow,  int *ncolumn, char **errmsg);
    
    功能执行SQL操作
    db数据库句柄
    sqlSQL语句
    resultp用来指向sql执行结果的指针
    nrow满足条件的记录的数目(但是不包含字段名(表头 id name score))
    ncolumn每条记录包含的字段数目
    errmsg错误信息指针的地址
    
    返回值成功返回SQLITE_OK失败返回错误码
    
    5.返回sqlite3定义的错误信息
    char *sqlite3_errmsg(sqlite3 *db);
    
    #include <stdio.h>
    #include <sqlite3.h>
    
    int callback(void *arg, int f_num, char **f_value, char **f_name)
    {
        printf("%s\n", (char *)arg);
        for (int i = 0; i < f_num; i++)
        {
            printf("%s ", f_name[i]);
        }
        putchar(10);
        for (int i = 0; i < f_num; i++)
        {
            printf("%s ", f_value[i]);
        }
        putchar(10);
        return 0;
    }
    int main(int argc, char const *argv[])
    {
        //1.打开或新建一个数据库
        sqlite3 *db;
        if (sqlite3_open("./stu.db", &db) != 0)
        {
            fprintf(stderr, "sqlite3_open:%s", sqlite3_errmsg(db));
            return -1;
        }
        printf("open ok.\n");
        //2.创建表
        char *errmsg = NULL;
        if (sqlite3_exec(db, "create table stu1(id int primary key,name char,score float);",
                         NULL, NULL, &errmsg) != 0)
        {
            fprintf(stderr, "create table err:%s", errmsg);
            //  return -1;
        }
        printf("create table ok.\n");
        //3.向表中插入数据
        int id, num;
        char name[32];
        float score;
        char sql[128];
        printf("please input student number:");
        scanf("%d", &num);
        for (int i = 0; i < num; i++)
        {
            scanf("%d %s %f", &id, name, &score);
            sprintf(sql, "insert into stu1 values(%d,\"%s\",%f);", id, name, score);
            if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
            {
                fprintf(stderr, "insert err:%s", errmsg);
                return -1;
            }
        }
        //4.查询表
        // if (sqlite3_exec(db, "select id,score from stu1 where id=1;", callback, "hello", &errmsg) != 0)
        // {
        //     fprintf(stderr, "select err:%s", errmsg);
        //     return -1;
        // }
    
        //只用于查询的函数 sqlite3_get_table
        char **rstp = NULL;
        int hang, lie;
        if (sqlite3_get_table(db, "select * from stu1 where id=1;", &rstp, &hang, &lie, &errmsg) != 0)
        {
            fprintf(stderr, "select err:%s", errmsg);
            return -1;
        }
        int k = 0;
        for (int i = 0; i < hang + 1; i++)
        {
            for (int j = 0; j < lie; j++)
            {
                printf("%s ", rstp[k++]);
            }
            putchar(10);
        }
    
        //5.关闭数据库
        sqlite3_close(db);
        return 0;
    }
    

    callback函数的使用

    #include <stdio.h>
    //(a+b) * c
    //(a-b) * c
    //(a*b) * c
    //(a/b) * c
    //a+b a-b a*b a/b
    
    int add(int a,int b)
    {
    	return a+b;
    }
    int sub(int a,int b)
    {
    	return a-b;
    }
    int mul(int a,int b)
    {
    	return a*b;
    }
    int chu(int a,int b)
    {
    	return a/b;
    }
    //int (* fun_p)(int,int);
    //fun_p=add;    fun_p = mul;
    int fun(int c,int (*fun_p)(int ,int ), int a,int b)
    {
    	return fun_p(a,b)*c;
    }
    //想通过一个函数更改一个变量的值
    //两种- 参数(传变量地址)返回值
    #if 0
    int func(void)
    {
    	return 1000;
    }
    void func1(int *sp)
    {
    	*sp=1000;
    }
    #endif
    
    int *func(void)
    {
    	static int a=1000;
    	return &a;
    }
    void func1(int **sp)
    {
    	static int a=1000;
    	*sp=&a;
    }
    
    int main(int argc, const char *argv[])
    {
        printf("%d\n",fun(10,add,2,3));
        printf("%d\n",fun(10,sub,2,3));
        printf("%d\n",fun(10,mul,2,3));
        printf("%d\n",fun(10,chu,2,3));
    
     //   int a;//1000;
    //	a=fun();//fun1(&a);
    
    	int *p;
    	int **q;
    
    
    	return 0;
    }
    

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6