套接字与文件描述符

Posted mychen06

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了套接字与文件描述符相关的知识,希望对你有一定的参考价值。

 

TCP服务器端:

int socket(int domain , int type , int protocol)
domain(协议族):常用的协议族便是IPV4(PF_INET), IPV6(PF_INET6),本地通信协议的UNIX族(PF_LOCAL)
type:数据传输类型;典型数据传输类型:面向连接的套接字(SOCK_STREAM),面向消息的套接字(SOCK_DGRAM)
protocal:具体协议;
返回套接字文件描述符,在linux中,不区分套接字和文件,统一用文件描述符来描述;

 

TCP与UDP的区别:

  1. TCP是面向连接,UDP是无连接的传输
  2. TCP保证了数据传输的正确和有序,而UDP不保证
  3. TCP数据传输是无边界的,也就是流模式(待查),UDP传输是有边界的,采用数据报模式(待查)
  4. TCP需要更多的系统资源

 

int bind(int sockfd , const struct sockaddr* servaddr , socklen_t addrlen);
给套接字分配IP地址和端口号,将套接字和相应的IP地址和端口号绑定,失败返回-1
当没有给套接字绑定IP地址和端口号(端口号指定为0)时,bind函数被调用时会分配一个临时端口号与套接字进行绑定,但是IP地址会当TCP连接建立(客户端目的IP地址)或者在此套接字上发送UDP数据报时才会绑定IP地址;当IP地址指定为通配地址(INADDR_ANY)时,由内核来选定IP地址)
int listen(int fd , int backlog)
将套接字变为可接受连接状态;内核为监听套接字维护两个队列,一个是未完成连接队列,该队列中的每一项为客户端发送的SYN字节,另一个是已完成连接队列,存储已完成连接的客户端套接字,当accept调用时,就从该队列的队头返回,当该队列为空的时候,进程进入睡眠,等待被唤醒

 

int accept(int sockfd , struct sockaddr* addr , socklen_t* addrlen) ;
接受连接,返回一个全新的套接字,称为“已连接套接字”,由内核创建的一个全新的套接字,自动完成与对应的客户端套接字的连接(三路握手),由这个全新的套接字与客户端套接字进行通信

 

TCP客户端:

int connect(int sockfd , struct sockaddr* servaddr, socklen_t addrlen)
connect函数完成两件事,如果套接字没有被绑定IP地址和端口号,内核会分配一个临时端口号,并与套接字进行绑定。如果是TCP套接字则会向服务器发起连接,进行三路握手,连接成功或失败才会返回;

 

基于TCP的服务器端/客户端函数调用过程:

技术图片

 

 

 TCP三路握手和四次挥手:

 

 

技术图片

 

 

 TCP为什么要三次握手?

  因为客户端和服务器之间要互相同步各自的初始信号,所以每一个SYN都需要一个ACK,因为服务器端的SYN和ACK可以合并发送,所以需要三次;

TCP为什么要四次挥手?

  因为客户端和服务器之间是全双工连接,每一个FIN都需要一个ACK。因为服务器端的FIN和ACK不能合并的原因是在某些情况下,客户端不再向服务器端发送数据后,服务器端可能还要向客户端发送数据,所以不能合并,所以需要四次。

为什么执行主动关闭的那端需要进入TIME_WAIT状态?

  首先ACK数据包可能在网络中丢失,所以被动关闭那端可能需要重传FIN,这就需要主动关闭那端维持状态准备重传ACK;另一个原因是网络中可能存在旧连接的一些迷路的重复分组,处于TIME_WAIT状态下的连接不能建立新的化身,TIME_WAIT状态会持续2个MSL时间,足够使得旧连接的重复分组消失在网络中,避免新连接出现旧连接的重复分组;

TCP套接字的I/O缓冲

  1. 输入缓冲和输出缓冲单独存在
  2. I/O缓冲在创建套接字的时候自动生成
  3. 关闭套接字后仍会继续传递输出缓冲中的数据
  4. 关闭套接字后会丢失输入缓冲中的数据
tcp_server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 1024

void error_handling(char* m) ;

int main(int argc , char* argv[])
{
     int listen_sock , connd_sock , readlen;
     struct sockaddr_in serv_addr , clnt_addr ;
     socklen_t addr_len;

     if(argc != 2) error_handling("argc error") ;

     listen_sock = socket(PF_INET , SOCK_STREAM , 0) ;       
     if(listen_sock == -1) error_handlind("socket error") ;

     serv_addr.sin_family = AF_INET ;
     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY) ;
     serv_addr.sin_port = htons(atoi(argv[1]));

     if(bind(listen_sock , (struct sockaddr*)&serv_addr , sizeof(serv_addr)) == -1)
         error_handling("bind error") ;

     if(listen(listen_sock , 5) == -1)  error_handling("error listen") ;

     addr_len = sizeof(clnt_addr) ;
     for(int i = 0 ; i < 5 ; i++)
     {
          connd_sock = accept(listen_sock , (struct sockaddr*)&clnt_addr , &addr_len) ;
          if(connd_sock == -1) error_handling("error accept") ;

          while(readlen = read(connd_sock , message , BUFSIZE) > 0)
              write(connd_sock , message , readlen) ;
      
          close(connd_sock) ;
     }
     
     close(listen_sock);
     return 0 ;
}        

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUFSIZE 1024

void error_handling(char* m);

int main(int argc , char* argv[])
{
    int clnt_sock;
    struct sockaddr_in serv_addr;

    if (argc != 3) error_handling("error argc");

    clnt_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (clnt_sock == -1) error_handling("error socket");

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(clnt_sock, (struct sock_addr*) & serv_addr, sizeof(serv_addr)) == -1) error_handling("connect error");

    char message[BUFSIZE];
    int sendlen , recvlen , readcnt;
    while (1)
    {
        fputs("input message(q to quit): ", stdout);
        fgets(message, BUFSIZE, stdin);

        if (message == "q
" || message == "Q
") break;

        sendlen = write(clnt_sock, message, sizeof(message));
        recvlen = 0;

        while (recvcnt = read(clnt_sock, message + recvlen, BUF_SIZE - 1) > 0)
        {
            recvlen += recvcnt;
            if (recvlen >= sendlen) break;
        }
        message[recvlen] = 0;
        fputs(message, stdout);
    }

    close(clnt_sock);

    return 0;
}

 

以上是关于套接字与文件描述符的主要内容,如果未能解决你的问题,请参考以下文章

Linux 套接字与文件描述符

套接字编程:'接受:错误的文件描述符'

什么是文件描述符?

编写一个程序, 将 a.txt 文件中的单词与 b.txt 文件中的 单词交替合并到 c.txt 文件中, a.txt 文件中的单词用回车符 分隔, b.txt 文件中用回车或空格进行分隔。(代码片段

Java(android)中socket.io的文件描述符?

Unix Socket - 啥是socket(套接字)?