文章目录
实验一 Winsock网络聊天程序
一、实验题目
网络聊天程序的设计和实现。
二、实验目的
了解 Socket 通信的原理,在此基础上编写一个聊天程序。
三、总体设计
使用Winsock API进行编程,其中
SOCKET
是贯穿整个流程的一个重要元素,SOCKET将机器的ip地址、端口号都整合起来。对于服务端,使用listen
函数进行监听,用accept
函数完成对客户端请求的接收。对于客户端,使用connect
函数连接服务端。服务端与客户端通过send
函数与recv
函数进行数据的发送。
四、详细设计
程序大致运行步骤如下:
客户端:
/* 设置服务器地址 */
Server_add.sin_family = AF_INET;
Server_add.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
Server_add.sin_port = htons(5000);
/* 连接服务器 */
socket_send = socket(AF_INET, SOCK_STREAM, 0);
if (connect(socket_send, (SOCKADDR *)&Server_add, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
printf("Connection Failed!\n");
}
/* 开始聊天 */
while ( 1 )
{
//发送数据过程
printf("Client sending:");
scanf("%s", Sendbuf);
SendLen = send(socket_send, Sendbuf, 100, 0);
if (SendLen < 0)
{
printf("Sending Failed!\n");
}
//接收数据过程
ReceiveLen = recv(socket_send, Receivebuf, 100, 0);
if (ReceiveLen < 0)
{
printf("Recieving Failed!\n");
printf("Program Failed\n");
break;
}
else
{
printf("From surround:%s\n", Receivebuf);
}
}
/* 释放套接字, 关闭动态库 */
closesocket(socket_send);
WSACleanup();
system("pause");
return 0;
服务端:
//绑定套接字到本地的某个地址和端口上
if (bind(socket_server, (SOCKADDR *)&Server_add, sizeof(SOCKADDR)) == SOCKET_ERROR)
{
printf("Binding Failed!\n");
}
//设置套接字为监听状态
if (listen(socket_server, 5) < 0)
{
printf("Listning Failed!\n");
}
//接受连接
Length = sizeof(SOCKADDR);
socket_receive = accept(socket_server, ( SOCKADDR * )&Client_add, &Length);
if (socket_receive == SOCKET_ERROR)
{
printf("Recieving Failed!\n");
}
五、实验结果与分析
程序运行演示如下:
程序可以完成半双工的通信。
实验二 路由追踪(Tracert)程序
一、实验题目
Tracert 与 Ping 程序设计与实现。
二、实验目的
了解 Tracert 程序的实现原理,并调试通过。
三、总体设计
Tracert程序的路由追踪主要是通过ip头部TTL与ICMP协议实现。参考课程设计指导书与C++实现路由追踪(Tracert)程序 程序向目的主机发送ICMP回显请求,每当该数据报经过一个路由器,该路由器就会发送一个ICMP超市差错报文给源主机,源主机用过解析每一份ICMP超市差错报文来获得到目的主机路上的路由信息。
四、详细设计
程序大致结构如图:
核心代码如下:
//计算网际校验和函数
USHORT checksum(USHORT *pBuf, int iSize) {
unsigned long cksum = 0;
while (iSize > 1) {
cksum += *pBuf++;
iSize -= sizeof(USHORT);
}
if (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;
if (iPacketSize < (int) (iIpHdrLen + sizeof(ICMP_HEADER)))
return FALSE;
//根据 ICMP 报文类型提取 ID 字段和序列号字段
ICMP_HEADER *pIcmpHdr = (ICMP_HEADER *) (pBuf + iIpHdrLen);
USHORT usID, usSquNo;
if (pIcmpHdr->type == ICMP_ECHO_REPLY) //ICMP 回显应答报文
{
usID = pIcmpHdr->id; //报文 ID
usSquNo = 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; //报文 ID
usSquNo = 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;
else
cout << " " << "<1ms" << flush;
}
return true;
}
五、实验结果与分析
程序运行结果如图所示:
实验六 电子邮件客户端程序设计与实现
一、实验题目
电子邮件客户端程序设计与实现。
二、实验目的
参照教材 6.5 节原理,设计一个电子邮件客户端程序。
三、总体设计
本实验所用到的基本原理与核心技术为SMTP(简单邮件传输协议)和POP3(邮局协议版本3)。SMTP用于发送邮件,POP3用于读取邮箱中的邮件。工作过程大致为:
首先,运行在发送端邮件服务器主机上的SMTP客户,发起建立一个到运行在接收端邮件服务器主机上的SMTP服务器端口号25之间的TCP连接。如果接收邮件服务器当前不在工作,SMTP客户就等待一段时间后再尝试建立该连接。
四、详细设计
程序大致运行步骤如下:
核心代码如下:
int num;
cin >> num;
if(num == 1)
{
sockClient = socket(AF_INET, SOCK_STREAM, 0);
pHostent = gethostbyname("smtp.qq.com");
addrServer.sin_addr.S_un.S_addr = *((DWORD *) pHostent->h_addr_list[0]);
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(25);
err = connect(sockClient, (SOCKADDR *) &addrServer, sizeof(SOCKADDR));
buff[recv(sockClient, buff, 500, 0)] = '\0';
message = "ehlo qq.com\r\n";
send(sockClient, message.c_str(), message.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
message = "auth login\r\n";
send(sockClient, message.c_str(), message.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
username = "________________________\r\n"; //base64加密的用户名(xxxx@qq.com)
send(sockClient, username.c_str(), username.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
password = "________________________\r\n"; //base64加密的密码
send(sockClient, password.c_str(), password.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
cout << "请输入目的邮箱:";
cin >> to_addr;
message = "MAIL FROM:________________________ \r\nRCPT TO:<";
message.append(to_addr);
message.append("> \r\n");
send(sockClient, message.c_str(), message.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
message = "DATA\r\n";
send(sockClient, message.c_str(), message.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
message = "From: ________________________\r\nTo: " + to_addr + "\r\nsubject:";
cout << "请输入邮件标题:";
cin >> subject;
message.append(subject);
message.append("\r\n\r\n");
cout << "请输入邮件正文:";
cin >> info;
message.append(info);
message.append("\r\n.\r\n");
send(sockClient, message.c_str(), message.length(), 0);//发送邮件内容
message = "QUIT\r\n";
send(sockClient, message.c_str(), message.length(), 0);
buff[recv(sockClient, buff, 500, 0)] = '\0';
cout << "发送成功!" << endl;
}
在
username = "________________________\r\n";
和password = "________________________\r\n";
中填入发送源邮箱的邮箱号(username)与SMTP/POP3授权码(password)即可以此邮箱发送邮件;下面的message = "MAIL FROM:________________________ \r\nRCPT TO:<";
与message = "From: ________________________\r\nTo: " + to_addr + "\r\nsubject:";
用于查阅邮件填入的信息是欲查阅邮箱的邮箱号(username)与授权码(password)。
五、实验结果与分析
程序运行演示如下:
仅供学习参考,文中可能出现错误,还请自行甄别,文中展示代码仅为部分核心代码。