Мельников В.Ю. Справочные материалы к лабораторным работам по курсу «Сети и телекоммуникации» 2010 г. Коммуникации через COM порт (интерфейс RS232) Физический уровень К COM порту компьютера подключается разъем DB9F Контакт сигнал примечание разъема DB9F 1 in DCD Определение несущей 2 in RD – receives data прием данных 3 out TD – transmit data передача данных 4 out DTR - data-terminal-ready Готовность терминала 5 SG – signal ground Сигнальное заземление 6 in DSR - data-set-ready Готовность к работе 7 out RTS - request-to-send Запрос на передачу (Данные готовы для передачи) 8 in CTS - clear-to-send Готовность к приему 9 in RI Индикатор вызова Жирным шрифтом помечены минимально необходимые сигналы. Сигналы готовности к приему данных можно заменить передачей байта данных XON(11h) / XOFF(13h) или просто игнорировать. Для обмена между двумя компьютерами и некоторыми устройствами с интерфейсом RS232 используется следующий кабель: 2 3 5 4 6 1 7 8 2 -RD 3 - TD 5 - SG 4 - DTR 76 -- RTS DSR 1 - DCD 8 - CTS DB9F (компьютер) 5 1 9 6 DB9M 1 5 6 9 Некоторые устройства с интерфейсом RS232 подключаются следующим кабелем 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 Логическая единица соответствует уровню сигнала в диапазоне +3V…+15V (обычно +12V) Логический нуль соответствует уровню сигнала в диапазоне -3V…-15V (обычно -12V) Максимальный ток 20mA. Максимальная скорость передачи 115200 бод (11520 байт/с). Максимальная длина кабеля Скорость передачи Экранированный кабель Неэкранированный кабель 2400 304м 152м 4800 304м 76м 9600 76 76 2 Формат передачи байта данных stop Start 1 2 P – бит четности 3 4 5 6 7 8 P stop start Функции работы с COM портом 1. Открываем COM порт HANDLE m_hCom=CreateFile(“COM1”, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0,NULL); 2. Настраиваем параметры порта DCB dcb; GetCommState(m_hCom, &dcb); dcb.DCBlength=sizeof(DCB); dcb.fBinary=1;//должен быть 1 dcb.BaudRate=9600; dcb.ByteSize=8; dcb.fParity=0;//игнорировать правильность принимаемых данных. dcb.fOutxCtsFlow=0;//посылать данные, игнорируя сигнал CTS от устройства dcb.fOutxDsrFlow=0;//посылать данные, игнорируя DSR от устройства dcb.fDtrControl=DTR_CONTROL_ENABLE; dcb.fRtsControl=RTS_CONTROL_ENABLE; dcb.Parity=NOPARITY; dcb.StopBits=ONESTOPBIT; DWORD dwRes=SetCommState(m_hCom, &dcb); if(dwRes==0) { char strErr[1024]; sprintf(strErr,"Ошибка настройки COM порта %d",GetLastError()); ::MessageBox(NULL,strErr,"Ошибка",MB_OK|MB_ICONERROR); CloseHandle(m_hCom); return(0); } 3. Настраиваем параметры ожидания (если хотим избежать блокировки программы без организации параллельных процессов) COMMTIMEOUTS TO; GetCommTimeouts(m_hCom,&TO); TO.ReadIntervalTimeout=100;//max интервал между приемом двух символов (или 0) TO.ReadTotalTimeoutMultiplier=100; TO.ReadTotalTimeoutConstant=100; //max время приема блока данных при вызове ReadFile(N) //равно ReadTotalTimeoutConstant+ N* ReadTotalTimeoutMultiplier //0 = время не ограничено TO.WriteTotalTimeoutConstant=100; TO.WriteTotalTimeoutMultiplier=100; //max время передачи блока данных при вызове WriteFile(N) //равно WriteTotalTimeoutConstant + N* WriteTotalTimeoutMultiplier //0 = время не ограничено SetCommTimeouts(m_hCom,&TO); 4. Почистим буфера обмена PurgeComm(m_hCom,PURGE_TXCLEAR|PURGE_RXCLEAR); 3 5. Передаем данные (При использовании параллельных процессов следует избегать одновременной работы функций WriteFile() и ReadFile() с одним портом) BYTE abBuf[256];//массив передаваемых данных DWORD dwRead= 255;//передаем байт DWORD dwAct = 0;//реально переданных байт if(WriteFile (m_hCom,abBuf, dwRead, &dwAct,NULL) && dwAct !=0) {…} 6. Принимаем данные BYTE abBuf[256];//массив для принятых данных DWORD dwRead= 255;//ждем байт DWORD dwAct = 0;//реально принятых байт if(ReadFile(m_hCom,abBuf, dwRead, &dwAct,NULL) && dwAct !=0) {…} 7. Освобождаем порт CloseHandle(m_hCom); Канальный уровень Протокол PPP (Point to Point Protocol) Не требует DTR, RTS и других управляющих сигналов 1 стартовый бит, 8 бит данных, без проверки четности, 1 стоп бит. Данные Контрольная Флаг Флаг Адрес Управляющее Протокол FFh поле 03h сумма 7Eh 7Eh 1 байт 1 байт 1 байт 2 байта 2 байта 1 байт Байт данных 7Eh заменяется на 2 байта 7D5Eh. Байт данных 7Dh заменяется на 2 байта 7D5Dh. Протокол: 0021h=IP, С021h =LCP (протокол установки соединения), С023h=PAP(Password Authentification Protocol)… 4 Технологии Ethernet Физический уровень Тонкий Ethernet 10Base-2 (длина сегмента до 185 метров) компьютер компьютер терминатор компьютер терминатор T-коннекторы Кабель – коаксиальный, разъем BNC Витая пара 10Base-T, 100 Base-T, 1000 Base-T (длина кабеля до 100 метров) компьютер компьютер компьютер компьютер компьютер Концентратор (HUB) или Коммутатор (Switch) Кабель – витая пара, разъем RJ45 Компьютер - Компьютер коммутатор компьютер RJ45 (вилка) 8 7 6 5 4 3 2 1 Коричневый Бело-коричневый Зеленый Бело-синий Синий Бело-зеленый Оранжевый Бело-оранжевый 8 7 6 5 4 3 2 1 RxRx+ TxTx+ Канальный уровень Ethernet II Преамбула Адрес Адрес Протокол получателя получателя 8 байт 6 байт 6 байт 2 байта Протоколы: IP=0800h, ARP=0806h, RARP=0835h… Широковещательный адрес=FFFFFFFFFFFFh Данные 46-15000 байт Контрольная сумма 4 байта 5 Протоколы UDP и TCP/IP Формат заголовка UDP (User Datagram Protocol) Версия IP Длина Тип обслуживания Общая длина IP-дейтаграммы в байтах 4 бита заголовка 8 бит 16 бит Идентификация IP- дейтаграммы Флаги Смещение фрагмента 16 бит 3 бита 13 бит TTL Протокол =17 Контрольная сумма заголовка 8 бит 8 бит 16 бит IP адрес отправителя 32 бита IP адрес получателя 32 бита Порт отправителя Порт получателя 16 бит 16 бит Длина UDP (8 байт заголовка + данные) Контрольная сумма UDP 16 бит 16 бит Данные (если есть) Серым цветом помечены данные IP пакета, в который инкапсулирован пакет UDP (Версия=4(Ipv4); Длина заголовка в 4 байтовых словах; Флаги – флаги фрагментизации; TTL – время жизни – максимальное число маршрутизаторов, через которые может пройти дейтаграмма). Длина заголовка в четырехбайтовых словах. Длина IP заголовка 20 байт (минимум) Длина UDP заголовка 8 байт (минимум) Фрагменты программы для работы с UDP 1. Необходимо подключить #include <Winsock2.h>//описание функций #pragma comment(lib, "Ws2_32.lib")//подключаем библиотеку 2. Запускаем WSA WSADATA wsaData; WORD wVersionRequested = MAKEWORD(1, 1); if(::WSAStartup(wVersionRequested, &wsaData)!=0) {printf(“Ошибка %X”,WSAGetLastError());return;} 3. Создаем «сокет» SOCKET S=socket(PF_INET,SOCK_DGRAM,0); if(S==INVALID_SOCKET) {printf(“Ошибка %X”,WSAGetLastError());return;} 4. Готовим адрес получателя u_long IA=inet_addr(strName);//адрес в виде 11.22.33.44 if(IA==INADDR_NONE) {//возможно задано DNS имя LPHOSTENT lphost=::gethostbyname(strName); if (lphost==NULL){printf(“Ошибка %X”,WSAGetLastError());return;} IA=((in_addr FAR *)lphost->h_addr)->s_addr; } sockaddr_in SA; memset(&SA, 0, sizeof(SA));//заполняем нулями SA.sin_family = AF_INET;//семейство протокола=IP SA.sin_addr.S_un.S_addr=IA; SA.sin_port=::htons(iPort); 5. Отправляем пакет 6 int iSend=::sendto (S,(LPCSTR)m_cstrSend,m_cstrSend.GetLength()+1,0, (sockaddr *)&SA, sizeof(SA)); if(iSend==SOCKET_ERROR) {printf(“Ошибка %X”,WSAGetLastError());return;} 6. Принимаем пакет //определяем допустимый адрес отправителя sockaddr_in SA; memset(&SA, 0, sizeof(SA)); SA.sin_family = AF_INET; SA.sin_addr.S_un.S_addr= ::htonl(INADDR_ANY);//Отправитель любой SA.sin_port=::htons(iPort); if (::bind(S, (sockaddr *)&SA, sizeof(SA))!=0) {printf(“Ошибка %X”,WSAGetLastError());return;} sockaddr_in SAfrom;//сюда запишется адрес отправителя int iSzSAfrom=sizeof(SAfrom); int iRec=::recvfrom(S,strBuf,sizeof(strBuf)-1,0,(sockaddr *)&SAfrom,&iSzSAfrom); if(iRec<0){printf(“Ошибка %X”,WSAGetLastError());return;} 7. Освобождаем ресурсы closesocket(S); Формат заголовка TCP (Transmission Control Protocol) Версия IP Длина Тип обслуживания Общая длина IP-дейтаграммы в байтах 4 бита заголовка 8 бит 16 бит Идентификация IP- дейтаграммы Флаги Смещение фрагмента 16 бит 3 бита 13 бит TTL Протокол = 6 Контрольная сумма заголовка 8 бит 8 бит 16 бит IP адрес отправителя 32 бита IP адрес получателя 32 бита Порт отправителя Порт получателя 16 бит 16 бит Порядковый номер первого байта данных в сегменте 32 бита Номер подтверждения – номер байта, который ожидает приемник 32 бита Длина Резерв Флаги управления Размер окна в байтах заголовка 6 бит 6 бит 16 бит 4 бита Контрольная сумма UDP Указатель срочности 16 бит 16 бит Данные (если есть) Серым цветом помечены данные IP пакета, в который инкапсулирован пакет UDP (Версия=4(Ipv4); Длина заголовка в 4 байтовых словах; Флаги – флаги фрагментизации; TTL – время жизни – максимальное число маршрутизаторов, через которые может пройти дейтаграмма). Длина заголовка в четырехбайтовых словах. Флаги управления: URG (вне очереди); ACK (сегмент содержит подтверждение); PSH (получатель должен пропихнуть данные из приемного буфера в приложение пользователя); RST (оборвать соединение, очистить буфер); SYN (синхронизация счетчика принимаемых байтов); FIN (передача данных завершена). 7 Длина IP заголовка 20 байт (минимум) Длина TCP заголовка 20 байт (минимум) Состояния сеанса 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 Состояние закрытия соединения (фиктивное?) Фрагменты программы для работы с TCP 1. Необходимо подключить #include <Winsock2.h>//описание функций #pragma comment(lib, "Ws2_32.lib")//подключаем библиотеку 2. Запускаем WSA WSADATA wsaData; WORD wVersionRequested = MAKEWORD(1, 1); if(::WSAStartup(wVersionRequested, &wsaData)!=0) {printf(“Ошибка %X”,WSAGetLastError());return;} 3. На стороне сервера создаем принимающий подключения «сокет» SOCKET Saccept=socket(PF_INET, SOCK_STREAM, 0); if(Saccept==INVALID_SOCKET) {printf(“Ошибка %X”,WSAGetLastError());return;} sockaddr_in SA; memset(&SA, 0, sizeof(SA)); SA.sin_family = AF_INET; SA.sin_addr.S_un.S_addr= ::htonl(INADDR_ANY);//Отправитель любой SA.sin_port=::htons(iPort); if(::bind(Saccept, (sockaddr *)&SA, sizeof(SA))!=0) {printf(“Ошибка %X”,WSAGetLastError());return;} if (::listen(Saccept, 100)!=0)//состояние=ожидание подключений {printf(“Ошибка %X”,WSAGetLastError());return;} 4. На стороне сервера запускаем процесс приема входящих подключений DWORD idThread; HANDLE h=CreateThread(NULL,0,s_LoopAccept, pAccceptData,0,&idThread); // pAccceptData – адрес объекта с информацией для работы процесса 5. Функция параллельного процесса приема входящих подключений Struct AccceptData {SOCKET Saccept;/*и прочее*/}; ULONG __stdcall s_LoopAccept(LPVOID pParam) //выполняется в нити приема входящих подключений { AccceptData *pData=(AccceptData *)pParam; while(TRUE) { 8 sockaddr_in SAfrom;//сюда запишется адрес отправителя int iSz=sizeof(SAfrom); SOCKET S=::accept(pData->Saccept,&SAfrom,&iSz); //сокет для обмена данными с if(S==INVALID_SOCKET) return(0); //какие-то проблемы с сетью или сокет закрыт //запускаем процесс приема данных ChannelData *pChannelData=new ChannelData(); pChannelData->S=S; DWORD idThread; HANDLE h=CreateThread(NULL,0,s_LoopRead, pChannelData, 0,&idThread); } return(0); } 6. Чтение данных char bufR[256]; while(TRUE) { int iRead=::recv(S, bufR,sizeof(bufR), 0); if(iRead==0)break;//на другой стороне закрыт сокет //... } ::closesocket(S); //закрываем соединение, освобождаем ресурсы 7. Подключение к серверу на стороне клиента //Готовим адрес получателя u_long IA=inet_addr(strName);//адрес в виде 11.22.33.44 if(IA==INADDR_NONE) {//возможно задано DNS имя LPHOSTENT lphost=::gethostbyname(strName); if (lphost==NULL){printf(“Ошибка %X”,WSAGetLastError());return;} IA=((in_addr FAR *)lphost->h_addr)->s_addr; } sockaddr_in SA; memset(&SA, 0, sizeof(SA));//заполняем нулями SA.sin_family = AF_INET;//семейство протокола=IP SA.sin_addr.S_un.S_addr=IA; SA.sin_port=::htons(iPort); if(::connect(S, (sockaddr *)&SA, sizeof(SA)) < 0) {printf(“Ошибка %X”,WSAGetLastError());return;} 8. Передача данных int iSend=::send(S,(char *)pBufW,iSz,0); 8. завершение сеанса ::shutdown(S,SD_SEND);//сообщаем на другой конец о закрытии связи и прекращаем передавать пакеты 9