• socket in Linux


    socket in Linux

    Socket编程界面由4BSD UNIX首先提出,目的是解决网间网进程通信问题。Socket接口为进程间通信提供了一种新的手段,它不但能用于同一机器中的进程之间的通信,而且支持网络通信功能。Socket具有类型,反应了对用户透明的通信特性。

    一个完整的Socket连接用一个相关描述: { 协议,本地地址,本地端口,远地地址,远地端口 }

    Socket 是面向客户-服务器模型而设计的,针对客户和服务器程序提供不同的Socket系统调用。

    socket系统调用

    #include 
    int socket(int domain, int type, int protocol);
    
    • 1
    • 2

    Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后 的连接建立、数据传输等操作都是通过该Socket实现的

    • domain指明所使用的协议族,PF_INET,表示互联网协议族(TCP/IP协议族),PF_UNIX 本地协议
    • type参数指定socket的类型:SOCK_STREAM 流式套接字 或 SOCK_DGRAM 数据报套接字,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;
    • protocol通常赋值"0"。

    Socket()调用返回一个整型socket描述符,你可以在后面 的调用使用它。

    使用socket的方法

    下图是面向连接客户–服务器模型的典型时序图

    服务器               客户

    socket()              socket()

    bind()               bind()

    listen()

    accept()   等待客户连接请求

    阻塞 <━━━━━━━━━━━━━━━ connect()

    read() <━━━━━━━━━━━━━━━> write()

    1、创建socket

    使用套接字函数socket创建,不过传递的参数与网络套接字不同,它会在内核创建一个struct socket实例。域参数应该是PF_LOCAL或者PF_UNIX,而不能用PF_INET之类。本地套接字的通讯类型应该是SOCK_STREAM或SOCK_DGRAM,协议为默认协议。该系统调用的返回值是一个socketid,protocol参数一般为0,表示默认协议。

    #include 
    int socket (int domain,int type, int protocol);
    
    • 1
    • 2

    实际不止下面这些参数,在《UNIX网络编程》中介绍到可以访问ICMP、IP链路层的分组等可设置其它参数。

    domain可选参数:AF 表示ADDRESS FAMILY 地址族、PF 表示PROTOCL FAMILY 协议族

    描述
    AF_INETIPv4 因特网域
    AF_INET6
    AF_UNIX
    AF_UPSPEC

    在windows中,AF_INET与PF_INET完全一样,而在Unix/Linux系统中,在不同的版本中这两者有微小差别。对于BSD,是AF,对于POSIX是PF**。理论上,建立socket时是指定协议,应该用PF_XXXX,设置地址时应该用AF_XXXX。**

    type类型可选参数:

    类型描述
    SOCK_DGRAM固定长度的、无连接的、不可靠的报文传递
    SOCK_RAMIP协议的数据报接口
    SOCK_SEQPACKET固定长度的、有序的、可靠地、面向连接的报文传递
    SOCK_STREAM有序的、可靠地、双向的、面向连接的字节流

    2、指定本地地址–绑定

    将本地socket地址与所创建的socket联系起来,即将本socket地址赋予socket。bind()的作用相当于给socket命名不同于网络套接字的绑定。本地套接字的绑定的是struct sockaddr_un结构。struct sockaddr_un结构有两个参数:sun_familysun_pathsun_family只能是AF_LOCALAF_UNIX,而sun_path是本地文件的路径。通常将文件放在/tmp目录下。bind时需要注意:

    • 绑定时,要注意绑定的地址的文件必须是系统中不存在的文件,且使用绝对路径
    • 绑定的地址中的文件的访问权限最好是0777,然后按照当前umask进行修正

    例如:

    struct sockaddr_un sun;
    sun.sun_family = AF_LOCAL;
    strcpy(sun.sun_path, filepath);
    
    
    #include 
    
    //设置本端IP地址和端口
    int bind (int sockfd, const struct sockaddr *addr, socklen_t len);
    
    //获取任何套接字本端IP地址和端口
    int getsockname(int sockfd, struct sockaddr *restrict addr, 
                    socklen_t *restrict alenp);
    
    //获取套接字上远端IP地址和端口
    int getpeername(int sockfd, struct sockaddr *restrict addr,
                   socklen_t *restrict alenp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3、建立socket连接

    connect()accept()这两个系统调用用于完成整个相关的建立。connect用于建立连接,accept用于面向连接的服务器。连接到一个正在监听的套接字之前,同样需要填充struct sockaddr_un结构,然后调用connect函数。

    //客户端
    #include 
    
    //无连接的协议SOCK_DGRAM也可以使用它,这样就不用每次发送数据时指定IP地址;
    int connect (int sockfd, const struct sockaddr *addr, socklen_t len);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • sockfd:调用socket()返回的fd;
    • addr: 指向对方socket地址(信宿地址)结构的指针;
    //服务端
    
    #include 
    
    /*
    参数:backlog指定内核处于三次握手或者三次握手已经完成的连接队列大小;如果队列慢了将不会接受新的连接
        DOS攻击就是使用大量SYN分解把这个队列占满,导致正常连接无法进行;
    说明:启动这个函数后,即使你不调用accept客户端也能完成三次握手
    */
    int listen(int sockfd, int backlog);
    
    /*
    说明:从已经完成三次握手的队列取出套接字,完成内核TCP状态的切换SYN_RCV--->ESTABLISHED
    */
    int accept (int sockfd, struct sockaddr *restrict addr,
               socklen_t *restrict len);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4、发送数据 ---- write(),writev(),send()与sendto(),sendmsg()

    用于socket数据发送的系统调用一共有五个,其中三个,write()、writev()和send()用于面向连接传输,其余两个用于无连接传输。面向连接的调用可以不指定信宿地址,而无连接的调用必须指定。假如无连接socket的双方均调用过connect(),可以认为是建立有连接的socket,也可以面向连接调用发送数据。

    三个面向连接调用三者的格式大致相同:

    • write (sockid,buff,bufflen) : 缓冲发送
    • writev (sockid,iovector,vectorlen) : 集中发送
    • send (sockid,buff,bufflen,flags) : 可控缓冲发送

    其中buff指向发送缓冲区的指针,bufflen是发送缓冲区大小。

    用于无连接数据发送的调用有两个:

    • sendto (sockid,buff,bufflen,flags,dsadd,addrlen)
    • sendmsg (sockid,message,flags):可控集中无连接发送
    #include 
    
    /*这个函数参数跟write功能类似,但增加了一个flag参数;这个函数执行成功代表仅
    仅将数据写入网卡驱动发送队列,而不是对方成功接收;
    */
    ssize_t send(int sockfd, const void *buf, size_t len, int flag);
    
    //可以在无连接的套接字上发送数据;对于有连接的套接字,目标地址会被忽略
    ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flag,
                  const struct sockaddr *destaddr, socklen_t len);
    
    //下面这个发送函数可以指定多个缓冲区,与writev相似;它也面向无连接套接字
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flag);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5、接收数据 ---- read(),readv(),recv()与recvfrom(),recvmsg()

    接收数据与发送数据系统调用是一一对应的,两者参数的最大区别是,前者buffer是一个指针,其所指单元初值为欲读数据长度,调用后的值是实际读出的值。

    #include 
    
    //面向连接的套接字接收,类似read,但是多了个参数flag,可以接收特殊TCP数据
    ssize_t recv(int sockfd, void *buf, size_t nbytes, int flag);
    
    //accept可以在连接时得到连接者的原地址,recvfrom可以在接收是同时得到发送者的原地址;
    //对于有连接的套接字,等同于recv
    ssize_t recvfrom (int sockfd, void *restrict buf, size_t len, int flag,
                     struct sockaddr *restrict add,
                     socklen_t *restrict addrlen);
    
    /*
    功能:同时接收到多个数据报
    返回值:返回数据的字节长度;若无可用数据,对方已经结束返回0,否则返回-1表示错误;
    */
    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flag);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6、关闭socket

    因为套接字是双工通信,所以可以单独关闭读或者写端,类似双工管道。

    /*
    功能:半关闭
    参数:how
        SHUT_RD
        SHUT_WR
        SHUT_RDWR
    */
    #include 
    int shutdown (int sockfd, int how);
    
    //可以关闭linux所有文件描述符
    int close(int sockfd);
    
    //同时关闭套接字的读写两端
    int closesocket(int sockfd);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    7、socket设置

    套接字选项是进阶必备的知识,比如立即重用服务器监听端口、调整TCP窗口大小。各个协议的详细选项参加《UNIX网络编程》的150页。

    #include 
    
    /*
    功能:设置套接字选项
    参数说明:
        level 协议层次IPPROT_TCP/IPPROT_IP
        option 选项
        val 选项对应的值
        len 值的字节长度
    */
    int setsockopt(int sockfd, int level, int option,const void* val,
                  socklen_t len);
    
    int getsockopt(int sockfd, int level, int option, const void* val,
                  socklen_t *restrict lenp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    本地socket和网络socket的区别:

    • 域套接字关联的路径名应该是一个绝对路径名,千万不能使用相对路径名

    • 调用connect连接一个域套接字设计的权限测试等同于调用open以只写方式访问相应的路径名

    • 如果对于某个域套接字的connect调用发现这个监听套接字的队列已满,调用就立即返回一个ECONNREFUSED错误。

    参考链接:

    https://blog.csdn.net/qq_37414405/article/details/83690447

    网络编程探究

    并发模型参考:

  • 相关阅读:
    多组学+机器学习+膀胱癌+分型+建模
    基于微信小程序的心理自测咨询APP设计与实现-计算机毕业设计源码+LW文档
    Android 11 修改Makefile去掉编译时禁止拷贝APK限制
    Buildroot 添加 Qt 支持
    能否通过手机号查询他人位置及技术实现(省流:不能)
    Windows net start mysql 启动MySQL服务报错 发生系统错误 5 解决方法
    在Avalonia项目中使用MediatR和MS.DI库实现事件驱动通信
    数据结构与算法之排序: 选择排序 (Javascript版)
    Redis与分布式-哨兵模式
    【css 动画】css实现奔跑的北极熊
  • 原文地址:https://blog.csdn.net/ALone_cat/article/details/126556144