《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;
}