НАЦИОНАЛЬНЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ УКРАИНЫ
“Киевский политехнический институт”
ННК «ИПСА»
Кафедра «СП»
Протокол лабораторной работы №1
По курсу: Компьютерные сети
Сделал:
студент группы ДА-71
Ткачов Паша
Вариант № 11
Киев 2010
Цель работы
Часть 1
Ознакомиться с технологией использования протоколов UDP и TCP. Рассмотреть их различия, преимущества и недостатки. Рассмотреть особенности использования этих протоколов на примере программы для пересылки данных между сервером и клиентом.
Вариант 11
Цель работы : ознакомится с протоколами передачи данных транспортного/сетевого уровня стека TCP/IP. Протоколы с установлением соединения и протоколы без установления соединения.
Для обеспечения сетевых коммуникаций используются сокеты. Сокет это конечная точка сетевых коммуникаций. Каждый использующийся сокет имеет тип и ассоциированный с ним процесс. Сокеты существуют внутри коммуникационных доменов. Домены это абстракции, которые подразумевают конкретную структуру адресации и множество протоколов, которое определяет различные типы сокетов внутри домена. Примерами коммуникационных доменов могут быть: UNIX домен, Internet домен, и т.д.
В Internet домене сокет - это комбинация IP адреса и номера порта, которая однозначно определяет отдельный сетевой процесс во всей глобальной сети Internet. Два сокета, один для хоста-получателя, другой для хоста-отправителя, определяют соединение для протоколов, ориентированных на установление связи, таких, как TCP.
Создание сокета
Привязка к локальным именам
Установление связи
Передача данных
Закрывание сокетов
Обзор функций для организации сокетов UDP или TCP
Для инициализации Winsock вызываем функцию WSAStartup
Код:
int WSAStartup( WORD wVersionRequested, (in) LPWSADATA lpWSAData (out) );
Параметр WORD wVersionRequested - младший байт - версия, старший байт - под.версия, интерфейса Winsock. Возможные версии - 1.0, 1.1, 2.0, 2.2... Для "сборки" этого параметра используем макрос MAKEWORD. Например: MAKEWORD (1, 1) - версия 1.1. Более поздние версии отличаются наличием новых функций и механизмов расширений. Параметр lpWSAData - указатель на структуру WSADATA. При возврате из функции данная структура содержит информацию о проинициализированной нами версии WinsockAPI.
С точки зрения WinsockAPI сокет - это дескриптор, который может получать или отправлять данные. На практике всё выглядит так: мы создаём сокет с определёнными свойствами и используем его для подключения, приёма/передачи данных и т.п. Итак, создавая сокет мы должны указать его параметры: сокет использует TCP/IP протокол или IPX (если TCP/IP, то какой тип и т.д.). Мы можем создать два основных типа сокетов работающих по TCP/IP протоколу - SOCK_STREAM и SOCK_DGRAM. Разница в том, что для первого типа сокетов (их еще называют TCP или connection-based socket), для отправки данных сокет должен постоянно поддерживать соединение с адресатом, при этом доставка пакета адресату гарантирована. Во втором случае наличие постоянного соединения не нужно, но информацию о том, дошел ли пакет, или нет - получить невозможно (так называемые UDP или connectionless sockets). И первый и второй типы сокетов имеют своё практическое применение. Начнём наше знакомство с сокетами с TCP (connection-based) сокетов. Для начала объявим его:
Код:
   SOCKET s;
