天天看點

socket網絡通信<二>

socket網絡通信

本文注意基于socket來分析TCP連接配接建立過程。

先回顧一下TCP連接配接建立過程:

socket網絡通信<二>

主機A運作的是TCP用戶端程式,主機B運作的是TCP伺服器程式,最初兩端TCP程序處于Closed态,A主動打開連接配接,對應用戶端connect函數發起連接配接,B被動接受連接配接,對應于伺服器listen函數。

伺服器TCP程序先建立傳輸控制塊TCB,準備接受客戶程序的連接配接請求;

1 用戶端程序也首先建立TCB,然後通過connect函數向伺服器發送連接配接請求,同步位SYN=1(不攜帶資料),初始序列号seq=x,用戶端進入SYN-SENT态;

2 伺服器一直通過listen監控socket,接收到連接配接請求後,如同意連接配接,向用戶端發射确認,ACK=1,SYN=1,ack=x+1,seq=y;伺服器端進入SYN-RCVD;

3 用戶端程序收到确認後,這時connect函數傳回,對伺服器程序再确認,ACK=1,seq=x+1,ack=y+1,用戶端進入連接配接态ESTABLISHED 可讀寫;伺服器收到再次确認後也進入ESTABLISHED 可讀寫。

對應socket可總結如下:

socket網絡通信<二>

總結:用戶端的connect在三次握手的第二次傳回,而伺服器端的accept在第三次握手的第三次傳回。

TCP連接配接釋放過程:

socket網絡通信<二>

TCP程序雙方都處于ESTABLISHED态進行讀寫通信,如果要釋放連接配接:

1 用戶端程序向伺服器發出釋放連接配接封包段,FIN=1,seq=u,并停止發送資料,此時用戶端進入FIN-WAIT-1;

2 伺服器收到釋放封包請求後,發出确認ACK=1,ack=u+1,seq=v,然後進入CLOSED-WAIT态,用戶端收到确認消息後進入FIN-WAIT-2态,并等待伺服器發出連接配接釋放封包;

3 伺服器發出連接配接釋放請求,FIN=1,ACK=1,ack=u+1,seq=w,并進入LAST-ACK;

4 用戶端收到伺服器釋放連接配接請求後,發出确認,ACK=1,seq=u+1,ack=w+1,然後進入TIME-WAIT狀态,此時還需在等2MSL(最長封包段壽命)後再進入CLOSED。B收到确認消息後立刻進入CLOSED。

對應socket可總結如下:

socket網絡通信<二>

某個應用程序首先調用close主動關閉連接配接,這時TCP發送一個FIN M;

另一端接收到FIN M之後,執行被動關閉,對這個FIN進行确認。它的接收也作為檔案結束符傳遞給應用程序,因為FIN的接收意味着應用程序在相應的連接配接上再也接收不到額外資料;一段時間之後,接收到檔案結束符的應用程序調用close關閉它的socket。這導緻它的TCP也發送一個FIN N;接收到這個FIN的源發送端TCP對它進行确認。這樣每個方向上都有一個FIN和ACK。

最後,通過例程實踐,例程内容:

下面編寫一個簡單的伺服器、用戶端(使用TCP)——伺服器端一直監聽本機的6666号端口,如果收到連接配接請求,将接收請求并接收用戶端發來的消息;用戶端與伺服器端建立連接配接并發送一條消息

伺服器端代碼:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    listenfd, connfd;
    struct sockaddr_in     servaddr;
    char    buff[4096];
    int     n;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
    printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }//調用socket函數

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//注意網絡位元組序與本機位元組序
    servaddr.sin_port = htons(6666);

    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }//将socket綁定IP和端口

    if( listen(listenfd, 10) == -1){
    printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }//listen 該端口下的socket,輸入隊列額度10

    printf("======waiting for client's request======\n");
    while(1){
    if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
        continue;
    }//accept來自用戶端程序,注意此時産生一個已建立socket描述符confd
           
n = recv(connfd, buff, MAXLINE, 0);//讀入到buff
    buff[n] = '\0';
    printf("recv msg from client: %s\n", buff);
    close(connfd);//結束
    }

    close(listenfd);
}

           

用戶端代碼:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    sockfd, n;
    char    recvline[4096], sendline[4096];
    struct sockaddr_in    servaddr;

    if( argc != 2){
    printf("usage: ./client <ipaddress>\n");
    exit(0);
    }

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
    exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
    printf("inet_pton error for %s\n",argv[1]);
    exit(0);
    }

    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
    printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }//發送請求

    printf("send msg to server: \n");
    fgets(sendline, 4096, stdin);
    if( send(sockfd, sendline, strlen(sendline), 0) < 0)
    {
    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
    exit(0);
    }//send函數,發送sendline裡内容

    close(sockfd);
    exit(0);
}

           

這是一個最基本socket通信例程,上面的伺服器使用的是疊代模式的,即隻有處理完一個用戶端請求才會去處理下一個用戶端的請求,這樣的伺服器處理能力是很弱的,現實中的伺服器都需要有并發處理。

繼續閱讀