Linux网络编程(socket通信)

TCP协议
                    客户端client                        服务端server
                创建tcp类型套接字                   创建tcp类型套接字
                绑定                                绑定
                连接服务器                          监听
                收发数据                            接受连接请求
                关闭                                收发数据
                                                    关闭
    A类地址:网络号占1个字节,主机号占3个字节,并且网络号第一个二进制必须是0,取值范围:0.xx.xx.xx到127.xx.xx.xx
            B类地址:网络号占2个字节,主机号占2个字节,并且网络号最前面两个二进制必须是10,取值范围:128.0.xx.xx到191.255.xx.xx
            C类地址:网络号占3个字节,主机号占1个字节,并且网络号最前面三个二进制必须是110,取值范围:192.0.0.xx到224.255.255.xx
            D类地址:不区分网络号和主机号,属于UDP组播专用地址,只要求前面四个二进制是1110,取值范围:224.xx.xx.xx到239.xx.xx.xx
            注意:主机号全部为0不使用,全部为1表示广播地址
            1、创建套接字                 
                #include <sys/socket.h>
                int socket(int domain, int type, int protocol);
                    返回值:成功返回值套接字的文件描述符  失败 -1
                    参数:domain -->地址协议类型,常用的有:
                            AF_INET或者PF_INET    -->ipv4地址
                            AF_INET6或者PF_INET6  -->ipv6地址
                            AF_UNIX或者PF_UNIX或者AF_LOCAL  -->unix本地域套接字
                         type -->你创建的套接字类型
                                  tcp套接字(流式套接字/数据流套接字) --> SOCK_STREAM
                                  udp套接字(数据报套接字) --> SOCK_DGRAM         
                         protocol -->扩展协议,默认设置为0
            2、绑定ip和端口号
                #include <sys/types.h>        
                #include <sys/socket.h>
                int bind(int socket, const struct sockaddr *address,socklen_t address_len);
                    返回值:成功 0  失败 -1
                    参数:socket-->套接字
                        struct sockaddr--> 通用地址结构体,兼容ipv4和ipv6
                        {
                           sa_family_t sa_family;-->存放地址协议类型
                           char        sa_data[14];-->存放ip和端口号
                        }
                        struct sockaddr_in --> ipv4地址结构体
                        {
                            unsigned short         sin_family; -->存放地址协议类型AF_INET或者AF_INET6
                            unsigned short int     sin_port; -->端口号
                            struct in_addr         sin_addr; -->绑定自己的ip地址
                                struct in_addr
                                {
                                    in_addr_t s_addr;
                                }
                            unsigned char          sin_zero; -->充数的,打酱油的,为了跟通用地址结构体大小保持一致
                        }
                        struct sockaddr_in6 -->ipv6地址结构体
                        {
                            sa_family_t     sin6_family;    // 地址族 
                            u_int16_t       sin6_port;      // 端口号 
                            struct in6_addr sin6_addr;      // IPv6地址结构体
                            struct in6_addr 
                            {
                                unsigned char   sa_addr[16];    // IPv6地址 
                            };
                            u_int32_t       sin6_flowinfo;  // 流信息 
                            u_int32_t       sin6_scope_id;  // scope ID 
                        }; 
                        address_len -->地址结构体的大小
            3、ip和端口号的转换
                大小端:
                    大端序:数据的高字节存放在低地址,低字节存放在高地址
                    小端序:数据的高字节存放在高地址,低字节存放在低地址
                    ubuntu系统:小端序存放,称之为主机字节序
                    网络上的数据:大端序存放,称之为网络字节序
                将主机字节序转换成网络字节序
                    转换ip:
                        #include <sys/socket.h>
                        #include <netinet/in.h>
                        #include <arpa/inet.h>
                        in_addr_t inet_addr(const char *cp);
                            返回值:大端序ip   失败 -1
                            参数:cp -->小端序ip,点分十进制格式的ip
                        int inet_aton(const char *cp, struct in_addr *inp);
                    转换端口号:
                        uint32_t htonl(uint32_t hostlong);//某些特定情况下转换ip
                        uint16_t htons(uint16_t hostshort);
                        uint32_t ntohl(uint32_t netlong);                               
                        规律:h  -->host
                             n  -->network
                             l  -->long 跟ip转换有关
                             s  -->short 跟端口转换有关                        
                将网络字节序转换成主机字节序
                    转换ip:
                        char *inet_ntoa(struct in_addr in);
                        const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
                            参数:af -->地址协议 AF_INET 
                                 src -->存放你要转换的网络字节序ip
                                 dst -->存放转换好的主机字节序ip
                                 size -->ip地质大小
                    转换端口号:
                        uint16_t ntohs(uint16_t netshort);         
            4、连接
                #include <sys/types.h>   
                #include <sys/socket.h>
                int connect(int socket, const struct sockaddr *address, socklen_t address_len); 
                参数:address -->存放对方的ip和端口号         
                     address_len -->地址结构体的大小         
            5、监听(待机)
                int listen(int socket, int backlog);
                    参数:backlog -->同时能够接受的最大客户端连接数量
                        listen(tcpsock,7);                 
            6、接受连接请求
                int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len);
                    返回值:成功 新的套接字用于等一会收发信息 失败 -1(要产生新的套接字,主要是为了区分不同客户端的连接)                            
                    参数:address -->存放连接服务器的那个客户端的ip和端口
                         address_len -->地址结构体的大小
                    特点:在没有客户端连接服务器的时候,服务器阻塞在accept(主要是阻塞在socket上)
                         当有客户端成功连接服务器的时候,会解除accept阻塞,产生新套接字
            7、收发信息
                ssize_t send(int socket, const void *buffer, size_t length, int flags);
                    返回值:length是多少,就返回多少
                    参数:flags -->默认设置0
                         length-->发送字节数
                ssize_t recv(int socket, void *buffer, size_t length, int flags);                                                                      返回值:跟send有关
                    参数:flags -->默认设置0
                               -->MSG_WAITALL -->recv会一直等待length个字节的数据全部接收完毕才退出 
                    三种情况
                        情况一: 成功 大于0  
                        情况二: 等于0,表示客户端或者服务器断开连接了
                        情况三: -1 失败
                    特点:正常情况下,收不到消息阻塞,但是如果客户端或者服务器断开连接,不阻塞,然后返回0     
            8、遇到的问题
                问题一: 绑定失败!: Address already in use
                原因:你退出程序的时候,使用的端口号并不是立马就释放掉了,会延时一段时间(不同系统,时间不一样),导致你再次运行程序,提示绑定失败
                解决方案:
                        第一种:更换新的端口号,通过主函数传参更换
                        第二种:取消端口号绑定限制(设置套接字的属性)                               
                        int  setsockopt(int socket,int level,int option_name,void *option_value,socklen_t option_len);
                            参数:level       -->SOL_SOCKET
                                 option_name -->SO_REUSEADDR  //取消端口号绑定限制
                                             -->SO_BROADCAST  //设置udp广播
                                 option_value
                                 option_len
                               int on=1;
                        例如: setsockopt(tcpsock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
                问题二: 服务器突然断开,导致客户端疯狂打印,不阻塞了
                原因:recv正常情况下,收不到消息阻塞,但是如果客户端或者服务器断开连接,不阻塞,然后返回0
            UDP协议
                    udp客户端                udp服务端
                创建udp套接字            创建udp套接字
                bind()                   bind()
                收发信息                 收发信息
                关闭                     关闭
            1、收发信息
                ssize_t sendto(int socket,void *message,size_t length,int flags,struct sockaddr *dest_addr,socklen_t dest_len);
                    返回值:length是多少,就返回多少
                    参数:dest_addr -->存放目标ip和端口号
                ssize_t recvfrom(int socket, void *buffer,size_t length,int flags, struct sockaddr *address,socklen_t *address_len);
                    返回值:跟recv差不多,但是重要区别是:recv函数断开连接就不会阻塞,返回0,recvfrom函数一直阻塞
                    参数:address -->存放对方的ip和端口号
            2、udp的广播和组播
                   第一个:广播(很简单)
                      思路:
                        发送端                            接收端
                       socket()                          socket()
                       bind()//不可以使用具体的ip         bind()//必须使用系统定义的宏INADDR_ANY
                             使用系统定义的宏INADDR_ANY
                       设置套接字的属性为可以广播         recvfrom()
                       sendto()//必须使用广播地址         close()
                       close()
                   第二个:组播
                        发送端                                接收端
                       socket()                              socket()
                       bind()//不可以使用具体的ip             bind()//必须使用系统定义的宏INADDR_ANY
                             使用系统定义的宏INADDR_ANY       将接收端添加到组播组
                       设置套接字的属性为可以广播             recvfrom()
                       sendto()//必须使用组播地址(D类地址)    close()
            总结:TCP和UDP区别
                    tcp面向连接的,可靠的通信方式 
                    面向连接:表面上理解connect()  accept()
                             深层次理解tcp连接过程中的三次握手(tcp协议的底层原理),tcp断开连接的时候:四次握手
                             三次握手是发生在客户端连接服务器的时候,双方通过三次握手,相互确认了连接关系
                    可靠:支持错误重传,不会丢包
                    tcp可以一口气发送的字节数远远大于udp,100万字节都行
                    用途:用于通信质量要求高的场合

                    udp无连接的,不可靠的通信方式(相对于tcp)          
                    不可靠:丢失数据包,出错了也不会重传,udp一口气大概5万个字节左右
                    用途:视频点播
            多路复用:监测多个IO口的数据流向(检测文件描述符的状态改变:读状态(读就绪)、写状态(写就绪)、异常状态(异常就绪))        
                    多路复用的一般套路:
                            第一步:想清楚你究竟要监测哪些文件描述符  
                            第二步:将所有你要监测的文件描述符添加到集合中
                            第三步:调用select监测集合中的文件描述符的状态改变
                                   你要监测哪种状态,传参的时候写入到对应位置就行了
                            第四步:判断是否真的发生了想要监测的状态改变,调用FD_ISSET()来判断
                1、文件描述符监测
                    #include <sys/select.h>
                    int select(int nfds, fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);//监测多个文件描述符的状态改变
                        返回值:成功 >0
                               失败 -1
                               超时 0
                        参数:nfds -->你要监测的文件描述符集合中最大的那个文件描述符值+1
                             readfds -->监测文件描述符集合中所有文件描述符的读状态(是否有数据可读)
                             writefds -->监测文件描述符集合中所有文件描述符的写状态(是否有数据可写)
                             errorfds -->监测文件描述符集合中所有文件描述符的异常状态
                             timeout -->超时  第一种情况:设置NULL表示永远等待
                                              第二种情况:设置为某个具体值,依照具体值等待,超过时间就退出监测                
                            struct timeval
                            {
                                tv_sec; //秒
                                tv_usec; //微秒
                            }
                        重点:select监测多个文件描述符的状态改变,如果某个文件描述符发生了状态改变,那么其他没有发生状态改变的文件描述符会被自动从集合中剔除
                             select如果没有监测到想要的状态改变,会阻塞
                2、操作文件描述符集合的函数
                    void FD_CLR(int fd, fd_set *fdset); //将fd从集合中删除
                    int FD_ISSET(int fd, fd_set *fdset);//判断fd是否在集合 是成员 返回非零   
                    void FD_SET(int fd, fd_set *fdset);//将fd添加到集合中
                    void FD_ZERO(fd_set *fdset);//清空集合
                    注意:当select需要监测多个文件描述符的时候 ,每次监测都要将所有的文件描述符添加一遍
                         每次调用select的时候保证第一个参数是最大的文件描述符+1         
                3、poll()             
                    #include <poll.h>                
                    int poll(struct pollfd fds[], nfds_t nfds, int timeout);//也是可以检测文件描述符的状态
                        返回值: 成功  > 0
                                 失败  -1
                                 超时  0
                        参数:struct pollfd
                            {
                                fd;  //存放你要监测的文件描述符
                                events; //决定你是想要监测读就绪,写就绪还是异常就绪
                                            POLLRDNORM或者POLLIN   --->读就绪
                                            POLLWRNORM或者POLLOUT  --->写就绪
                                            POLLERR                --->异常就绪
                                revents;//判断是否发生你想要的状态改变 
                                例如: 判断是否读就绪
                                if(fds[0].revents&POLLIN) //说明发生了读就绪
                                {
                                }
                            }
                            nfds -->表明结构体数组中成员个数 
                            timeout -->超时  毫秒  
                                       -1  永远等待
                    poll和select的区别:
                        poll不会将结构体数组中状态没有改变的文件描述符剔除          
            获取网络上的时间和天气
                问题一: 网站的ip和端口号如何得到??
                    方法一:例如:进入到百度,按F12,接着按F5,点击第一个header 
                    方法二:调用linux提供函数解析
                        #include <netdb.h>
                        struct hostent *gethostbyname(const char *name);
                            返回值:struct hostent
                                   {
                                        char  *h_name;            官方名字
                                        char **h_aliases;         别名
                                        int    h_addrtype;        地址协议类型 AF_INET
                                        int    h_length;          地址结构体大小
                                        char **h_addr_list;       具体的ip地址
                                   }
                            参数:name --->网站的域名 www.baidu.com
                问题二:寻找网上免费提供的天气,时间接口
                    http://www.sojson.com/open/api/weather/json.shtml?city=广州    //天气
                        ip地址 183.131.24.37
                        端口号 443
                    http://api.k780.com:88/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json //时间
                        ip地址 139.199.7.215
                        端口号 88 
                    域名:www.sojson.com
                    连接字符:/open/api/weather/json.shtml?city=广州
                    发如下指令给网页服务器
                    strcpy(cmd,"GET /open/api/weather/json.shtml?city=广州 HTTP/1.1\r\n");
                    strcat(cmd,"Host: www.sojson.com\r\n\r\n");
                    
                    例如:                    
                    strcpy(cmd,"GET /?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json HTTP/1.1\r\n");
                    strcat(cmd,"Host: api.k780.com:88\r\n\r\n");
                    ret=send(tcpsock,cmd,strlen(cmd),0); //发送指令给服务器                    
                    recv(tcpsock,timebuf,1024,0);//马上接收服务器反馈的信息

猜你喜欢

转载自blog.csdn.net/lly_3485390095/article/details/83002611