套接字與文件描述符


 

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. 關閉套接字后會丟失輸入緩沖中的數據

如何優雅的斷開套接字連接(半關閉數據流):

int shutdown(int sock , int howto);

howto:SHUT_RD(斷開輸入流) , SHUT_WR(斷開輸出流),SHUT_RDWR(斷開輸入輸出流);

為什么要半關閉數據流?

  當服務器文件傳輸結束但客戶端還有數據傳輸的時候,如果直接調用close關閉套接字表示文件傳輸結束,那么也無法接收客戶端傳來的數據。在此種應用場景之下,可以調用shutdown函數關閉輸出流表示文件傳輸結束,但是此時輸入流並沒有關閉,可以繼續接收數據傳輸;

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\n" || message == "Q\n") 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;
}

 

基於UDP的數據傳輸:

  1. 雙方通信只各自需要一個套接字
  2. 通信之前不需要建立連接,通信結束后也不需要斷開連接;
  3. 通信數據存在數據邊界

UDP的數據I/O函數:

ssize_t sendto(int sockfd ,  void *buff  , size_t ntypes , int flags , struct sockaddr* to , socklen_t addrlen) ;

ssize_t recvfrom(int sockfd , void *buff , size_t ntypes , int flags , struct sockaddr* from , socklen_t* addrlen) ;

如果在調用I/O函數之前沒有給套接字分配IP地址和端口號,那么在第一次調用sendto函數會自動向套接字分配IP地址和端口號;

sendto函數調用過程:

  1. 向套接字注冊目標地址
  2. 數據傳輸
  3. 套接字清除目標地址

如果要多次向同一個目標地址傳輸數據,可以向套接字注冊目標地址(調用connect函數,如何清除呢?),變成已連接套接字;

阻塞I/O和非阻塞I/O:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM