Linux网络


*************基本概念***************
【1】计算机与网络发展的7个阶段
    1.    批处理(20世纪50年代)
        是指实现将用户每个数据装入卡带或者磁带。并有计算机按照一定的顺序读取,
        是用户索要执行的这些程序和数据能够一并批量得到处理的方式。
    2.    分时系统(20世纪60年代)
        是指多个终端(包含鼠标、键盘、显示器等输入输出设备组成,最初还包括打
        印机)与一台计算机连接,允许多个用户同时使用一台计算机的系统。
        特性:多路性、独占性、交互性和及时性。
    3.计算机之间的通信(20世纪70年代)
    4.    计算机网络的产生(20世纪80年代)
    5.    互联网的普及(20世纪90年代)
    6.    以互联网技术为中心的时代(2000年)
    7.    从“单纯建立连接”到“安全建立连接”(2010年)
【2】网络体系结构即指网络的层次结构和每层所使用协议的集合
【3】
    1. 协议
        一组控制数据通信的规则。
        三要素:语法(包括数据格式、编码及信号电平等)、
                语义(包括用于协议和差错处理的控制信息)、
                时序(包括速度匹配和排序)。
    2. 标准
        一致同意的规则。
        分类:
        事实上的标准:实际情况或者习惯
        合法标准:法律或者规章制度
    3. 标准化组织
        缓慢发展:
            ISO:国际标准化组织
            ITU-T:国际电联-电信标准部
            ANSI:美国国家标准化局
            IEEE:电气电子工程师协会(主要是以太网、局域网方面的)
            EIA:电子工业协会(物理传输标准、光钎传输)
        快速发展:
            论坛:帧中继论坛、ATM论坛
            
        管理机构:FCC 联邦通信委员会

        Internet标准:RFC
        
【4】OSI开放系统互联模型
    OSI模型是一个理想化的模型,尚未有完整的实现

    应用层    应用程序:FTP、E-mail、Telnet
    表示层    数据格式定义、数据转换/加密
    会话层    建立通信进程的逻辑名字与物理名字之间的联系
    传输层    差错处理/恢复,流量控制,提供可靠的数据传输
    网络层    数据分组、路由选择
    数据链路层  数据组成可发送、接收的帧
    物理层    传输物理信号、接口、信号形式、速率
    
【5】7层通信
    (1)应用层:指定特定应用的协议(比如发送和接受文件的软件按钮,发送者输入“早上好”并附上收件人,按下发送按钮,接受者收到信息会将其存储在硬盘或者非易失存储器(数据不会因为断电而丢失的一种存储设备)上,这些都是在应用层上的)
    (2)表示层:设备固有数据格式和网络标准数据格式的转换(接受者和发送者如果使用的邮件客户端不一样,那么就会出现问题,如何实现用户之间的通信,那么就需要在表示层来起作用,使得在不同的客户端上拥有相同的网络格式)
    (3)会话层:通信管理,负责建立或者断开通信连接(发送者一次性发送5份邮件,那么接受者如何接受,是一次性接受所有的文件然后断开连接还是没接受一次就断开,然后在此进行,发送者同理)
    (4)传输层:管理两个节点(互联的网络中断)之间的数据传输。负责可靠传输(确保数据被可靠地传送到目标地址)(确保发送者和接受者之间的通信,会话层负责决定建立连接和断开连接的时机,而传输层进行实际的建立和断开处理)
    (5)网络层:地址管理与路由选择,作用:在网络相互连接的环境中,将数据从发送端主机发送到接受端主机
    (6)数据链路层:互连设备之间传送和识别数据帧
    (7)物理层:以“0”、“1”代表的电压的高低、灯光的闪灭。界定连接器和网络的规格。
【6】TCP/IP协议:传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议
    TCP/IP协议是Internet事实上的工业标准。
    传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议

    应用层  TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet

    传输层    TCP,UDP

    网络层    IP,ICMP,RIP,OSPF,BGP,IGMP

    物理层    SLIP,CSLIP,PPP,ARP,RARP,MTU  ISO2110,IEEE802.1,EEE802.2

    TCP(Transport Control Protocol)传输控制协议
    UDP(User Datagram Protocol)用户数据报协议
    IP(Internetworking Protocol)网间协议
    HTTP(Hypertext Transfer Protocol) 超文本传输协议
    SMTP(Simple Mail Transfer Protocol)简单邮件传输协议
    
【7】UDP和TCP
    共同点:同为传输层协议
    不同点:
        TCP:有连接,可靠
        UDP:无连接,不保证可靠
    
    TCP:(全双工)
        TCP(即传输控制协议):是一种面向连接的传输层协议,
        它能提供高可靠性通信(即数据无误、数据无丢失、数据
        无失序、数据无重复到达的通信)
        适用情况:
            适合于对传输质量要求较高,以及传输大量数据的通信。
            在需要可靠数据传输的场合,通常使用TCP协议
            MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
            
            TCP“三次握手”
            
    UDP:(全双工)
        UDP(User Datagram Protocol)用户数据报协议,是不可靠
        的无连接的协议。在数据发送前,因为不需要进行连接,所以
        可以进行高效率的数据传输。
        适用情况:
            发送小尺寸数据(如对DNS服务器进行IP地址查询时)
            在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
            适合于广播/组播式通信中。
            MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
            流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
【8】socket
    是一个编程接口
    是一种特殊的文件描述符 (everything in Unix is a file)
    并不仅限于TCP/IP协议
    面向连接 (Transmission Control Protocol - TCP/IP)、
    无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)
    
    在OSI模型中,主要位于会话层和传输层之间

    类型:
        流式套接字(SOCK_STREAM)
            提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
        --------> TCP协议
        数据报套接字(SOCK_DGRAM)
            提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
        --------> UDP协议
        原始套接字(SOCK_RAW)
            可以对较低层次协议如IP、ICMP直接访问。    
【9】IP地址
    IP地址是Internet中主机的唯一标识
    Internet中的主机要与别的机器通信必须具有一个IP地址
    IP地址为32位(IPv4)或者128位(IPv6)
    每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由

    ipv4表示形式:点分十进制形式,如202.38.64.10,最后都会转换为一个32位的无符号整数。

    IP地址分类(基于ipv4地址前八位来区分)
    A类  0000 0000 - 0111 1111     0.x.x.x - 127.x.x.x
    B类  1000 0000 - 1011 1111     128.x.x.x - 191.x.x.x
    C类  1100 0000 - 1101 1111  192.x.x.x - 223.x.x.x
    D类  1110 0000 - 1110 1111     224.x.x.x - 239.x.x.x   表示组播地址
    E类  1111 0000 - 1111 1111  240.x.x.x - 255.x.x.x   属于保留测试

    127.x.x.x 表示主机地址
    
    子网掩码:表示链接的主机最大数
    A类 255.0.0.0
    B类 255.255.0.0
    C类 255.255.255.0
    
    192.168.2.x
        192.168.2.1 表示网关
        192.168.2.255 表示该网段下的广播地址
    
    #include <arpa/inet.h>

    将点分十进制ip地址转化为网络字节序的整型数据
    in_addr_t inet_addr(const char *cp);
    
    将网络字节序的整型数据转化为点分十进制ip地址
    char *inet_ntoa(struct in_addr in);

    例子:
        inet_addr("192.168.2.189");
【10】端口号(vi /etc/services 查看端口号)
    为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
    众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
    已登记端口:1024~49151
    动态或私有端口:49152~65535

    一般使用:8888 9999 10000 10001
【11】字节序    
    不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
    小端序(little-endian) - 低序字节存储在低地址
    将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
    大端序(big-endian)- 高序字节存储在低地址
    将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用
        
    如何测试主机字节序:
        方法1:使用指针
        方法2:使用file命令,file a.out 其中LSB的L代表小端存储
        方法3:共用体
    
    网络中传输的数据必须按网络字节序,即大端字节序
    
    #include <arpa/inet.h>
    
    将主机字节序转化为网络字节序
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    
    将网络字节序转化为主机字节序
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);

    例子:
        htons(9999);
    
****************TCP网络编程******************
【1】流程
        举个例子:
            买个手机、买张卡
            手机和卡必须匹配
            将卡与手机绑定
            设置为非飞行模式
            进行通信
        服务器:server.c
            创建套接字 socket( )
            填充服务器的网络信息结构体 sockaddr_in
            将套接字与服务器的网络信息结构体绑定 bind( )
            将套接字设置为监听模式 listen( )
            阻塞等待客户端的连接请求 accept( )
            进行通信 recv( )/send( )
        客户端:client.c
            创建套接字 socket( )
            填充服务器的网络信息结构体 sockaddr_in
            发送客户端的连接请求 connect( )
            进行通信 send( )/recv( )
【2】ctags的创建
    第一步:在/usr/include 里面执行
            sudo ctags -R,生成一个tags 的索引文件
            测试:vim -t sockaddr_in
    第二步:实现全局
            在家目录下的 .vimrc 添加
            set tags+=/usr/include/tags
    使用:ctrl ] 追代码
          ctrl t 返回上一层
    

【3】socket( )
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int socket(int domain, int type, int protocol);
    功能:创建一个套接字,返回一个文件描述符
    参数:
        domain:通信域或者协议族
            AF_UNIX,AF_LOCAL 本地通信
            AF_INET ipv4网络通信
            AF_PACKET 底层通信
        type:类型
            SOCK_STREAM 流式套接字 TCP
            SOCK_DGRAM 数据报套接字 UDP
            SOCK_RAW 底层通信
        protocol:协议,一般为0
    返回值:
        成功:文件描述符
        失败:-1
    例子:
        int sockfd;
        if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("fail to socket");
            //return -1;
            exit(1);
        }
【4】bind( )
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    功能:将套接字与网络信息结构体绑定
    参数:
        sockfd:文件描述符,socket的返回值
        addr:网络信息结构体
            通用的(一般不用)
                struct sockaddr {
                    sa_family_t sa_family;
                    char        sa_data[14];
                }
            一般使用sockaddr_in
                #include <netinet/in.h>
                struct sockaddr_in
                {
                    __SOCKADDR_COMMON (sin_);
                        ==>
                            #define __SOCKADDR_COMMON(sa_prefix) \
                                    sa_family_t sa_prefix##family
                        ==>
                            在函数宏里面,##代表字符串的拼接
                            sa_family_t sin_family;  //协议族 AF_INET
                                    
                    in_port_t sin_port;  //端口号
                    
                    struct in_addr sin_addr;  
                        ==>
                            struct in_addr
                            {                                                                                                              
                                in_addr_t s_addr;  //ip地址
                            };
                                                                                                                            
                    //这个没有用。为了使得sockaddr_in与sockaddr大小一致
                    unsigned char sin_zero[sizeof (struct sockaddr) -
                            __SOCKADDR_COMMON_SIZE -
                            sizeof (in_port_t) -
                            sizeof (struct in_addr)];
                };

        addrlen:addr的大小
    返回值:
        成功:0
        失败:-1
    例子:
        struct sockaddr_in serveraddr;
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = inet_addr("192.168.2.222");
        serveraddr.sin_port = htons(9999);
        
        if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0)
        {
            perror("fail to bind");
            exit(1);
        }
【5】listen( )
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int listen(int sockfd, int backlog);
    功能:将套接字设置为监听模式
    参数:
        sockfd:文件描述符,socket的返回值
        backlog:同时响应客户端的连接的个数,一般设置为5,10
    返回值:
        成功:0
        失败:-1
    例子:
        if(listen(sockfd, 10) < 0)
        {
            perror("fail to listen");
            exit(1);
        }
【6】accept
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    功能:阻塞等待文件描述符准备就绪
    参数:
        sockfd:文件描述符,socket的返回值
        addr:网络信息结构体(自动填充的客户端的网络信息结构体)
        addrlen:addr的大小
    返回值:
        成功:新的文件描述符(用于通信)
        失败:-1
    例子:
        int acceptfd;
        struct sockaddr_in clientaddr;
        socklen_t addrlen = sizeof(clientaddr);
        if((acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen)) <0 )
        {
            perror("fail to accept");
            exit(1);
        }
【7】connect
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    功能:发送客户端的连接请求
    参数:
        sockfd:文件描述符,socket的返回值
        addr:网络信息结构体(自己填充的服务器的网络信息结构体)
        addrlen:addr的大小
    返回值:
        成功:0
        失败:-1
    例子:
        if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
        {
            perror("fail to connect");
            exit(1);
        }
【8】send( )
    #include <sys/types.h>
    #include <sys/socket.h>

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    功能:发送数据
    参数:
        sockfd:文件描述符
            服务器:accept的返回值
            客户端:socket的返回值
        buf:发送的数据
        len:buf的长度
        flags:标志位
            0 阻塞
            MSG_DONTWAIT 非阻塞
    返回值:
        成功:发送的数据的长度
        失败:-1
        
【9】recv( )        
    #include <sys/types.h>
    #include <sys/socket.h>

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    功能:接收数据
    参数:
        sockfd:文件描述符
            服务器:accept的返回值
            客户端:socket的返回值
        buf:接收的数据
        len:buf的长度
        flags:标志位
            0 阻塞
            MSG_DONTWAIT 非阻塞
    返回值:
        成功:接收的数据的长度
            0 发送端异常退出或者关闭文件描述符
        失败:-1
        
****************UDP网络编程*******************
【1】流程
    服务器:
        创建套接字 socket( )
        填充服务器的网络信息结构体 sockaddr_in
        将套接字与服务器的网络信息结构体绑定 bind( )
        进行通信 recvfrom( )/sendto( )
    客户端:
        创建套接字 socket( )
        填充服务器的网络信息结构体 sockaddr_in
        进行通信 sendto( )/recvfrom( )
        
【2】sendto( )
    #include <sys/socket.h>

    ssize_t sendto(int socket, const void *message, size_t length,
            int flags, const struct sockaddr *dest_addr, socklen_t dest_len);
    功能:发送数据
    参数:
        socket:文件描述符,socket的返回值
        message:发送的数据
        length:数据的长度
        flags:标志位,一般为0
        dest_addr:目的地址(给谁发送数据)
        dest_len:addr的大小
    返回值:
        成功:发送的数据的长度
        失败:-1
        
【3】recvfrom( )
    #include <sys/types.h>
    #include <sys/socket.h>

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
    功能:接收数据
    参数:
        sockfd:文件描述符
        buf:接收的数据
        len:数据的长度
        flags:标志位,一般为0
        src_addr:源的地址(接收谁的信息)
        addrlen:addr的长度
    返回值:
        成功:接收的数据的长度
        失败:-1    
    作业:基于tcp 的文件服务器
        功能:
            客户端可以查看服务器所在目录的文件 opendir readdir
            客户端可以下载服务器所在目录的文件
            客户端可以上传文件到服务器
    
******************day_2*******************
***********IO模型***************
【1】定义
    在UNIX/Linux下主要有4种I/O 模型:
    阻塞I/O:
        最常用、最简单、效率最低
    非阻塞I/O:
        可防止进程阻塞在I/O操作上,需要轮询
    I/O 多路复用:
        允许同时对多个I/O进行控制
    信号驱动I/O:
        一种异步通信模型
【2】阻塞I/O 模式
    阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
    缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式
    前面学习的很多读写函数在调用过程中会发生阻塞。
        读操作中的read、recv、recvfrom
        写操作中的write、send
        其他操作:accept、connect
【3】非阻塞模式I/O
    当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,
    不要这么做,请马上返回一个错误给我。”
    当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。
    应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。
    这种模式使用中不普遍。
    
    使用fcntl函数实现非阻塞IO
    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ... /* arg */ );
    功能:操作一个文件描述符
    参数:
        fd:文件描述符
        cmd:具体的命令或者选项
            F_GETFL 获取文件状态标志位
            F_SETFL 设置文件状态标志位
                O_NONBLOCK 非阻塞
        arg:可变参,根据cmd的后面括号决定,如果是void则不使用,如果为long,则需要
    返回值:    
        成功:
            F_GETFL 获取到的文件状态标志位
            F_SETFL 0
        失败:-1
    注意:对寄存器或者位的操作,一般执行读、改、写三步

    第一步:获取标志位
    int flags;
    if((flags = fcntl(0, F_GETFL)) < 0)
    {
        perror("fail to fcntl");
        exit(1);
    }
    
    第二步:修改标志位
    //flags = flags | O_NONBLOCK;
    flags |= O_NONBLOCK;
    
    第三步:将新的标志位写回去
    if(fcntl(0, F_SETFL, flags) < 0)
    {
        perror("fail to fcntl");
        exit(1);
    }
