1. 程式人生 > >《TCP/IP網路程式設計》筆記5-基於TCP的伺服器端客戶端2

《TCP/IP網路程式設計》筆記5-基於TCP的伺服器端客戶端2

概述

上一節已經給出了服務端和客戶端的實現,為何還有這第二節?
a. 上一節中的回聲客戶端是有問題的

問題的來源是tcp的傳輸特性:TCP傳輸的無邊界性。這樣造成了兩個後果,第一. client端呼叫write後並無法保證資料立即傳遞,可能發生多次呼叫一次傳遞。第二,伺服器端回傳資料會不會分包?資料包太長時,則一定會分包。

b. 上一節主要是程式設計實現,還沒有tcp的工作原理

回聲客戶端的完美解決

兩種方法:1. 在客戶端按照發送長度,一個位元組一個位元組讀取 ,程式碼實現見本文最後 2.定義應用層協議

TCP工作原理

tcp傳輸無邊界,服務端發了30位元組資料,客戶端確可以一個一個讀,那麼讀之前其他的資料在哪呢?

這是因為socket的write和read函式實際上都是跟緩衝區互動的,write是將資料寫到緩衝區,而read也是從緩衝區讀資料。這些IO緩衝有以下特點:

  • IO緩衝在每個tcp套接字單獨存在
  • 建立套接字時自動生成
  • 關閉套接字,輸出緩衝區中的資料繼續傳送
  • 關閉套接字,輸入緩衝區的資料丟失

另外,tcp不會發生超出輸入緩衝區大小的資料傳輸,即不會因為緩衝區溢位而丟失資料

因為tcp中有滑動視窗傳輸協議,資料傳輸是對話式的
A:我緩衝區有50位元組的可用空間,最多給我傳50位元組
B:OK!
A:我騰出了20位元組,現在有70位元組空間了
B:OK!

tcp連線建立的三次握手

TCP是面向連線的,在資料傳輸發生之前,必須先在雙方之間建立一條連線。
A:[SYN] SEQ:1000,ACK:-

現在傳資料包序號1000,如果接受無誤,請通知我傳遞1001號資料包

B:[SYN+ACK] SEQ:2000,ACK:1001

現在傳資料包序號2000,如果接受無誤,請通知我傳遞2001號資料包,您傳送的seq1000資料包無誤,請傳送1001資料包

A:[ACK] SEQ:1001.ACK:2001

收到2000資料包,請傳送2001資料包

資料交換

A分兩個資料包(每個100位元組)向B傳遞資料的過程:
A: SEQ 1200 100 byte data
B: ACK 1301
A: SEQ 1301 100 byte data
B: ACK 1402

如果A傳送SEQ 1301後,超時無ACK應答,則重發資料包。(TCP套接字會啟動計時器等待ACK應答)

斷開連線

斷開連線需要四次握手,FIN 表示斷開連線
A:FIN SEQ 5000 ACK -
B:ACK SEQ 7500 ACK 5001
B:FIN SEQ 7501 ACK 5001
A:ACK SEQ 5001 ACK 7502

解決回聲客戶端問題的實現

// tcpclientwin.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment (lib,"ws2_32.lib") 
#define BUF_SZ 1024
int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSocket;
    SOCKADDR_IN servAddr;
    char message[BUF_SZ];
    memset(message,0,sizeof(message));
    int slen,scnt,str_len;
    if(argc!=3){
        printf("usage : %s ip port\n",argv[0]);
        return 0;
    }
    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0){
        printf("WSAStartup  failed\n");
        return 0;
    }
    hSocket = socket(PF_INET,SOCK_STREAM,0);
    if(hSocket == INVALID_SOCKET){
        printf("socket error\n");
        return 0;
    }
    memset(&servAddr,0,sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    if(connect(hSocket,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR){
        printf("connect error\n");
        return 0;
    }

    for (;;)
    {
        fgets(message,BUF_SZ,stdin);
        if(!strcmp(message,"q\n")){
            break;
        }
        char msg[] = "hello server";
        str_len = send(hSocket,msg,strlen(msg),0);
        slen = 0;
        while(slen<str_len){
            scnt = recv(hSocket,&message[slen],1,0);
            if (scnt == -1)
            {
                printf("recv error\n");
            }
            slen += scnt;
        }
        message[slen] = 0;
        printf("message from server:%s\n",message);
    }

    closesocket(hSocket);
    WSACleanup();
    return 0;
}