• linux 网络编程 基础


     网络编程基础

    • 套接字编程需要指定套接字地址作为参数,不同的协议族有不同的地址结构,比如以太网其结构为sockaddr_in。
    • 通用套接字:
      struct sockaddr {
          sa_family_t    sa_family;    /* address family, AF_xxx 16Bytes  */
          char        sa_data[14];    /* 14 bytes of protocol address    */
      };
      
    • 实际使用的套接字结构

    • 以bind函数为例:

        bind(int  sockfd, //套接字文件描述符

           struct sockaddr *uaddr,//套接字结构地址

           int addr_len)//套接字地址结构长度  

            使用struct    sockaddr  为通用结构体,在以太网中,一般使用结构 sockaddr_in

    • 以太网套接字
    /* Internet address. */
    struct in_addr {
    	__be32	s_addr;
    };
    
    /* Structure describing an Internet (IP) socket address. */
    #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { __kernel_sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; };
    • 结构 sockaddr 和结构 sockaddr_in的关

    第二章:TCP网络编程流程

    tcp网络编程主要采取C/S模式,即客户端(C)、服务器(S)模式

    • 创建网路套接字接口函数socket

    int socket (int family, int type, int protocol)

    int family

    • AF_UNIX : Sockets for interprocess communication in the local computer.
    • AF_INET : Sockets of the TCP/IP protocol family based on the Internet Protocol Version 4
    • AF_INET6 : TCP/IP protocol family based on the new Internet Protocol, Version 6.
    • AF_IPX : IPX protocol family.

    int type

    • SOCK_STREAM (stream socket) specifies a stream-oriented, reliable, in-order full duplex connection between two sockets.
    • SOCK_DGRAM (datagram socket) specifies a connectionless, unreliable datagram service, where packets may be transported out of order.
    • SOCK_RAW (raw socket).

    int protocol

    • TCP is always selected for the SOCK_STREAM socket type, and UDP is always used as the transport protocol for  SOCK_DGRAM

    int bind(int sockfd,  struct sockaddr *uaddr,  socketlen_t  uaddrlen)

    • sockfd为 socket()函数创建返回的fd
    • uaddr 指向一个包含了ip地址 端口等信息
    • uaddrlen 是sockaddr的长度
    • bind 可以指定Ip地址或者端口  可以都指定

    int listen(int sockfd, int backlog)

    • sockf为socket创建成功返回的fd
    • backlog 表示在accept 函数处理之前在等待队列中允许最多的客户端个数

    int accept(int sockfd,  struct sockaddr *addr,  socketlen_t * addrlen)

    • accept 函数可以得到成功连接客户端的ip地址、端口信息和协议族等信息
    • accpet返回值是新连接客户端套接字的描述符

    数据的IO和复用

    常用的数据I/O函数有recv/send()  readv/writev  recvmsg/sendmsg

    int recv(int sockfd, void *buf, size_t len, int flag)

    • recv 函数的参数flag用于设置接收数据的方式
    • recv函数返回成功接收到的字节数,错误时返回-1

    int send(int sockfd,  const void *buf,  size_t len,  int flags)

    • send函数成功发送的字节数,发生错误是返回-1

    int recvmsg (int sockfd, struct msghdr *msg, int flags)

    • recvmsg 表示从sockd 中接收数据放在缓冲区,其操作方式由flags指定
    • 其返回值表示成功接收到的字节数,-1时表示发生错误
    • 当对端使用正常方式关闭连接时,返回值为0,如调用close

    • flags含义:

    I/O模型

    I/O复用

     select函数简介

    int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

    • maxfdp1:指定待测试的描述符个数,它的值是待测试的最大描述符加1
    • readset、writeset、exceptset:指定让内核测试读、写、异常条件的描述符
    • timeout:最长等待时间
    • timeout参数的三种可能:
      a.设为空指针:永远等待下去,仅在有描述符就绪时才返回
      b.正常设置timeout,在不超过timeout设置的时间内,在有描述符就绪时返回
      c.将timeout.tv_sec和timeout.tv_usec都设为0:检查描述符后立即返回(轮询)

    非阻塞I/O

    • 非阻塞connect 以及非阻塞accept

    • 以及调用select 的非阻塞I/O

    进程间通信

    •  Unix域协议

      

    #define UNIX_PATH_MAX 128
    struct sockaddr_un{
    sa_family_t sun_family; /* AF_UNIX 或者 AF_LOCAL */
    char sun_path[UNIX_PATH_MAX]; /* path name */
    };
    使用流程分析:
    一、服务器端通信过程分析
    服务器端基本遵循面向连接的socket数据流通信过程。
    1、调用socket()函数,建立socket对象,指定通信协议为AF_UNIX。
    2、调用bind()函数,将创建的socket对象与bind()函数产生的那个socket类型的文件server_socket P绑定。
    UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,其地址用结构体sockaddr_un表示, 网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,且已被link,则bind()错误返回。一个套接字只能绑定到一个路径上,同样的,一个路径也只能被一个套接字绑定。
    sockaddr_un结构的sun_path成员包含一路径名,当我们将一地址绑定至UNIX域套接字时,系统用该路径名创建一类型为S_IFSOCK的文件。该文件仅用于向客户端进程告知套接字名字,该文件不能打开,也不能由应用程序用于通信,当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作(unlink(path)),或删除该文件。
    struct sockaddr_un结构有两个参数:sun_family、sun_path。sun_family只能是AF_LOCAL或AF_UNIX;而sun_path就是本地文件的路径。存放文件路径的sun_path数组必须以空字符(即’\0’字符)结尾
    3、调用listen()函数,使socket对象处于监听状态,并设置监听队列大小。
    4、服务器端监听到该请求,在客户端发出请求后,accept()函数接收请求,返回新文件描述符,从而建立连接。
    5、服务器端调用read()函数接收数据(开始处于阻塞状态,等待客户端发送数据,因此,客户端在编程是需要首先发送数据,接收到数据后,输出接收到的数据)。
    6、调用write()函数发送数据到客户端。
    7、通信完成后,调用close()函数关闭socket对象;unlink(sockaddr_un.sun_path)。
     
    二、客户端通信过程分析
    客户端基本遵循面向连接的socket数据流通信过程。
    1、调用socket()函数,建立socket对象,指定相同通信协议。
    2、客户端调用connect()函数,向服务器端发起连接请求。
    3、在得到服务器端允许后,首先调用write()函数向服务器端发送消息(因服务器端循环体中首先是接收数据)。
    4、调用read()函数接收数据。
    5、通信完成后,调用close()函数关闭socket对象。
     

    管道

    #include <unistd.h>
    int pipe(int pipefd[2]);

    成功调用 pipe 函数之后,可以对写入端描述符 pipefd[1] 调用 write ,向管道里面写入数据,比如

    write(pipefd[1],wbuf,count);

    一旦向管道的写入端写入数据后,就可以对读取端描述符 pipefd[0] 调用 read

    管道有如下三条性质:
    · 只有当所有的写入端描述符都已关闭,且管道中的数据都被读出,对读取端描述符调用 read 函数
    才会返回 0 (即读到 EOF 标志)。
    · 如果所有读取端描述符都已关闭,此时进程再次往管道里面写入数据,写操作会失败, errno 被设
    置为 EPIPE ,同时内核会向写入进程发送一个 SIGPIPE 的信号。
    · 当所有的读取端和写入端都关闭后,管道才能被销毁

    这种管道因为没有实体文件与之关联,适用于有亲缘关系的任意两个进程之间通信

    命名管道 FIFO

    命名管道就是为了解决无名管道的这个问题而引入的。 FIFO 与管道类似,最大的差别就是有实体
    文件与之关联。由于存在实体文件,不相关的没有亲缘关系的进程也可以通过使用 FIFO 来实现进程之
    间的通信;

    从外表看,我是一个 FIFO 文件,有文件名,任何进程通过文件名都可以打开我

    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);

    一旦 FIFO 文件创建好了,就可以把它用于进程间的通信了。一般的文件操作函数如 open 、 read 、 write 、 close 、 unlink 等都可以用在 FIFO 文件
    上; 对 FIFO 文件推荐的使用方法是,两个进程一个以只读模式( O_RDONLY )打开 FIFO 文件,另一个以只写模式( O_WRONLY )打开 FIFO 文
    件。这样负责写入的进程写入 FIFO 的内容就可以被负责读取的进程读到,从而达到通信的目的

     System V 消息队列 信号量 共享内存

    管道和 FIFO 都是字节流的模型,这种模型不存在记录边界,如果从管道里面读出 100
    个字节,你无法确认这 100 个字节是单次写入的 100 字节,还是分 10 次每次 10 字节写入的,你也无法知
    晓这 100 个字节是几个消息。管道或 FIFO 里的数据如何解读,完全取决于写入进程和读取进程之间的约
    定;System V 消息队列是优于管道和 FIFO 的。原因是消息队列机
    制中,双方是通过消息来通信的,无需花费精力从字节流中解析出完整的消息;

    System V 消息队列比管道或 FIFO 优越的第二个地方在于每条消息都有 type 字段,消息的读取进程可
    以通过 type 字段来选择自己感兴趣的消息,也可以根据 type 字段来实现按消息的优先级进行读取,而不
    一定要按照消息生成的顺序来依次读取

    一般来说,信号量是和某种预先定义的资源相关联的。信号量元素的值,表示与之关联的资源的个数

    一旦将信号量和某种资源关联起来,就起到了同步使用某种资源的功效

    共享内存是所有 IPC 手段中最快的一种。它之所以快是因为共享内存一旦映射到进程的地址空间,
    进程之间数据的传递就不须要涉及内核了。
    回顾一下前面已经讨论过的管道、 FIFO 和消息队列,任意两个进程之间想要交换信息,都必须通
    过内核,内核在其中发挥了中转站的作用:
    · 发送信息的一方,通过系统调用( write 或 msgsnd )将信息从用户层拷贝到内核层,由内核暂存这
    部分信息。
    · 提取信息的一方,通过系统调用( read 或 msgrcv )将信息从内核层提取到应用层

     

    经验:

    • epoll 或者 select 处理事件时,可读事件时,read返回值-1,如果errno不为EAGAIN,可以认为失败,并关闭fd。read返回0,说明对方断开连接,此时也需要关闭fd。如果链路断了,如拔掉网线,需要是用keepalive来触发可写事件
    • 本地UDP发送过快也是会丢包的。非阻塞情况下的unix domain socket哪怕是STREAM的也是会丢包的
    • 使用unix socket通信相比于本地udp通信减少了校验和的计算。使用阻塞函数时,unix domain socket可以保证不丢包不乱序,但是当发送缓冲区满了的话则会阻塞。使用非阻塞操作时经测试会丢包
    • 使用setsockopt设置发送缓冲时,SO_RCVBUF和SO_SNDBUF的最大值受系统设置限制,可以使用SO_RCVBUFFORCE和SO_SNDBUFFORCE来无视系统设置
    • SIGPIPE信号,网络编程时一定要处理该信号。同样一般要设置的还有SO_REUSEADDR。当客户端close连接时,若server继续发送数据,会收到RST,继续写就会SIGPIPE
    • 网络编程对事件进行封装,提供注册回调函数,在可读、可写时进行函数调用。一般用法,针对非阻塞情况,初始化时将可读事件注册,需要写的时候先写,写不下去的时候(errno=EAGAIN)再挂上可写事件,只要发送缓冲区还有空间,就是可写的
    • 基于事件的编程框架,需要记录最后一次成功read或write的时间,如果idletime大于阈值,直接close
      • 服务器编程可以设置最大的fd个数,然后一次性申请FileEvent数组,之后由fd到事件查询代价O(1)
        • 针对非阻塞socket,connect返回EINPROGRESS时需要将fd加到可写事件监视集合中,当select()或者poll()返回可写事件时,需要用getsockopt去读SOL_SOCKET层面的SO_ERROR选项,SO_ERROR为0表示连接成功,否则为连接失败
        • epoll ET模式的处理方式。读:只要可读就一直读,一直读到返回0,或者error = EAGAIN。写:只要可写就一直写,知道数据发送完,或者errno = EAGAIN
        • socket read缓冲区最大值TCP可查看”/proc/sys/net/ipv4/tcp_rmem”, udp 65536
        • 实现定时器时通常办法是select/poll/epoll接口,精度毫秒级;还有就是新增的系统调用timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,高于poll的精度
        • 在主动关闭连接时,可以先shutdown(fd, SHUT_WR)关闭写端,等对方close时再关闭读端。这样子的好处是如果对方已经发送了一些数据,这些数据不会漏收。这就要求对端在read返回0之后关闭连接或者shutdown写端
        • 网络编程一种比较好的模型是“one loop per thread”,如果事件库不是线程安全的,则需要使用pipe或者
        • socketpair通知,子线程接受到通知(fd可读)后处理,kernel 2.6.22加入了eventfd,是更好的通知方法
        • TCP Nagle算法和TCP Delayed Ack机制可能会导致网络延时(Linux 40ms, Windows 200ms),最容易产生问题的就是"Write-Write-Read”这种模型,发送端的Nagle算法和接收端的Delayed Ack会导致一直等到接收端delayed ack超时后数据才发送出去
        • accept返回EMFILE,进程描述符用完了,无法创建新的socket,也无法close连接,会导致不断通知该可读事件,程序busy loop,cpu 100%,解决方法是事先准备一个nullfd=open(“/dev/null”),close该fd,accept,close socket,然后再nullfd=open(“/dev/null”),缺点是该方法线程不安全,多线程accept可能导致nullfd用于新socket创建,然后又处于busy loop中
    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    Cartographer源码阅读(1):程序入口
    ROS开发与常用命令
    实时Cartographer测试(1)
    Cartographer安装
    ROS安装(2)
    Linux学习和ROS安装(1)
    无法启动程序
    c# 获取端口的连接数,网站的连接数
    SignarL服务器端发送消息给客户端的几种情况
    c#操作IIS之IISHelper
  • 原文地址:https://www.cnblogs.com/codestack/p/11046706.html
Copyright © 2020-2023  润新知