zoukankan      html  css  js  c++  java
  • 基本套接字

    基本套接字函数

    创建套接字

    #include <sys/socket.h>
    int socket (int family, int type, int protocol);
    

    a a
    a a

    返回套接字描述符

    客户端调用

    #include <sys/socket.h>
    int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
    
    1. 客户端没收到SYN包(超时),返回ETIMEDOUT
    2. 服务端回复RST,表明对应端口没有进程监听,产生硬件错误(hard error)返回ECONNREFUSED
      • RST是在出错的情况下TCP发送的包,共三种情况,其余两种是:1.TCP终止已存在的连接,2.TCP收到不存在的连接的包(即连接已经关闭后收到包)
    3. SYN从中间路由器收到ICMP"destination unreachable",称为软错误(soft error)。客户端保存信息,持续发送SYN直到超时,返回EHOSTUNREACH或ENETUNREACH;也有可能本地所有转发表都无法到达,connect立即返回。如果connect失败,套接字不可用,不能再次调用connect,必须调用close,重新调用socket。

    bind调用将本地协议地址赋值给套接字

    #include <sys/socket.h>
    int bind (int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
    

    bind的主要错误是EADDRINUSE,涉及到SO_REUSEADDR和SO_REUSEPORT套接字选项。

    #include <sys/socket.h>
    #int listen (int sockfd, int backlog);
    

    listen调用执行两步操作:

    1. 当套接字通过socket()创建就视为活动的套接字。如果是客户端,会调用connect;lixten()调用将套接字转换为被动套接字,表示内核应该接受连接请求。
    2. 第二个参数指明内核为这个套接字分配队列的最大连接数量(最好的方式是包装listen函数,支持配置、环境变量指定数量,避免重新编译)。内核为lintening套接字维护两个队列,未完成连接队列:包含所有收到客户端的SYN项,等待完成TCP的三步握手,处于SYN_RCVD状态。已完成连接队列:包含已完成TPC三步握手项,处于ESTABLISHED状态。
      a
      当SYN到达server端时,TCP在未完成连接队列中创建一项,并完成三步握手中的第二步,直到第三步完成或超时,这一项都处于未完成连接队列。如果三步握手正常完成,这一项被移动到已完成队列的末尾。当进程调用accept(),已完成队列的第一项被返回,如果为空,进程会睡眠(默认阻塞套接字),直到有项被转移到已完成队列中,backlog的值是两个队列之和的最大值。在三步握手完成后,调用accept之前,客户端发送的数据存储在套接字接收缓冲区中,直到填满。
      如果客户端SYN到达时队列已满,TCP无视这个SYN,因为这个状况是临时的,可以视为软错误,客户端仅仅重发SYN即可。如果不想任何客户端连接listening套接字,最好关闭它而不是将backlog设为0(可能有多种实现,产生不同的解释)。

    accept返回下一个已完成连接。如果进程会睡眠(默认阻塞套接字)。

    #include <sys/socket.h>
    int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
    

    accept成功则返回内核自动产生的新描述符,代表TCP连接,称为连接套接字。服务结束后,连接描述符关闭。函数返回三个值,描述符。客户端地址,地址长度。如果不需要后两个值,可将指针设为NULL。


    #include <unistd.h>
    int close (int sockfd);
    

    每调用一次close,减少描述符的引用计数,只有当引用计数为0时才发FIN包。

    #include <sys/socket.h>
    int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen); /*get local protocol adddress*/
    int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen); /*get foreign procotol address*/
    
    • 客户端调用connect后,getsockname获取本地IP地址和端口
    • 服务端调用bind并设置端口号为0时,内核自动选取端口,getsockname获取本地地址与端口
    • getsockname还可以获取套接字的地址族
    • 服务端bind()通配的IP地址,accept返回后,连接套接字调用getsockname,获取为连接赋值的本地IP地址
    • 子进程调用exec函数族执行新进程,accept返回的cliaddr信息丢失,只能通过描述符调用getpeername得到客户端的IP地址和端口。新进程通常有两种方式获取连接描述符,一种是将描述符格式化为参数通过exec传递;一种是提前约定特定的描述符值。

    信号处理

    信号的作用是向进程通知一件事件的发生,可以由一个进程发送给另一个进程(或发给自己),也可以由内核发送给进程。当调用fork()产生子进程处理连接时,主要处理的信号时SIGCHILD,当一个进程终止时,内核向它的父进程发送这个信号,处理信号采取的默认行为是忽视,因此子进程进入僵尸状态。POSIX中指明,将SIGCHLID设为SIG_IGN的处理方式是未定义的,不能保证子进程不变成僵尸,可移植的方式是捕捉信号并调用wait()或waitpid()。 对于一个典型的echo server和echo client会产生如下输出

    solaris % tcpserv & //start server in background
      
    solaris % tcpcli01 127.0.0.1
    hi there  //we type this
    hi there  //this is echoed
    ^D        //we type our EOF character
    
    child 16942 terminated //tcpserv  output by printf in signal handler
    accept error: Interrupted system call //tcpserv main function aborts 
    
    1. 客户端输入EOF,客户端进程退出,并向server发送FIN,server返回ACK
    2. 服务端对应的子进程终止
    3. 向父进程发送SIGCHILD信号,调用signal handler处理,此时父进程正阻塞在accept
    4. 由于在阻塞期间捕捉到信号,accept(永远阻塞的慢系统调用)返回错误EINTR,代表系统调用被中断,而父进程没有处理这个错误,因此异常终止 有些系统signal()设置了SA_RESTART标志,内核会重新开始被中断的系统调用,因此不会出现上面的错误,但是为了可移植性,还是要手动包装signal()。可以使用下面这种方式:
    for ( ; ; ) {
        clilen = sizeof (cliaddr);
        if ( (connfd = accept (listenfd, (SA *) &cliaddr, &clilen)) < 0) {
            if (errno == EINTR)
                continue;         /* back to for () */
            else
                err_sys ("accept error");
        }
        ...
        ...
    }
    
    

    几乎所有的慢系统调用都可以使用这种方式处理被中断情况(EINTR),但是connect()不可以,它需要使用类似select的I/O多路复用方法确定。I/O多路复用主要解决进程有多个I/O源的情况,进程不应该阻塞在一个源上,而是当任何一个源有事件发生时都可以得到通知。

    wait和waitpid

    如果在信号处理函数执行前产生了多个信号,处理函数只执行一次,因为Unix没有信号队列。
    如果该进程有正在运行的子进程,在第一个子进程结束前,wait()会一直阻塞。waitpid可以设置WNOHANG标志,即使有正在运行的子进程也不必阻塞,可以使用下面的方法处理产生多次信号的情况:

     2 void
     3 sig_chld(int signo)
     4 {
     5     pid_t    pid;
     6     int      stat;
    
     7     while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
     8         printf("child %d terminated
    ", pid);
     9     return;
    10 }
    

    调用write()向已经收到FIN的套接字写数据,第一次对方会发送RST,第二次进程会收到SIGPIPE信号,write()返回EPIPE错误。

  • 相关阅读:
    vue-cli 3.0 路由懒加载
    vue 路由拦截、axios请求拦截
    vue-cli 3.0 图片路径问题(何时使用 public 文件夹)
    vue 监听页面宽度变化 和 键盘事件
    WGS84、GCJ-02(火星坐标)、百度坐标,Web墨卡托坐标
    Java学习之道:Java项目打包发布
    ora-14550问题解决
    费氏搜寻法之算法分析与实现
    [置顶] woff格式字体怎么打开和编辑?
    C++小知识之Vector排序
  • 原文地址:https://www.cnblogs.com/zyfgs2012/p/4103396.html
Copyright © 2011-2022 走看看