zoukankan      html  css  js  c++  java
  • socket

    套接字类型与协议设置

    创建套接字

    #include <sys/socket.h>
    
    /* 成功时返回文件描述符,失败时返回-1 */
    int socket(int domain, int type, int protocol);
    

    协议族(Protocol Family)

    PF_INET:IPV4

    PF_INET6:IPV6

    套接字类型(Type)

    面向连接的套接字(SOCK_STREAM)

    特点

    • 传输过程中数据不会消失
    • 按序传输数据
    • 一一对应
    • 传输的数据不存在数据边界(TCP粘包问题)
    面向消息的套接字(SOCK_DGRAM)

    特点

    • 快速传输,不按顺序
    • 数据可能丢失(UDP不可靠)
    • 有数据边界(UDP无粘包问题)
    • 限制每次传输的数据大小

    协议类型(Protocol)

    SOCK_STREAM面向连接,在IPV4协议族中,只有IPPROTO_TCP面向连接。

    SOCK_DGRAM面向消息,在IPV4协议族中,只有IPPORT_UDP面向消息。

    所以第三个参数通常指定为0,系统会自动推导出协议类型。

    网络地址的初始化与分配

    端口号

    IP地址用于定位计算机,端口号用于定位计算机中的进程。

    TCP和UDP不共享端口号,不会互相占用。

    地址信息的表示

    sockaddr_in和in_addr
    struct sockaddr_in
    {
        sa_family_t 	sin_family;  	//地址族
        uint16_t 		sin_port;	//16位TCP/UDP端口号
        struct in_addr      sin_addr;	//32位IP地址
        char 		sin_zero[8];	//填充为0,使sockaddr_in与sockaddr保持一致
    };
    
    struct in_addr
    {
        In_addr_t		s_addr;		//32位IPV4地址
    };
    

    sockaddr和sockaddr_in的区别:后者保存IPV4地址信息,前者不只为IPV4设计。

    网络字节序与地址变换

    CPU字节序
    • 大端法
    • 小端法

    进行网络传输时,统一转换为大端序。

    字节序转换
    • h(host):主机
    • n(network):网络
    • l(long):long型数据,通常用于IP地址转换
    • s(short):short型数据,通常用于端口号转换

    组合出四种转换字节序的函数

    htons:把short型数据,从主机字节序转换到网络字节序

    htonl:把long型数据,从网络字节序转换到主机字节序

    ntohs:把short型数据,从主机字节序转换到网络字节序

    ntohl:把long型数据,从网络字节序转换到主机字节序

    数据传输无需考虑字节序问题。

    网络地址的初始化与分配

    IP地址格式的转换

    存储在sockaddr_in中的IP地址是32位整型,而人类熟悉的IP地址表示方法是点分十进制表示法

    #include <arpa/inet.h>
    
    /**
     * 将点分十进制表示的IP地址转换为32位整型的IP地址
     * 成功时返回32位整数,失败时返回INADDR_NONE
     * 1)检测无效IP地址 2)验证是否转换为了网络字节序
     */
    in_addr_t inet_addr(const char * string);
    
    /**
     * 功能与inet_addr相同,将转换后的IP地址直接赋给sockaddr_in结构体中的in_addr结构体变量
     * 成功返回1,失败返回0
     */
    int inet_aton(const char * string, struct in_addr * addr);
    
    /**
     * 32位整数转点分十进制IP地址
     * 成功返回字符串地址,失败返回-1
     * 调用该函数后,应立即将字符串值复制到其他内存空间,下一次调用该函数时,上一次的值将被覆盖
     */
    char * inet_ntoa(struct in_addr adr);
    
    网络地址初始化
    memset(&serv_addr, 0, sizeof(serv_addr));		        //清零
    serv_addr.sin_family = AF_INET;					//地址族
    serv_addr.sin_addr.s_addr = htonl(inet_addr(serv_ip));	        //IP地址
    serv_addr.sin_port = htons(atoi(serv_port));			//端口号
    

    INADDR_ANY:利用常数分配服务器端的IP地址,自动获取服务器IP。若服务器有多个IP,只要端口号一致,可以从多个IP地址接收数据。

    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    

    向套接字分配网络地址

    #include <sys/socket.h>
    
    /**
     * 传入3个参数,依次为socket文件描述符,存储地址信息的结构体变量,结构体变量的长度
     * 成功返回0,失败返回-1
     */
    int bind(int sockfd, struct sockaddr * myaddr, socklen_t addrlen);
    

    进入等待连接请求状态

    #include <sys/socket.h>
    
    /**
     * sock:希望进入等待状态的socket文件描述符
     * backlog:连接请求等待队列的长度
     */
    int listen(int sock, int backlog);
    

    受理客户端请求

    #include <sys/socket.h>
    
    /**
     * sock:服务器socket文件描述符
     * addr:保存客户端地址信息的结构体变量
     * addrlen:第二个参数的长度
     * 成功返回客户端socket文件描述符,失败返回-1
     */
    int accept(int sock, struct sockaddr * addr, socket_t * addrlen);
    

    UDP套接字

    基于UDP的I/O函数

    #include <sys/socket.h>
    
    /**
     * 成功时返回传输的字节数,失败是返回 -1
     * sock: 用于传输数据的 UDP 套接字
     * buff: 保存待传输数据的缓冲地址值
     * nbytes: 待传输的数据长度,以字节为单位
     * flags: 可选项参数,若没有则传递 0
     * to: 存有目标地址的 sockaddr 结构体变量的地址值
     * addrlen: 传递给参数 to 的地址值结构体变量长度
     */
    ssize_t sendto(int sock, void *buff, size_t nbytes, int flags,
                   struct sockaddr *to, socklen_t addrlen);
    
    /**
     * 成功时返回传输的字节数,失败是返回 -1
     * sock: 用于传输数据的 UDP 套接字
     * buff: 保存待传输数据的缓冲地址值
     * nbytes: 待传输的数据长度,以字节为单位
     * flags: 可选项参数,若没有则传递 0
     * from: 存有发送端地址信息的 sockaddr 结构体变量的地址值
     * addrlen: 保存参数 from 的结构体变量长度的变量地址值。
     */
    ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags,
                     struct sockaddr *from, socklen_t *addrlen);
    

    UDP的IO次数

    由于UDP套接字存在数据边界,输入次数必须和输出次数相等。

    断开套接字连接

    TCP半关闭

    close

    close函数的断开是完全断开,即同时关闭接收和发送。

    试想一种场景,A、B主机进行双向通信,A发送完所有数据后close,同时失去接收数据的能力,此时B尚未发送给A的数据将无法被A接收到。

    shutdown
    #include <sys/socket.h>
    
    /**
     * sock:文件描述符
     * howto:SHUT_RD(断开输入流),SHUT_WR(断开输出流),SHUTRDWR(同时断开)
     * 成功返回0,失败返回-1
     */
    int shutdown(int sock, int howto);
    

    SHUTRDWR相当于调用两次shutdown,两次参数分别为SHUT_RD,SHUT_WR。

    DNS

    使用域名的必要性

    域名不常变而IP常变,在代码中使用IP地址不是一个好办法。

    IP地址和域名的转换

    域名->IP
    #include <netdb.h>
    
    /**
     * 域名->IP
     * 成功返回hostent结构体地址,失败返回NULL
     */
    struct hostent * gethostbyname(const char * hostname);
    
    struct hostent
    {
        char * h_name;			//官方域名
        char ** h_aliases;		        //其他域名
        int h_addrtype;			//IP地址类型(IPV4,IPV6)
        int h_length;			//IP地址长度
        char ** h_addr_list;	        //IP地址
    }    
    
    IP->域名
    #include <netdb.h>
    
    /**
     * IP->域名
     * 传入三个参数,依次为IP地址,长度(IPV4时为4,IPV6时为16),地址族信息(AF_INET,AF_INET6)
     * 成功返回hostent结构体变量,失败返回NULL
     */
    struct hostent * gethostbyaddr(const char * addr, socklen_t len, int family);
    

    套接字的多种可选项

    套接字选项的分层

    • IPPROTO_IP:IP协议相关
    • IPPROTO_TCP:TCP协议相关
    • SOL_SOCKET:套接字通用

    getsocket & setsocket

    #include <sys/socket.h>
    
    /**
     * getsocket():读取可选项
     * sock:套接字文件描述符
     * level:可选的协议层
     * optname:可选项名
     * optval:保存结果的缓冲地址
     * optlen:optval的缓冲大小,调用后该变量中保存通过第四个参数返回的信息的字节数
     * 成功时返回0,失败时返回-1
     */
    int getsockopt(int sock, int level, int optname, void *optval, socket_t *optlen);
    
    /**
     * setsocket():修改可选项
     * 成功时返回0,失败时返回-1
     */
    int setsockopt(int sock, int level, int optname, const void *optval, socket_t *optlen);
    
  • 相关阅读:
    Tomcat 三种运行方式
    MariaDB介绍
    Nginx 平滑升级
    代理命令 proxy_pass 详解
    Nginx 和 Tomcat 负载均衡
    基于Apache和tomcat实现负载均衡
    centos7 通过源码编译的方式安装和配置Apache
    基于nginx结合openssl实现https
    HTTP 和 HTTPS 区别
    linux系统中修改别名配置文件,构建命令别名
  • 原文地址:https://www.cnblogs.com/CofJus/p/14468445.html
Copyright © 2011-2022 走看看