zoukankan      html  css  js  c++  java
  • Socket

    本文链接:https://blog.csdn.net/weixin_39258979/article/details/80835555

    一、TCP/IP UDP是什么?

    TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

    UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。

    下面是他们三者的关系:

    可以看出TCP/IP协议族包括运输层、网络层、链路层。socket是一个接口,

    在用户进程与TCP/IP协议之间充当中间人,完成TCP/IP协议的书写,用户只需理解接口即可。

    二、socket与TCP/IP的对应关系

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。

    在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,

    对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

     

    原作者将socket通信类比为打电话这一生活场景。

    这里我把TCP服务器比作政府某一服务部门能,TCP客户端比作企业中某一部门电话,描述这一过程,恰好就像是socket通信,服务部门提供服务,企业部门申请服务。
    要实现通信,首先政府部门都必须申请一个电话(socket_fd),并向有关部门注册(我们的系统),提供地址(sockadrr)以及属于哪个部门的(port),录入系统后,

    就算是合约生效了(bind),于是乎,政府广而告之,这个服务热线就算开通了,在部门里面的人员所需要做的事情,就是等待企业家拨打热线(listen)。

    企业家拨打电话对地点和部门没有这么多的要求了,他并不需要绑定地址和部门,在任何一个可以拨打电话的地方(可能是同个部门,也可以同公司不同部门,甚至可能是竞争对手),

    他只需要拿起一个已经注册的电话(socket_fd),拨打电话(connect)政府部门接通电话(accept)后,

    桥梁就打通了(服务者client_fd、顾客server_fd),可以进行听说了(read write)。

    企业家咨询完成(close),政府到点下班关闭服务(close)

    三、socket API 简单介绍

    2.1 socket() 创建socket描述符

    int socket(int domain, int type, int protocol); 
    //成功返回非负描述符,失败返回-1

    domain:即协议域,又称为协议族(family)。

    常用的地址族有:

    AF_INET
    AF_INET6
    AF_LOCAL(AF_UNIX,本地通信用)
    AF_ROUTE
    协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

    type:信息传送方式。

    SOCK_STREAM
    SOCK_DGRAM
    SOCK_RAW
    SOCK_PACKET
    SOCK_SEQPACKET

    protocol:对应协议。

    IPPROTO_TCP TCP传输协议
    IPPROTO_UDP UDP传输协议
    IPPROTO_SCTP STCP传输协议
    IPPROTO_TIPCTIPC传输协议
    通常设置为0,让其自动匹配。

    我的理解就是:
    domain 网络层相关协议 type 信息传送方式 protocol 运输层相关协议,如果前两个都确定了,系统就可以自动选择协议了。

    2.2 bind()绑定实际地址

    int bind(int sockfd, const struct sockaddr *addr, socklen_t
    addrlen); 
    //返回值:成功则为0,失败为-1

    sockfd 一般服务端才需要绑定,客户端由系统内核解决

    addr 所有协议都有一个公共的结构叫做const struct sockaddr,不同协议对应不同的具体结构,结构如下:

    struct sockaddr { 
    sa_family_t sin_family;//地址族
       char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息 
       }; 


    可以看出,我们端口和目标地址放在同一数组中,不太容易使用。对于不同的协议,我们会通过另一种特定的结构体的来完成初始化,再通过强制类型转换,来使用bind函数。

    强制转换第二个地址参数为const sockaddr *

    (const sockaddr *)sockaddr_in;
    (const sockaddr *)sockaddr_in6;
    (const sockaddr *)sockaddr_un;
    ipv4结构体
    
    struct sockaddr_in {
    sa_family_t sin_family; 
    in_port_t sin_port; //typedef    __uint16_t    in_port_t;
    struct in_addr sin_addr; 
    };
    
    struct in_addr {
    uint32_t s_addr; 
    };


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


    Unix域结构体

    #define UNIX_PATH_MAX 108
    
    struct sockaddr_un { 
    sa_family_t sun_family; 
    char sun_path[UNIX_PATH_MAX]; 
    };


    addrlen:协议结构体大小
    通常使用sizeof运算符计算

    sizeof(sockaddr_in);//IPv4 in:internet
    sizeof(sockaddr_in6);//IPv6
    sizeof(sockaddr_un);//本地 un:unix

    2.3 listen()、connect() 主机监听、从机链接

    int listen(int sockfd, int backlog);
    //返回值:成功则为0,失败为-1 
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    //返回值:成功则为0,失败为-1 

    listen决定需要开启的部门热线,connect拨打电话。

    listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。

    socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

    connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。

    客户端通过调用connect函数来建立与TCP服务器的连接。

    2.4 accept 建立链接

    只要服务端建立链接,我们就可以进行操作了

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    //返回值:成功则为0,失败为-1 


    第一个参数为服务器的socket描述字,
    第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址
    第三个参数为协议地址的长度。

    如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

    后面的读写操作都是根据这个返回值来完成的。

    2.5 read和write函数,读写


    这不是属于socket的API,但是socket总是伴随着以下函数:

    ssize_t read(int fd, void *buf, size_t count);
    ssize_t write(int fd, const void *buf, size_t count);
    
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
    const struct sockaddr *dest_addr, socklen_t addrlen);
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
    struct sockaddr *src_addr, socklen_t *addrlen);
    
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

    read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。

    如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

    write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节 数。失败时返回-1,并设置errno变量。

    在网络程序中,当我们向套接字文件描述符写时有俩种可能。

    1)write的返回值大于0,表示写了部分或者是 全部的数据。

    2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。

    如果错误为EINTR表示在写的时候出现了中断错误。

    如果为EPIPE表示 网络连接出现了问题(对方已经关闭了连接)。

    close关闭服务

    int close(int fd);
    //返回值:成功则为0,失败为-1 

    close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

    注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

  • 相关阅读:
    10 款最佳剪贴板管理器
    悉数美剧《黑客军团》中的黑客工具
    Vim的使用方法
    Mysql跨平台(Windows,Linux,Mac)使用与安装
    Linux下网络故障诊断
    RHEL6.2下挂载光驱安装软件
    MySQL数据库服务器的架设
    Unix如何轻松快速复制
    【Linux基础】Linux常用命令汇总
    博客编号数字密码
  • 原文地址:https://www.cnblogs.com/zhuyalong/p/11401625.html
Copyright © 2011-2022 走看看