Создать сокет можно с помощью функции socket
Код:
SOCKET socket ( int af (in),          // протокол (TCP/IP, IPX...)                int type (in),        // тип сокета (SOCK_STREAM/SOCK_DGRAM)                int protocol (in)     // для Windows приложений может быть 0              );
Для того что бы послать данные используем функцию send
Код:
int send(SOCKET s,              // сокет- отправитель         const char FAR *buf,   // указатель на буффер с данными         int len,               // длинна данных         int flags              // флаги (может быть 0)        );
Принять данные от машины с которой мы предварительно установили соединение позволяет функция recv.
Код:
int recv(SOCKET s,         // сокет- получатель         char FAR *buf,    // адрес буфера для приёма данных          int len,          // длинна буфера для приёма данных         int flags         // флаги (может быть 0)        );
MSG_DONTROUTE - указывает на то, что в отправляемое сообщение, не включатся информация о маршрутизации. Однако Winsock service provider может игнорировать этот флаг при доставке сообщения. Используется для отладки. Адрес назначения - локальный. То есть данные могут быть доставлены только на машины, соединенные напрямую. MSG_OOB - Сообщение является OOB данными. (Out Of Band) То есть, такое сообщение передаётся вне потока. Это значит, что при отправке сообщения, транспортный протокол не ждёт полного заполнения буфера, а отсылает сообщение немедленно. Данный флаг можно использовать при передаче приоритетных данных. При использовании MSG_OOB, Winsock-приложения поддерживающие связь, должны заранее "договориться" об использовании этого флага.
MSG_PEEK - Данные копируются в принимающий буфер, но из очереди сообщений не изымаются. Функция возвращает количество принятых на данный момент байт данных.
Процедура закрытия активного соединения происходит с помощью функций shutdown и closesocket. Различают два типа закрытия соединений: abortive и graceful. Первый вид - это экстренное закрытие сокета (closesocket). В таком случае соединение разрывается моментально. Вызов closesocket имеет мгновенный еффект. После вызова closesocket сокет уже недоступен.
Код:
int shutdown(SOCKET s,     // Закрываемый сокет             int how       // Способ закрытия           );
Код:
int closesocket(SOCKET s   // Закрываемый сокет               );
Для того, что бы узнать IP адрес машины зная ёё имя, существует функция gethostbyname. Для получения имени машины по ёё адресу используем функцию gethostbyaddr. Рассмотрим эти функции подробнее.
Код:
struct hostent FAR *gethostbyname(const char FAR *name );
Код:
   hostent* d_addr; // Структура, в которую будет помещен IP адрес, // при возврате.   hostent* hn = gethostbyname ("www.google.com");   //...
Если функция отработала успешно, то структура hn содержит нужные нам данные, иначе gethostbyname возвращает NULL; Рассмотрим структуру hn.
Код:
struct hostent {   char FAR * h_name;               // Официальное имя машины   char FAR * FAR * h_aliases;      // Массив альтернативных имен машины // (заканчивающийся 0)   short h_addrtype;                // Тип адреса (AF_INET...)   short h_length;                  // Длина адреса в байтах   char FAR * FAR * h_addr_list;    // Список адресов (заканчивающийся 0)};
Итак, после вызова gethostbyname нас интересуют следующие поля этой структуры: h_name - можем узнать официальное имя машины :)h_addrtype - тип адреса (для TCP/IP должно быть AF_INET)h_length - длинна адреса (для TCP/IP V4 - 4 байта)h_addr_list - массив адресов (заканчивающийся 0).Для получения первого адреса в массиве, можно использовать макрос h_addr_list[0]. Для того, что бы получить адрес интересующей нас машины, необходимо вызвать gethostbyname (имя машины) и проанализировав полученные назад данные, выбрать из массива h_addr_list ее адрес. Адрес машины сохраним в структуре sockaddr_in
tcpClient.cpp:
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>
#define PORT 123
#define SERVERADDR "127.0.0.1"
int main(int argc, char* argv[])
{
char buff[1024];
if (WSAStartup(0x202,(WSADATA *)&buff[0]))
{
printf("WSAStart error %d\n",WSAGetLastError());
return -1;
}
SOCKET my_sock;
my_sock=socket(AF_INET,SOCK_STREAM,0);
if (my_sock < 0)
{
printf("Socket() error %d\n",WSAGetLastError());
return -1;
}
sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(PORT);
HOSTENT *hst;
if (inet_addr(SERVERADDR)!=INADDR_NONE)
dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
else
if (hst=gethostbyname(SERVERADDR))
((unsigned long *)&dest_addr.sin_addr)[0]=
((unsigned long **)hst->h_addr_list)[0][0];
else
{
printf("Invalid address %s\n",SERVERADDR);
closesocket(my_sock);
WSACleanup();
return -1;
}
if (connect(my_sock,(sockaddr *)&dest_addr,
sizeof(dest_addr)))
{
printf("Connect error %d\n",WSAGetLastError());
return -1;
}
printf("Connection with %s success\n\
Type quit for quit\n\n",SERVERADDR);
int nsize;
while((nsize=recv(my_sock,&buff[0],
sizeof(buff)-1,0))
!=SOCKET_ERROR)
{
buff[nsize]=0;
printf("Server: %s",buff);
printf("Client: "); fgets(&buff[0],sizeof(buff)-1,
stdin);
if (!strcmp(&buff[0],"quit\n"))
{
printf("Exit...");
closesocket(my_sock);
WSACleanup();
return 0;
}
send(my_sock,&buff[0],nsize,0);
}
printf("Recv error %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}
tcpServer.cpp:
#include <stdio.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#include <winsock2.h>
#include <windows.h>
#define MY_PORT 123
#define PRINTNUSERS if (nclients)
printf("%d user on-line\n",nclients);
else printf("No User on line\n");
DWORD WINAPI SupClient(LPVOID client_socket);
int nclients = 0;
int main(int argc, char* argv[])
{
char buff[1024];
if (WSAStartup(0x0202,(WSADATA *) &buff[0]))
{
printf("Error WSAStartup %d\n",
WSAGetLastError());
return -1;
}
SOCKET mysocket;
if ((mysocket=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Error socket %d\n",WSAGetLastError());
WSACleanup();

return -1;
}
sockaddr_in local_addr;
local_addr.sin_family=AF_INET;
local_addr.sin_port=htons(MY_PORT);
local_addr.sin_addr.s_addr=0;

if (bind(mysocket,(sockaddr *) &local_addr, sizeof(local_addr)))
{
printf("Error bind %d\n",WSAGetLastError());
closesocket(mysocket);
WSACleanup();
return -1;
}
if (listen(mysocket, 0x100))
{
printf("Error listen %d\n",WSAGetLastError());
closesocket(mysocket);
WSACleanup();
return -1;
}
printf("Waiting connection\n");
SOCKET client_socket;
sockaddr_in client_addr;
int client_addr_size=sizeof(client_addr);
while((client_socket=accept(mysocket, (sockaddr *)
&client_addr, &client_addr_size)))
{
nclients++;
HOSTENT *hst;
hst=gethostbyaddr((char *)
&client_addr.sin_addr.s_addr,4, AF_INET);
printf("+%s [%s] new connect!\n",
(hst)?hst->h_name:"",
inet_ntoa(client_addr.sin_addr));
PRINTNUSERS
DWORD thID;
CreateThread(NULL,NULL,SupClient,
&client_socket,NULL,&thID);
}
return 0;
}
DWORD WINAPI SupClient(LPVOID client_socket)
{
SOCKET my_sock;
my_sock=((SOCKET *) client_socket)[0];
char buff[20*1024];
#define sHELLO "You are connected.\r\n"
send(my_sock,sHELLO,sizeof(sHELLO),0);
#define sRECIVED "Message recived.\r\n"
int bytes_recv;
while((bytes_recv = recv(my_sock,&buff[0],sizeof(buff),0))&& bytes_recv != SOCKET_ERROR)
{
std::cout<<"Client: "<<buff;
send(my_sock, sRECIVED, sizeof(sRECIVED), 0);
}
nclients--;
printf("-disconnect\n"); PRINTNUSERS
closesocket(my_sock);
return 0;
}
udpClient.cpp:
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>
#define PORT 123
#define SERVERADDR "127.0.0.1"
int main(int argc, char* argv[])
{
char buff[10*1014];
printf("\nType quit to quit\n");
if (WSAStartup(0x202,(WSADATA *)&buff[0]))
{
printf("WSAStartup error: %d\n",
WSAGetLastError());
return -1;
}
SOCKET my_sock=socket(AF_INET, SOCK_DGRAM, 0);
if (my_sock==INVALID_SOCKET)
{
printf("socket() error: %d\n",WSAGetLastError());
WSACleanup();
return -1;
}
HOSTENT *hst;
sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(PORT);
if (inet_addr(SERVERADDR))
dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
else
if (hst=gethostbyname(SERVERADDR))
dest_addr.sin_addr.s_addr=((unsigned long **)
hst->h_addr_list)[0][0];
else
{
printf("Unknown host: %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}
while(1)
{
printf("Client: ");fgets(&buff[0],sizeof(buff)-1,
stdin);
if (!strcmp(&buff[0],"quit\n")) break;
sendto(my_sock,&buff[0],strlen(&buff[0]),0,
(sockaddr *) &dest_addr,sizeof(dest_addr));
sockaddr_in server_addr;
int server_addr_size=sizeof(server_addr);
int n=recvfrom(my_sock,&buff[0],sizeof(buff)-1,0,
(sockaddr *) &server_addr, &server_addr_size);
if (n==SOCKET_ERROR)
{
printf("recvfrom() error:"\
"%d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}
buff[n]=0;
printf("Server: %s",&buff[0]);
}
closesocket(my_sock);
WSACleanup();
return 0;
}
udpServer.cpp:
#pragma comment(lib,"ws2_32.lib")
#include <stdio.h>
#include <winsock2.h>
#define PORT 123
int main(int argc, char* argv[])
{
char buff[1024];
printf("Waiting for connections\n");
if (WSAStartup(0x202,(WSADATA *) &buff[0]))
{
printf("WSAStartup error: %d\n",
WSAGetLastError());
return -1;
}
SOCKET my_sock;
my_sock=socket(AF_INET,SOCK_DGRAM,0);
if (my_sock==INVALID_SOCKET)
{
printf("Socket() error: %d\n",WSAGetLastError());
WSACleanup();
return -1;
}
sockaddr_in local_addr;
local_addr.sin_family=AF_INET;
local_addr.sin_addr.s_addr=INADDR_ANY;
local_addr.sin_port=htons(PORT);
if (bind(my_sock,(sockaddr *) &local_addr,
sizeof(local_addr)))
{
printf("bind error: %d\n",WSAGetLastError());
closesocket(my_sock);
WSACleanup();
return -1;
}
while(1)
{
sockaddr_in client_addr;
int client_addr_size = sizeof(client_addr);
int bsize=recvfrom(my_sock,&buff[0],
sizeof(buff)-1,0,
(sockaddr *) &client_addr, &client_addr_size);
if (bsize==SOCKET_ERROR)
printf("recvfrom() error: %d\n",
WSAGetLastError());
HOSTENT *hst;
hst=gethostbyaddr((char *)
&client_addr.sin_addr,4,AF_INET);
printf("+%s [%s:%d] new Datagram\n",
(hst)?hst->h_name:"Unknown host",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
buff[bsize]=0;
printf("Client: %s\n",&buff[0]);
#define sRECIVED "Message recived.\r\n"
sendto(my_sock,sRECIVED,sizeof(sRECIVED),0,
(sockaddr *)&client_addr, sizeof(client_addr));
}
return 0;
}


Выводы
TCP — это транспортный механизм, предоставляющий поток данных, с предварительной установкой соединения, за счёт этого дающий уверенность в достоверности получаемых данных, осуществляет повторный запрос данных в случае потери данных и устраняет дублирование при получении двух копий одного пакета. В отличие от UDP, гарантирует, что приложение получит данные точно в такой же последовательности, в какой они были отправлены, и без потерь. Реализация TCP, как правило, встроена в ядро системы, хотя есть и реализации TCP в контексте приложения. Когда осуществляется передача от компьютера к компьютеру через Internet, TCP работает на верхнем уровне между двумя конечными системами, например, интернет-браузер и интернет-сервер. Также TCP осуществляет надежную передачу потока байт от одной программы на некотором компьютере в другую программу на другом компьютере. Программы для электронной почты и обмена файлами используют TCP. TCP контролирует длину сообщения, скорость обмена сообщениями, сетевой трафик.
UDP (англ. User Datagram Protocol — протокол пользовательских датаграмм) — это транспортный протокол для передачи данных в сетях IP без установления соединения. Он является одним из самых простых протоколов транспортного уровня модели OSI. Его IP-идентификатор-0x11. В отличие от TCP, UDP не гарантирует доставку пакета, поэтому аббревиатуру иногда расшифровывают как Unreliable Datagram Protocol (протокол ненадёжных датаграмм). Это позволяет ему гораздо быстрее и эффективнее доставлять данные для приложений, которым требуется большая пропускная способность линий связи, либо требуется малое время доставки данных.
Часть 2
Флаги (управляющие биты)
Это поле содержит 6 битовых флагов:
URG — Поле «Указатель важности» задействовано (англ. Urgent pointer field is significant)
ACK — Поле «Номер подтверждения» задействовано (англ. Acknowledgement field is significant)
PSH — (англ. Push function) инструктирует получателя протолкнуть данные, накопившиеся в приемном буфере, в приложение пользователя
RST — Оборвать соединения, сбросить буфер (очистка буфера) (англ. Reset the connection)
SYN — Синхронизация номеров последовательности (англ. Synchronize sequence numbers)
FIN (англ. final, бит) — флаг, будучи установлен, указывает на завершение соединения (англ. FIN bit used for connection termination).
Контрольная сумма
Поле контрольной суммы — это 16-битное дополнение суммы всех 16-битных слов заголовка и текста. Если сегмент содержит нечетное число октетов в заголовке /или тексте, последние октеты дополняются справа 8 нулями для выравнивания по 16-битовой границе. Биты заполнения (0) не передаются в сегменте и служат только для расчёта контрольной суммы. При расчёте контрольной суммы значение самого поля контрольной суммы принимается равным 0.
Указатель важности
16-битовое значение положительного смещения от порядкового номера в данном сегменте. Это поле указывает порядковый номер октета которым заканчиваются важные (urgent) данные. Поле принимается во внимание только для пакетов с установленным флагом URG.
Механизм действия протокола
В отличие от традиционной альтернативы — UDP, который может сразу же начать передачу пакетов, TCP устанавливает соединения, которые должны быть созданы перед передачей данных. TCP соединение можно разделить на 3 стадии:
Установка соединения
Передача данных
Завершение соединения
Состояния сеанса TCP

Упрощённая диаграмма состояний TCP. Более подробно в TCP EFSM diagram (на английском языке)
Состояния сеанса TCP

CLOSED
Начальное состояние узла. Фактически фиктивное

LISTEN
Сервер ожидает запросов установления соединения от клиента

SYN-SENT
Клиент отправил запрос серверу на установление соединения и ожидает ответа

SYN-RECEIVED
Сервер получил запрос на соединение, отправил ответный запрос и ожидает подтверждения

ESTABLISHED
Соединение установлено, идёт передача данных

FIN-WAIT-1
Одна из сторон (назовём её узел-1) завершает соединение, отправив сегмент с флагом FIN

CLOSE-WAIT
Другая сторона (узел-2) переходит в это состояние, отправив, в свою очередь сегмент ACK и продолжает одностороннюю передачу

FIN-WAIT-2
Узел-1 получает ACK, продолжает чтение и ждёт получения сегмента с флагом FIN

LAST-ACK
Узел-2 заканчивает передачу и отправляет сегмент с флагом FIN

TIME-WAIT
Узел-1 получил сегмент с флагом FIN, отправил сегмент с флагом ACK и ждёт 2*MSL секунд, перед окончательным закрытием соединения

CLOSING
Обе стороны инициировали закрытие соединения одновременно: после отправки сегмента с флагом FIN узел-1 также получает сегмент FIN, отправляет ACK и находится в ожидании сегмента ACK (подтверждения на свой запрос о разъединении)

Установка соединения
Процесс начала сеанса TCP называется «тройным рукопожатием».
1. Клиент, который намеревается установить соединение, посылает серверу сегмент с номером последовательности и флагом SYN.
Сервер получает сегмент, запоминает номер последовательности и пытается создать сокет (буфера и управляющие структуры памяти) для обслуживания нового клиента.
В случае успеха сервер посылает клиенту сегмент с номером последовательности и флагами SYN и ACK, и переходит в состояние SYN-RECEIVED.
В случае неудачи сервер посылает клиенту сегмент с флагом RST.
2. Если клиент получает сегмент с флагом SYN, то он запоминает номер последовательности и посылает сегмент с флагом ACK.
Если он одновременно получает и флаг ACK (что обычно и происходит), то он переходит в состояние ESTABLISHED.
Если клиент получает сегмент с флагом RST, то он прекращает попытки соединиться.
Если клиент не получает ответа в течение 10 секунд, то он повторяет процесс соединения заново.
3. Если сервер в состоянии SYN-RECEIVED получает сегмент с флагом ACK, то он переходит в состояние ESTABLISHED.
В противном случае после тайм-аута он закрывает сокет и переходит в состояние CLOSED.
Процесс называется «тройным рукопожатием», так как несмотря на то что возможен процесс установления соединения с использованием 4 сегментов (SYN в сторону сервера, ACK в сторону клиента, SYN в сторону клиента, ACK в сторону сервера), на практике для экономии времени используется 3 сегмента.
Пример базового 3-этапного согласования:
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
В строке 2 на рисунке 7 TCP A начинает передачу сегмента SYN, говорящего об использовании номеров последовательности, начиная со 100. В строке 3 TCP B передает SYN и подтверждение для принятого SYN в адрес TCP A. Надо отметить, что поле подтверждения показывает ожидание TCP B приема номера последовательности 101, подтверждающего SYN с номером 100.
В строке 4 TCP A отвечает пустым сегментом с подтверждением ACK для сегмента SYN от TCP B; в строке 5 TCP A передает некоторые данные. Отметим, что номер последовательности сегмента в строке 5 совпадает с номером в строке 4, поскольку ACK не занимает пространства номеров последовательности (если это сделать, придется подтверждать подтверждения — ACK для ACK!).
Передача данных
При обмене данными приемник использует номер последовательности, содержащийся в получаемых сегментах, для восстановления их исходного порядка. Приемник уведомляет передающую сторону о номере последовательности, до которой он успешно получил данные, включая его в поле «номер подтверждения». Все получаемые данные, относящиеся к промежутку подтвержденных последовательностей, игнорируются. Если полученный сегмент содержит номер последовательности больший, чем ожидаемый, то данные из сегмента буферизируются, но номер подтвержденной последовательности не изменяется. Если впоследствии будет принят сегмент, относящийся к ожидаемому номеру последовательности, то порядок данных будет автоматически восстановлен исходя из номеров последовательностей в сегментах.
Для того, чтобы передающая сторона не отправляла данные интенсивнее, чем их может обработать приемник, TCP содержит средства управления потоком. Для этого используется поле «окно». В сегментах, направляемых от приемника передающей стороне в поле «окно» указывается текущий размер приемного буфера. Передающая сторона сохраняет размер окна и отправляет данных не более, чем указал приемник. Если приемник указал нулевой размер окна, то передача данных в направлении этого узла не происходит, до тех пор пока приемник не сообщит о большем размере окна.
В некоторых случаях передающее приложение может явно затребовать протолкнуть данные до некоторой последовательности принимающему приложению, не буферизируя их. Для этого используется флаг PSH. Если в полученном сегменте обнаруживается флаг PSH, то реализация TCP отдает все буферизированные на текущий момент данные принимающему приложению. «Проталкивание» используется, например, в интерактивных приложениях. В сетевых терминалах нет смысла ожидать ввода пользователя после того, как он закончил набирать команду. Поэтому последний сегмент, содержащий команду, обязан содержать флаг PSH, чтобы приложение на принимающей стороне смогло начать её выполнение.
Завершение соединения
Завершение соединения можно рассмотреть в три этапа:
Посылка серверу от клиента флагов FIN и ACK на завершение соединения.
Сервер посылает клиенту флаги ответа ACK , FIN, что соединение закрыто.
После получения этих флагов клиент закрывает соединение и в подтверждение отправляет серверу ACK , что соединение закрыто.
1. Прием по адресу http://cad/~valex/netz.php?variant=03&group=71 через WireShark сеанса ТСР:

2. Создание графиков через программу tcptrace:

3.Прорисовка графиков через xplot: