计算机网络实验设计-网络聊天程序的实现

2023-12-23 19:30

本文主要是介绍计算机网络实验设计-网络聊天程序的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

网络聊天程序的实现

一.设计题目

网络聊天程序的设计与实现

二.设计内容

1.设计主要内容

了解Socket通信原理,会使用Socket进行进行简单的网络编程,在此基础上设计编写一聊天程序,能够实现通过运行程序,两台计算机之间可以通信。

2.知识要点介绍

WinSock 并不是一种网络协议,它只是一个网络编程接口,也就是说,它不是协议,但是它可以 访问很多种网络协议,你可以把它当作一些协议的封装。现在的 WinSock 已经基本上实现了与协议无关。你可以使用 WinSock 来调用多种协议的功能。可以通过调用 WinSock 的接口函数来调用 TCP/IP 的各种功能。
TCP/IP 协议包含的范围非常的广,它是一种四层协议,包含了各种硬件、软件需求的定义。TCP/IP 协议确切的说法应该是 TCP/UDP/IP 协议。UDP 协议(User Datagram Protocol 用户数据报协议),是一 种保护消息边界的,不保障可靠数据的传输。TCP 协议(Transmission Control Protocol 传输控制协议),是一种流传输的协议。他提供可靠的、有序的、双向的、面向连接的传输。 保护消息边界,就是指传输协议把数据当作一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。

三.设计步骤

首先,对于任何基于 WinSock 的编程首先必须要初始化 WinSock DLL 库。int WSAStarup( WORD wVersionRequested,LPWSADATA lpWsAData )wVersionRequested 是我们要求使用的 WinSock 的版本。 调用这个接口函数可以初始化 WinSock 。
然后必须创建一个套接字(Socket)。SOCKET Socket(int af,int type,int protocol); 套接字可以说是 WinSock 通讯的核心。WinSock 通讯的所有数据传输,都是通过套接字来完成的,套接字包含了两个信息,一个是 IP 地址,一个是 Port 端口号,使用这两个信息,就可以确定网络中 的任何一个通讯节点。
在这里插入图片描述
然后使用 Socket()接口函数创建了一个套接字后,必须把套接字与你需要进行通讯的地址建立联计算机网络系,可以通过绑定函数 bind 来实现这种联系。

四.调试过程

在对程序进行调试的过程中,首先将程序运行在同一台电脑上,也就是让服务端进程和客户端进程同时运行在同一台电脑上,这时候客户端访问的IP地址可以为127.0.0.1,也可以通过ipconfig查看本机的真实地址,然后请求连接,连接成功后进行测试。
在这里插入图片描述
当程序在本机可以跑,并且客户端和服务端之间可以实现简单的通信,说明在本机程序是可以跑的,此时选择将客户端或者是服务端放到其他电脑上,将客户端的ip地址改为运行了服务端的那台电脑的ip地址,再次测试是否可以正常运行。
如何发现同步:
通过查阅资料,我找到了两种解决办法。第一种是开启一个收文件的线程,让其在后台不断地执行recvfrom并打印在控制台上。第二个是使用select函数,通过检测套接字状态,使用read和write函数对文件描述符进行读写。也可以实现同步收发消息。我选择了第二种方式。
关键代码:
FD_ISSET(con, &rfds) //返回1则表示此时可读,con为套接字描述字
FD_ISSET(0, &rfds) //返回1则表示此时可写,文件描述符0即为stdin
send(con, buf, sizeof(buf), 0);
recv(con, buffer, sizeof(buffer), 0).

五.设计结果及结果分析

1.实验结果截图展示

在这里插入图片描述
在这里插入图片描述
实验结果展示图(上:服务端,下:客户端)

2.实验结果分析

本实验实现的聊天程序,即通过使用socket编程,利用协议接口,ip与端口号实现程序之间的通信。在此过程中主要的就是对于Socket编程接口的使用。在程序中我们使用绑定bind()、listen()、acpet()、等函数方法实现了服务端的基本搭建和对于客户端的监听,其次是在客户端中指定了相应的服务端的ip地址和端口号,让客户端去连接服务端。最终达到通信的目的。

六.心得体会

