首页 > 技术文章 > 网络编程之套接字

hangzhi 2019-08-29 07:54 原文

套接字

套接字格式

IPv4  IPv6  本地	

套接字建立连接

TCP三次握手:使用套接字建立连接

过程

服务端准备连接

  • 创建套接字

    /**
     * domain:指定套接字格式:PF_INET、PF_INET6 以及 PF_LOCAL 等
     * type:字节流TCP,数据报UDP,原始套接字
     * protocol:0
     */
    int socket(int domain, int type, int protocol)
    
  • 绑定地址

    调用bind函数把套接字和套接字地址绑定,像登记电话号码一样

    /**
     * fd:套接字
     * addr:一般地址被转为通配地址 todo
     * len:地址长度
     */
    bind(int fd, sockaddr * addr, socklen_t len)
    
  • 监听客户端连接

    调用listen函数,告诉系统内核:这个套接字用来等待客户端请求;这样系统内核会为此做好准备,如完成连接队列。

    /**
     * socketfd:套接字
     * backlog:未完成连接队列大小,决定可接收的并发数目
     */
    int listen (int socketfd, int backlog)
    
  • 接收客户端请求

    客户端请求到达,服务端应答成功,连接建立(TCP三次握手),调用accept函数通知应用程序,让其感知到这个连接,并返回给一个客户端独有的已连接套接字,用于客户端和服务器之间的通信,当TCP连接断开,那么这个套接字就会被关闭。

    如果使用的是阻塞式模型,accept会阻塞调用直到有一个连接过来。

    /**
     * listensockfd:套接字(这是经历创建/监听得到的,公用的,它一直处于监听状态)
     * cliaddr:客户端地址
     * addrlen:地址长度
     * 返回套接字:已连接套接字描述字(这是单独给当前客户端创建的,后面都使用这个套接字和客户
     * 端通信)
     */
    int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
    

客户端发起连接

  • 建立套接字

  • 向服务端发起请求

    客户端调用connect函数和服务端建立连接,客户端无需调用bind函数来绑定端口,系统会自动随机分配一个空闲的临时端口,保证端口占用不会发生冲突。

    /**
     * socketfd:连接套接字
     * servaddr:服务端地址
     */
    int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
    

    如果使用TCP套接字,那么这个connect过程将发生TCP三次握手,以下是阻塞式模型。TCP连接需要建立三次握手,是因为信道是不可靠的,所以需要保证连接双方各自收发消息的能力都正常,以可靠的传输信息。

     

套接字收发数据

一段数据流从应用程序发送端到接收端一个经历6次拷贝:用户空间(应用程序)——>发送缓冲区(系统内核)——>报文封装 * 2

发送数据

发送缓冲区:

内核缓冲区总是充满数据时会产生粘包的问题,而且w网络传输大小MTU也会限制每次发送的大小,缓冲区内的数据什么时候被发送由系统内核决定,所以缓冲区大小无限大并不会提高效率

(阻塞套接字)每次TCP连接成功建立后,内核系统会为每个连接建立发送缓冲区,如果x系统内核的发送缓冲区足够大,那么直接存储,如果不够大,应用程序会被阻塞,不返回,有点空间就先一点点的存储,等到全部存储到缓冲区中,函数才会被返回。

当TCP连接建立后,系统内核会将发送缓冲区中的数据,按照TCP/IP语义封装成TCP的MSS包,及IP的MTU包,最后走数据链路层发送出去。

/**
 * 文件写函数:系统内核向文件系统中写入字节流,size与字节流大小必须一致
 * 实现:把数据从应用程序中拷贝到操作系统内核的发送缓冲区中
 * ssize_t:成功存储到发送缓冲区的大小,不代表对端成功接收到,发送是由系统内核决定的
 */
ssize_t write (int socketfd, const void *buffer, size_t size)
/**
 * 可发送外带数据(基于TCP协议的紧急数据),用于客户端-服务端双向连接特定场景下的j紧急处理
 */
ssize_t send (int socketfd, const void *buffer, size_t size, int flags)
/**
 * 指定多重缓冲区传输数据
 */
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)

读取数据

因为在unix系统中,套接字描述本身和本地文件描述没有区别,所以都是用的处理本地文件的函数来读取数据

/**
 * socketfd:套接字描述字
 * buffer:读取缓冲区:存储读到的内容
 * ssize_t:读取到的字节数:0表示EOF,读取结束
 */
ssize_t read (int socketfd, void *buffer, size_t size)

 

推荐阅读