【4】多路复用I/O
    应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
    若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
    若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
    比较好的方法是使用I/O多路复用。其基本思想是:
        先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
        函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

    使用select实现IO多路复用
    #include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);
    功能:允许与一个程序可以操作多个文件描述符,阻塞等待文件描述符准备就绪,
          如果有一个或者多个文件描述符准备就绪,函数立即返回,并执行相应的IO操作
    参数:
        nfds:最大的文件描述符加1
        readfds:读文件描述符集合
        writefds:写文件描述符集合
        exceptfds:其他或者异常的文件描述符集合
        timeout:超时
            NULL 阻塞
    返回值:
        成功:准备就绪的文件描述符的个数
        失败:-1
        
    void FD_ZERO(fd_set *set);
    清空一个集合
    
    void FD_SET(int fd, fd_set *set);
    将文件描述符fd添加到集合set里面
    
    void FD_CLR(int fd, fd_set *set);
    将文件描述符fd从集合set里面移除
    
    int  FD_ISSET(int fd, fd_set *set);
    判断文件描述符fd是否在集合set里面
    
***************服务器模型*********************
【1】定义
    在网络程序里面,通常都是一个服务器处理多个客户机。

    为了处理多个客户机的请求, 服务器端的程序有不同的处理方式。

    目前最常用的服务器模型.
        循环服务器:
            循环服务器在同一个时刻只能响应一个客户端的请求
            TCP循环服务器
            UDP循环服务器
        并发服务器:
            并发服务器在同一个时刻可以响应多个客户端的请求
            TCP并发服务器
            UDP并发服务器
【2】如何实现tcp并发服务器
    方法1:使用父子进程实现tcp并发服务器
    socket()
    sockaddr_in
    bind()
    listen()
    //如何处理僵尸进程
    while(1)
    {
        accept()
        pid = fork;
        if(pid > 0)  //父进程负责连接
        {
            
        }
        else if(pid == 0) //子进程负责通信
        {
            while(1)
            {
                recv()/send()
            }
        }
    }
    
    方法2:使用select函数实现tcp并发服务器
    #include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);
    功能:允许与一个程序可以操作多个文件描述符,阻塞等待文件描述符准备就绪,
          如果有一个或者多个文件描述符准备就绪,函数立即返回,并执行相应的IO操作
    参数:
        nfds:最大的文件描述符加1
        readfds:读文件描述符集合
        writefds:写文件描述符集合
        exceptfds:其他或者异常的文件描述符集合
        timeout:超时
            NULL 阻塞
    返回值:
        成功:准备就绪的文件描述符的个数
        失败:-1
        
    void FD_ZERO(fd_set *set);
    清空一个集合
    
    void FD_SET(int fd, fd_set *set);
    将文件描述符fd添加到集合set里面
    
    void FD_CLR(int fd, fd_set *set);
    将文件描述符fd从集合set里面移除
    
    int  FD_ISSET(int fd, fd_set *set);
    判断文件描述符fd是否在集合set里面
    
    推荐书籍:
        tcp/ip 详解 卷一 卷二 卷三
        UNIX环境高级编程
        UNIX网络编程 卷一 卷二
        unix/linux系统编程手册
    
    作业:
        使用poll实现io多路复用,fgets、accept
        基于udp的网络聊天室
*********************day_3********************
【1】网络信息检索函数
    getsockopt( ) 获取一个套接口选项
    #include <sys/socket.h>

    int getsockopt(int socket, int level, int option_name,
            void *restrict option_value, socklen_t *restrict option_len);
    功能:获取一个套接字的选项
    参数:
        socket:文件描述符
        level:层次
            SOL_SOCKET 套接字层次
            IPPROTO_IP IP层次
            IPPROTO_TCP TCP层次
        option_name:选项的名称(SOL_SOCKET)
            SO_BROADCAST 是否允许发送广播信息
            SO_REUSEADDR 是否允许重复使用本地地址
            SO_SNDBUF 获取发送缓冲区的大小
            SO_RCVBUF 获取接收缓冲区的大小
            SO_RCVTIMEO 设置接收超时时间
            SO_SNDTIMEO 设置发送超时时间
        option_value:获取到的值
        option_len:option_value的大小
    返回值:
        成功:0
        失败:-1