本实验是从整体上对于网络协议的应用有了一个全面的认识。首先,在此之前我们没有接触过使用socket接口实现网络协议的一系列编程思想,在实验一总我们学会了socket中简单的协议的用法,服务端的建立,绑定,使用,监听等一系列的问题的研究已经从客户端连接服务端的各项内容。
从整体上对于winsock的使用流程有了更深一步的了解,通过阅读源码,对于TCP/IP协议在程序运行的实质和流程有了根深的理解。

七.源码

1.服务端

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
using namespace std;#pragma comment(lib, "Ws2_32.lib")//IP报头
typedef struct IP_HEADER
{unsigned char hdr_len:4;       //4位头部长度unsigned char version:4;       //4位版本号unsigned char tos;             //8位服务类型unsigned short total_len;      //16位总长度unsigned short identifier;     //16位标识符unsigned short frag_and_flags; //3位标志加13位片偏移unsigned char ttl;             //8位生存时间unsigned char protocol;        //8位上层协议号unsigned short checksum;       //16位校验和unsigned long sourceIP;        //32位源IP地址unsigned long destIP;          //32位目的IP地址
} IP_HEADER;//ICMP报头
typedef struct ICMP_HEADER
{BYTE type;    //8位类型字段BYTE code;    //8位代码字段USHORT cksum; //16位校验和USHORT id;    //16位标识符USHORT seq;   //16位序列号
} ICMP_HEADER;//报文解码结构
typedef struct DECODE_RESULT
{USHORT usSeqNo;        //序列号DWORD dwRoundTripTime; //往返时间in_addr dwIPaddr;      //返回报文的IP地址
}DECODE_RESULT;//计算网际校验和函数
USHORT checksum( USHORT *pBuf, int iSize )
{unsigned long cksum = 0;while( iSize > 1 ){cksum += *pBuf++;iSize -= sizeof(USHORT);}if( iSize )//如果 iSize 为正,即为奇数个字节{cksum += *(UCHAR *)pBuf; //则在末尾补上一个字节,使之有偶数个字节}cksum  = ( cksum >> 16 ) + ( cksum&0xffff );cksum += ( cksum >> 16 );return (USHORT)( ~cksum );
}//对数据包进行解码
BOOL DecodeIcmpResponse(char * pBuf, int iPacketSize, DECODE_RESULT &DecodeResult,BYTE ICMP_ECHO_REPLY, BYTE  ICMP_TIMEOUT)
{//检查数据报大小的合法性IP_HEADER* pIpHdr = ( IP_HEADER* )pBuf;int iIpHdrLen = pIpHdr->hdr_len * 4;    //ip报头的长度是以4字节为单位的//若数据包大小 小于 IP报头 + ICMP报头,则数据报大小不合法if ( iPacketSize < ( int )( iIpHdrLen + sizeof( ICMP_HEADER ) ) )return FALSE;//根据ICMP报文类型提取ID字段和序列号字段ICMP_HEADER *pIcmpHdr = ( ICMP_HEADER * )( pBuf + iIpHdrLen );//ICMP报头 = 接收到的缓冲数据 + IP报头USHORT usID, usSquNo;if( pIcmpHdr->type == ICMP_ECHO_REPLY )    //ICMP回显应答报文{usID = pIcmpHdr->id;        //报文IDusSquNo = pIcmpHdr->seq;    //报文序列号}else if( pIcmpHdr->type == ICMP_TIMEOUT )//ICMP超时差错报文{char * pInnerIpHdr = pBuf + iIpHdrLen + sizeof( ICMP_HEADER ); //载荷中的IP头int iInnerIPHdrLen = ( ( IP_HEADER * )pInnerIpHdr )->hdr_len * 4; //载荷中的IP头长ICMP_HEADER * pInnerIcmpHdr = ( ICMP_HEADER * )( pInnerIpHdr + iInnerIPHdrLen );//载荷中的ICMP头usID = pInnerIcmpHdr->id;        //报文IDusSquNo = pInnerIcmpHdr->seq;    //序列号}else{return false;}//检查ID和序列号以确定收到期待数据报if( usID != ( USHORT )GetCurrentProcessId() || usSquNo != DecodeResult.usSeqNo ){return false;}//记录IP地址并计算往返时间DecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;DecodeResult.dwRoundTripTime = GetTickCount() - DecodeResult.dwRoundTripTime;//处理正确收到的ICMP数据报if ( pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT ){//输出往返时间信息if(DecodeResult.dwRoundTripTime)cout<<"      "<<DecodeResult.dwRoundTripTime<<"ms"<<flush;elsecout<<"      "<<"<1ms"<<flush;}return true;
}int   main()
{//初始化Windows sockets网络环境WSADATA wsa;WSAStartup( MAKEWORD(2,2), &wsa );char IpAddress[255];cout<<"请输入一个IP地址或域名:";cin>>IpAddress;//得到IP地址u_long ulDestIP = inet_addr( IpAddress );//转换不成功时按域名解析if( ulDestIP == INADDR_NONE ){hostent * pHostent = gethostbyname( IpAddress );if( pHostent ){ulDestIP = ( *( in_addr* )pHostent->h_addr).s_addr;}else{cout<<"输入的IP地址或域名无效!"<<endl;WSACleanup();return 0;}}cout<<"Tracing roote to "<<IpAddress<<" with a maximum of 30 hops.\n"<<endl;//填充目的端socket地址sockaddr_in destSockAddr;ZeroMemory( &destSockAddr, sizeof( sockaddr_in ) );destSockAddr.sin_family = AF_INET;destSockAddr.sin_addr.s_addr = ulDestIP;//创建原始套接字SOCKET sockRaw = WSASocket( AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED );//超时时间int iTimeout = 3000;//设置接收超时时间setsockopt( sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *)&iTimeout, sizeof( iTimeout ) );//设置发送超时时间setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char *)&iTimeout,sizeof(iTimeout));//构造ICMP回显请求消息,并以TTL递增的顺序发送报文//ICMP类型字段const BYTE ICMP_ECHO_REQUEST = 8;    //请求回显const BYTE ICMP_ECHO_REPLY   = 0;    //回显应答const BYTE ICMP_TIMEOUT      = 11;   //传输超时//其他常量定义const int DEF_ICMP_DATA_SIZE   = 32;    //ICMP报文默认数据字段长度const int MAX_ICMP_PACKET_SIZE = 1024;  //ICMP报文最大长度(包括报头)const DWORD DEF_ICMP_TIMEOUT   = 3000;  //回显应答超时时间const int DEF_MAX_HOP          = 30;    //最大跳站数//填充ICMP报文中每次发送时不变的字段char IcmpSendBuf[ sizeof( ICMP_HEADER ) + DEF_ICMP_DATA_SIZE ];//发送缓冲区memset( IcmpSendBuf, 0, sizeof( IcmpSendBuf ) );               //初始化发送缓冲区char IcmpRecvBuf[ MAX_ICMP_PACKET_SIZE ];                      //接收缓冲区memset( IcmpRecvBuf, 0, sizeof( IcmpRecvBuf ) );               //初始化接收缓冲区ICMP_HEADER * pIcmpHeader = ( ICMP_HEADER* )IcmpSendBuf;pIcmpHeader->type = ICMP_ECHO_REQUEST; //类型为请求回显pIcmpHeader->code = 0;                //代码字段为0pIcmpHeader->id   = (USHORT)GetCurrentProcessId();    //ID字段为当前进程号memset( IcmpSendBuf + sizeof( ICMP_HEADER ), 'E', DEF_ICMP_DATA_SIZE );//数据字段USHORT usSeqNo      = 0;            //ICMP报文序列号int iTTL            = 1;            //TTL初始值为1BOOL bReachDestHost = FALSE;        //循环退出标志int iMaxHot         = DEF_MAX_HOP;  //循环的最大次数DECODE_RESULT DecodeResult;    //传递给报文解码函数的结构化参数while( !bReachDestHost && iMaxHot-- ){//设置IP报头的TTL字段setsockopt( sockRaw, IPPROTO_IP, IP_TTL, (char *)&iTTL, sizeof(iTTL) );cout<<iTTL<<flush;    //输出当前序号,flush表示将缓冲区的内容马上送进cout,把输出缓冲区刷新//填充ICMP报文中每次发送变化的字段((ICMP_HEADER *)IcmpSendBuf)->cksum = 0;                   //校验和先置为0((ICMP_HEADER *)IcmpSendBuf)->seq   = htons(usSeqNo++);    //填充序列号((ICMP_HEADER *)IcmpSendBuf)->cksum =checksum( ( USHORT * )IcmpSendBuf, sizeof( ICMP_HEADER ) + DEF_ICMP_DATA_SIZE ); //计算校验和//记录序列号和当前时间DecodeResult.usSeqNo         = ( ( ICMP_HEADER* )IcmpSendBuf )->seq;    //当前序号DecodeResult.dwRoundTripTime = GetTickCount();                          //当前时间//发送TCP回显请求信息sendto( sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr*)&destSockAddr, sizeof(destSockAddr) );//接收ICMP差错报文并进行解析处理sockaddr_in from;           //对端socket地址int iFromLen = sizeof(from);//地址结构大小int iReadDataLen;           //接收数据长度while(1){//接收数据iReadDataLen = recvfrom( sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr*)&from, &iFromLen );if( iReadDataLen != SOCKET_ERROR )//有数据到达{//对数据包进行解码if(DecodeIcmpResponse( IcmpRecvBuf, iReadDataLen, DecodeResult, ICMP_ECHO_REPLY, ICMP_TIMEOUT ) ){//到达目的地,退出循环if( DecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr )bReachDestHost = true;//输出IP地址cout<<'\t'<<inet_ntoa( DecodeResult.dwIPaddr )<<endl;break;}}else if( WSAGetLastError() == WSAETIMEDOUT )    //接收超时,输出*号{cout<<"         *"<<'\t'<<"Request timed out."<<endl;break;}else{break;}}iTTL++;    //递增TTL值}
}

2.客户端

#include <stdio.h>
#include <Winsock2.h>
#include <windows.h>
#include<iostream>
using namespace std;
#pragma comment(lib,"ws2_32")
//同样设置句柄对象
HANDLE hMutex;//客户端你的发送函数
void Send(SOCKET sockClient){char sendBuf[50]={0};int a=0;while(1){//printf("服务器连接成功!");printf("\n用户输入发送内容:");gets(sendBuf);// 客户端发送数据a=send(sockClient, sendBuf, 50, 0);printf("\n");if(a<=0){break;}elseSleep(1000);ReleaseMutex(hMutex);}//关闭socket,一次通信完成closesocket(sockClient);
}void Rec(SOCKET sockClient){char recBuf[50]={0};int b=0;while(1){b=recv(sockClient, recBuf, 50, 0);	// 客户端从服务器接收信息if(b<=0){break;}elseprintf("\n%s\n%s\n","来自服务器的内容:",recBuf);Sleep(1000);ReleaseMutex(hMutex);printf("\n用户输入发送内容:");}closesocket(sockClient);//关闭socket,一次通信完成
}int main()
{system("color 6");WORD wVersionRequested;WSADATA wsaData;int err;//通过连接两个给定的无符号参数,创建一个无符号16位整形wVersionRequested = MAKEWORD( 1, 1 );err = WSAStartup( wVersionRequested, &wsaData );//完成对Winsock的初始化if ( err != 0 ) //判断函数返回值{printf("********** 客户端启动成功!  **********\n");return 0;}//取10进制数最低和最高字节内容,用于判断版本是否一致if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ){WSACleanup( );return 0;}while(1){SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);//创建流式套接字,返回socketSrvSOCKADDR_IN addrSrv;addrSrv.sin_addr.S_un.S_addr=inet_addr("10.1.11.239");//绑定套接字到一个IP地址和一个端口上addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(6000);connect(sockClient,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));//建立连接HANDLE hThread1=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Send,(LPVOID)sockClient,0,0);HANDLE hThread2=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Rec,(LPVOID)sockClient,0,0);WaitForSingleObject(hThread1,INFINITE);CloseHandle(hThread1);WaitForSingleObject(hThread2,INFINITE);CloseHandle(hThread2);}getchar();//WSACleanup()是一个计算机函数,功能是终止Winsock 2 DLL (Ws2_32.dll) 的使用,函数原型是int PASCAL FAR WSACleanup (void)。	WSACleanup();return 0;
}

这篇关于计算机网络实验设计-网络聊天程序的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/529191

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import