**************网络超时检测***************
【1】必要性
    在网络通信中,很多操作会使得进程阻塞
    TCP套接字中的recv/accept/connect
    UDP套接字中的recvfrom
    超时检测的必要性
        避免进程在没有数据时无限制地阻塞
        当设定的时间到时,进程从原操作返回继续运行
【2】本质
    阻塞函数如果没有数据到来时会一直等待
    非阻塞即使没有数据到来,函数也会立即返回
    网络超时检测的本质是设定一定的时间,在时间到达之前如果没有数据到来,
    则一直阻塞等待,如果时间到达是还没有数据到来,则函数立即返回
【3】方法1:使用setsockopt设置网络超时检测
    #include <sys/socket.h>

    int setsockopt(int socket, int level, int option_name,
            const void *option_value, socklen_t option_len);
    功能:设置一个套接字的选项
    参数:
        socket:文件描述符
        level:层次
            SOL_SOCKET 套接字层次
        option_name:选项的名称
            SO_RCVTIMEO 设置接收超时时间
        option_value:设置的值
            struct timeval
            {
                __time_t tv_sec;        秒
                __suseconds_t tv_usec;  微秒                                                                
            };
        option_len:option_value的大小
    返回值:
        成功:0
        失败:-1
【4】方法2:使用select设置网络超时检测
    #include <sys/select.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);
    功能:允许与一个程序可以操作多个文件描述符,阻塞等待文件描述符准备就绪,
          如果有一个或者多个文件描述符准备就绪,函数立即返回,并执行相应的IO操作
    参数:
        nfds:最大的文件描述符加1
        readfds:读文件描述符集合
        writefds:写文件描述符集合
        exceptfds:其他或者异常的文件描述符集合
        timeout:超时
            struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
            };
            
            >0 超时时间
            0  立即返回
            NULL 一直阻塞
    返回值:
        成功:准备就绪的文件描述符的个数
        失败:-1
        
    void FD_ZERO(fd_set *set);
    清空一个集合
    
    void FD_SET(int fd, fd_set *set);
    将文件描述符fd添加到集合set里面
    
    void FD_CLR(int fd, fd_set *set);
    将文件描述符fd从集合set里面移除
    
    int  FD_ISSET(int fd, fd_set *set);
    判断文件描述符fd是否在集合set里面
【5】使用alarm闹钟实现网络超时检测
    如果直接使用alarm,当时间到达时,会退出整个进程
    如果结合信号,当时间到达时,触发SIGALRM信号,执行信号处理函数
    当信号处理函数执行完毕之后,会接着刚才的位置继续执行,这一属性
    称之为自重启属性,如果想实现超时,需要关闭这一属性,
    
    使用sigaction函数设置信号的行为
    #include <signal.h>

    int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);
    功能:设置一个信号的行为
    参数:    
        signum:信号
        act:新的行为
        oldact:旧的行为
            struct sigaction {
                void     (*sa_handler)(int);  信号处理函数
                void     (*sa_sigaction)(int, siginfo_t *, void *); 信号处理函数
                sigset_t   sa_mask; 掩码(有关阻塞)
                int        sa_flags; 标志位
                    SA_RESTART 自重启属性
                void     (*sa_restorer)(void);  没有用
            };
    返回值:
        成功:0
        失败:-1

    注意:对寄存器或者位的操作,一般执行读、改、写三步
    
********************广播*************
【1】定义
    前面介绍的数据包发送方式只有一个接受方,称为单播
    如果同时发给局域网中的所有主机,称为广播
    只有用户数据报(使用UDP协议)套接字才能广播
    
    广播地址
    以192.168.3.0 (255.255.255.0) 网段为例,最大的主机地址192.168.3.255代表该网段的广播地址
    发到该地址的数据包被所有的主机接收
    255.255.255.255在所有网段中都代表广播地址
    
【2】流程(基于udp)
    发送者:    
        创建套接字 socket( )
        填充广播信息结构体 sockaddr_in
        设置为允许发送广播权限 setsockopt( )
        接收数据 sendto( )
        
    接收者:
        创建套接字 socket( )
        填充广播信息结构体 sockaddr_in
        将套接字与广播信息结构体绑定 bind( )
        接收数据 recvfrom( )
【3】设置为允许发送广播权限
    #include <sys/socket.h>

    int setsockopt(int socket, int level, int option_name,
            const void *option_value, socklen_t option_len);
    功能:设置一个套接字的选项
    参数:
        socket:文件描述符
        level:层次
            SOL_SOCKET 套接字层次
        option_name:选项的名称
            SO_BROADCAST 设置接收超时时间
        option_value:设置的值
            0 不允许
            1 允许
        option_len:option_value的大小
    返回值:
        成功:0
        失败:-1

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

    D类地址(组播地址)
        不分网络地址和主机地址,第1字节的前4位固定为1110
        224.0.0.1 – 239.255.255.255

    
【2】流程
    发送者:
        创建套接字 socket( )
        填充组播信息结构体 sockaddr_in
        发送数据 sendto( )
    接收者:
        创建套接字 socket( )
        填充组播信息结构体 sockaddr_in
        将套接字与组播信息结构体绑定 bind( )
        设置为加入多播组 setsockopt( )
        接收数据 recvfrom( )
        
【3】加入多播组
    #include <sys/socket.h>

    int setsockopt(int socket, int level, int option_name,
            const void *option_value, socklen_t option_len);
    功能:设置一个套接字的选项
    参数:
        socket:文件描述符
        level:层次
            IPPROTO_IP     IP层次
        option_name:选项的名称
            IP_ADD_MEMBERSHIP 加入多播组
        option_value:设置的值
            struct ip_mreq  {
                struct in_addr imr_multiaddr;   组播地址
                struct in_addr imr_interface;   本地地址
                        INADDR_ANY 任意主机地址
            };       
        option_len:option_value的大小
    返回值:
        成功:0
        失败:-1
    
    例子:
        struct ip_mreq mreq;
        mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
        
        if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
        {
            perror("fail to setsockopt");
            exit(1);
        }
    
*************使用poll实现IO多路复用****************
    实现fgets、accept两个阻塞函数
    
    #include <poll.h>

    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    功能:同select
    参数:
        fds:结构体数组
             struct pollfd {
                int   fd;         文件描述符
                
                short events;  请求的事件
                    POLLIN        普通或优先级带数据可读
                    POLLRDNORM    普通数据可读
                    POLLRDBAND    优先级带数据可读
                    POLLPRI        高优先级数据可读
                    POLLOUT        普通数据可写
                    POLLWRNORM    普通数据可写
                    POLLWRBAND    优先级带数据可写
                    POLLERR        发生错误
                    POLLHUP        发生挂起
                    POLLNVAL    描述字不是一个打开的文件
                    
                short revents;    返回的事件
            };
        nfds:文件描述符的个数
        timeout:超时
            <0    永远等待
            0    立即返回,不阻塞进程
            >0    等待指定数目的毫秒数
    返回值:
        成功:准备就绪的文件描述符的个数
        失败:-1    
    
******************本地通信********************
【1】定义
    socket同样可以用于本地通信
    创建套接字时使用本地协议PF_UNIX(或PF_LOCAL)。
    分为流式套接字和用户数据报套接字
    和其他进程间通信方式相比使用方便、效率更高
    常用于前后台进程通信

【2】本地信息结构体  sockaddr_un
    #include <sys/un.h>
    
    struct sockaddr_un                                                                                               
    {
        __SOCKADDR_COMMON (sun_);
            ==>
                #define __SOCKADDR_COMMON(sa_prefix) \                                                                           
                        sa_family_t sa_prefix##family
            ==>
                sa_family_t sun_family;  //地址族 AF_UNIX

        char sun_path[108];  路径名
    };
    
【3】TCP本地通信
    服务器:
        创建套接字 socket( )
        填充本地信息结构体 sockaddr_un
        将套接字与本地信息结构体绑定 bind( )
        将套接字设置为监听模式 listen( )
        阻塞等待客户端的连接请求 accept( )
        进行通信 recv( )/send( )
    客户端:
        创建套接字 socket( )
        填充本地信息结构体 sockaddr_un
        发送客户端的连接请求 connect( )
        进行通信 send( )/recv( )
   

posted @ 2017-08-27 09:53  专注it  阅读(345)  评论(0编辑  收藏  举报