栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++实现的Socket接口实现自定义协议通信

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++实现的Socket接口实现自定义协议通信

资源下载地址:https://download.csdn.net/download/sheziqiong/85836435
资源下载地址:https://download.csdn.net/download/sheziqiong/85836435

主要仪器设备

联网的 PC 机、Wireshark 软件

Visual C++、gcc 等 C++ 集成开发环境。

操作方法与实验步骤

设计请求、指示(服务器主动发给客户端的)、响应数据包的格式,至少要考虑如下问题:

定义两个数据包的边界如何识别。

定义数据包的请求、指示、响应类型字段。

定义数据包的长度字段或者结尾标记。

定义数据包内数据字段的格式(特别是考虑客户端列表数据如何表达)。

小组分工:1 人负责编写服务端,1 人负责编写客户端。

客户端编写步骤(需要采用多线程模式)

运行初始化,调用 socket(),向操作系统申请 socket 句柄。

编写一个菜单功能,列出 7 个选项等待用户选择。

根据用户选择,做出相应的动作(未连接时,只能选连接功能和退出功能)

选择连接功能:请用户输入服务器 IP 和端口,然后调用 connect(),等待返回结果并打印。连接成功后设置连接状态为已连接。然后创建一个接收数据的子线程,循环调用 receive(),如果收到了一个完整的响应数据包,就通过线程间通信(如消息队列)发送给主线程,然后继续调用 receive(),直至收到主线程通知退出。

选择断开功能:调用 close(),并设置连接状态为未连接。通知并等待子线程关闭。

选择获取时间功能:组装请求数据包,类型设置为时间请求,然后调用 send()将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印时间信息。

选择获取名字功能:组装请求数据包,类型设置为名字请求,然后调用 send()将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印名字信息。

选择获取客户端列表功能:组装请求数据包,类型设置为列表请求,然后调用 send() 将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印客户端列表信息(编号、IP 地址、端口等)。

选择发送消息功能(选择前需要先获得客户端列表):请用户输入客户端的列表编号和要发送的内容,然后组装请求数据包,类型设置为消息请求,然后调用 send()将数据发送给服务器,接着等待接收数据的子线程返回结果,并根据响应数据包的内容,打印消息发送结果(是否成功送达另一个客户端)。

选择退出功能:判断连接状态是否为已连接,是则先调用断开功能,然后再退出程序。否则,直接退出程序。

主线程除了在等待用户的输入外,还在处理子线程的消息队列,如果有消息到达,则进行处理,如果是响应消息,则打印响应消息的数据内容(比如时间、名字、客户端列表等);如果是指示消息,则打印指示消息的内容(比如服务器转发的别的客户端的消息内容、发送者编号、IP 地址、端口等)。

服务端编写步骤(需要采用多线程模式)

运行初始化,调用 socket(),向操作系统申请 socket 句柄

调用 bind(),绑定监听端口(请使用学号的后 4 位作为服务器的监听端口),接着调用 listen(),设置连接等待队列长度主线程循环调用 accept(),直到返回一个有效的 socket 句柄,在客户端列表中增加一个新客户端的项目,并记录下该客户端句柄和连接状态、端口。然后创建一个子线程后继续调用 accept()。该子线程的主要步骤是(刚获得的句柄要传递给子线程,子线程内部要使用该句柄发送和接收数据):

调用 send(),发送一个 hello 消息给客户端(可选)

循环调用 receive(),如果收到了一个完整的请求数据包,根据请求类型做相应的动作:

请求类型为获取时间:调用 time()获取本地时间,然后将时间数据组装进响应数据包,调用 send()发给客户端

请求类型为获取名字:将服务器的名字组装进响应数据包,调用 send()发给客户端

请求类型为获取客户端列表:读取客户端列表数据,将编号、IP 地址、端口等数据组装进响应数据包,调用 send()发给客户端

请求类型为发送消息:根据编号读取客户端列表数据,如果编号不存在,将错误代码和出错描述信息组装进响应数据包,调用 send()发回源客户端;如果编号存在并且状态是已连接,则将要转发的消息组装进指示数据包。调用 send()发给接收客户端(使用接收客户端的 socket 句柄),发送成功后组装转发成功的响应数据包,调用 send()发回源客户端。

主线程还负责检测退出指令(如用户按退出键或者收到退出信号),检测到后即通知并等待各子线程退出。最后关闭 Socket,主程序退出。

编程结束后,双方程序运行,检查是否实现功能要求,如果有问题,查找原因,并修改,直至满足功能要求使用多个客户端同时连接服务端,检查并发性使用 Wireshark 抓取每个功能的交互数据包

四、实验数据记录和处理

请将以下内容和本实验报告一起打包成一个压缩文件上传:

源代码:客户端和服务端的代码分别在一个目录

可执行文件:可运行的.exe 文件或 Linux 可执行文件,客户端和服务端各一个

以下实验记录均需结合屏幕截图(截取源代码或运行结果),进行文字标注(看完请删除本句)。

描述请求数据包的格式(画图说明),请求类型的定义

Int DES (目的地的 socket ID)
REQ_TYPE OPT (请求的类型)
Char[1024] MES (消息内容,固定长度)
const int MAXMES = 1024;
enum REQ_TYPE {DISCON, TIME, NAME, LINK, SEND};
string REQ_STR[] = { "DISCON", "TIME", "NAME", "LINK", "SEND" };
struct MESSAGE {
    int DES;
    REQ_TYPE OPT;
    char MES[MAXMES];
};

描述响应数据包的格式(画图说明),响应类型的定义

Int DES (目的地的 socket ID)
REQ_TYPE OPT (请求的类型)
Char[1024] MES (消息内容,固定长度)

描述指示数据包的格式(画图说明),指示类型的定义

Int DES (目的地的 socket ID)
REQ_TYPE OPT (请求的类型)
Char[1024] MES (消息内容,固定长度)

客户端初始运行后显示的菜单选项

客户端的主线程循环关键代码截图(描述总体,省略细节部分)

根据输入的指令,选择像服务器发放什么类型的包

while(1) {
// initialize the MESSAGE being sent
    MESSAGE sen ;
    sen = MESSAGE{
        -1, NAME, ""
        } ;
    int opt ;
    string Text ;
    int des_num ;
    cin >> opt ;
    switch (opt) {
    case 1:
        sen.OPT = DISCON ;
        break ;
    case 2:
        sen.OPT = TIME ;
        break ;
    case 3:
        sen.OPT = NAME ;
        break ;
    case 4:
        sen.OPT = LINK ;
        break ;
    case 5:
// only the type of sending message to others need to be treated differently
        sen.OPT = SEND ;
// 输入要发送的目的地和信息内容,略
    case 6:
        cout << "Quiting..." << endl ;
        return 0 ;
    default:
        cout << "Wrong option!" << endl ;
        break ;
    }
    if ( opt < 1 || opt > 6 )
        continue ;
// sending the packet  	 	char buf[sizeof(MESSAGE)] ;  	 	memcpy(buf, &sen, sizeof(MESSAGE)) ;
    int seRet = send(client, buf, sizeof(buf), 0);
    if (seRet == -1) {
        cout << "Sending failed:" << errno << endl;
    }
}

客户端的接收数据子线程循环关键代码截图(描述总体,省略细节部分)

void receiveT(SOCKET client) {
    char buf[sizeof(MESSAGE)];
    MESSAGE sen ;
    while ( 1 ) {
// Message Receiving
        int reRet = recv(client, buf, sizeof(buf), 0);
        if (reRet == -1) {
            cout << "Receiving failed:"<< errno << endl;
            exit(0);
        }
        memcpy(&sen, buf, sizeof(buf)) ;
        if(sen.OPT == DISCON) {
            cout << "Disconnecting..." << endl ;
            exit(0);
        }
    }
}

服务器初始运行后显示的界面

服务器的主线程循环关键代码截图(描述总体,省略细节部分)

while (true) {
    int client = accept(slisten, (sockaddr*)&sin, &addr_size);
    if (client == -1)
// 省略
// lambda expression used when using thread  	 	thread t([&](sockaddr_in addr, int client) {
// 放在下一题里
    }, sin, client);
t.detach();
}

服务器的客户端处理子线程循环关键代码截图(描述总体,省略细节部分) char TMP_CLI[255];

// Get ip from hex
inet_ntop(AF_INET, (void*)&sin.sin_addr, TMP_CLI, 16);
Clients[client] = sin;
Clients_info[client].ip = TMP_CLI;
Clients_info[client].port = sin.sin_port;
// Initialize Flas as TRUE    Flags[client] = 1;
while (Flags[client]) {
// Receive the packet     char buf[sizeof(MESSAGE)];
    int reRet = recv(client, buf, sizeof(buf), 0);
    if (reRet == -1) {
        cout << "receive failed:" << errno << endl;
        break;
    }
// interpret the packet  	 	 	 	MESSAGE rec, sen;
    memcpy(&rec, buf, sizeof(MESSAGE));
    memset(sen.MES, 0, sizeof(sen.MES));
    sen.DES = client;
    sen.OPT = rec.OPT;
// Process it according to its type     switch (rec.OPT) {     case DISCON:
    Flags[client] = 0;
    Clients_info.erase(client);
    cout << "Disconnected link with " << client << endl;
    break;
case TIME:
    cout << "Send time to " << client << endl;
    cc = time(0);
    tmp_time = ctime(&cc);
    strcpy(rec.MES, tmp_time.c_str());
    break;
case NAME:
    cout << "Send name to " << client << endl;
    gethostname(name, size);
    strcpy(rec.MES, name);
    break;
case LINK:
    cout << "Send current active links to " << client << endl;
// 通过 map 获取现有的活跃链接,并发送
    break;
case SEND:
    cout << "Send message from " << client << " to " << rec.DES << endl;
// 向目标地址发送消息
    break;
default:
    cout << "Wrong requirement type!" << endl;
}
// Always reply the sender after processing the packet  	 	 	 	if (rec.OPT >= DISCON && rec.OPT <= SEND) {
// 回复发出指令的客户端
}
}

客户端选择连接功能时,客户端和服务端显示内容截图。

Wireshark 抓取的数据包截图:

客户端选择获取时间功能时,客户端和服务端显示内容截图。

客户端:

服务端:

Wireshark 抓取的数据包截图(展开应用层数据包,标记请求、响应类型、返回的时间数据对应的位置):

客户端选择获取名字功能时,客户端和服务端显示内容截图。

客户端:

服务端:

Wireshark 抓取的数据包截图(展开应用层数据包,标记请求、响应类型、返回的名字数据对应的位置):

相关的服务器的处理代码片段:

gethostname(name, size);strcpy(rec.MES, name);客户端选择获取客户端列表功能时,客户端和服务端显示内容截图。

客户端:

服务端:

Wireshark 抓取的数据包截图(展开应用层数据包,标记请求、响应类型、返回的客户端列表

数据对应的位置):

相关的服务器的处理代码片段:

tmp_link = "tIDtIPtPortn";
for (auto i : Clients_info)
    tmp_link = tmp_link + "t" + to_string(i.first) + "t" + i.second.ip + "t"
               + to_string(i.second.port) + "n";
strcpy(rec.MES, tmp_link.c_str());

客户端选择发送消息功能时,客户端和服务端显示内容截图。

发送消息的客户端:

服务器:

接收消息的客户端:

Wireshark 抓取的数据包截图(发送和接收分别标记):

发送:

接受:

相关的服务器的处理代码片段:

cout << "Send message from " << client << " to " << rec.DES << endl;
tmp_text = rec.MES;
tmp_text = "message from [" + to_string(client) + "]: n" + tmp_text;
memset(sen.MES, 0, sizeof(sen.MES));
strcpy(sen.MES, tmp_text.c_str());
sen.DES = rec.DES;
cout << "Start sending..." << endl;
ret = send(sen.DES, (char*)&sen, sizeof(sen), 0);
if (ret < 0) {
    cout << "send failed" << endl;
    strcpy(rec.MES, "Sending Failed");
}

相关的客户端(发送和接收消息)处理代码片段:发送:

sen.OPT = SEND ;
cout << "To. " ;
cin >> des_num ;
sen.DES = des_num ;
cout << "Input the text need to send:n" ;
cin >> Text ;
strcpy(sen.MES, Text.c_str()) ;
接受就是前面接受服务器消息的方法。

拔掉客户端的网线,然后退出客户端程序。观察客户端的 TCP 连接状态,并使用 Wireshark观察客户端是否发出了 TCP 连接释放的消息。同时观察服务端的 TCP 连接状态在较长时间内分钟以上)是否发生变化。直接用关闭窗口的方式关掉了,有释放连接。

再次连上客户端的网线,重新运行客户端程序。选择连接功能,连上后选择获取客户端列表功能,查看之前异常退出的连接是否还在。选择给这个之前异常退出的客户端连接发送消息,出现了什么情况?

没有了该线程对应的 while 循环中会在链接断开的时候立即得到链接失败的信息。

断开前:

断开后:

修改获取时间功能,改为用户选择 1 次,程序内自动发送 100 次请求。服务器是否正常处理了次请求,截取客户端收到的响应(通过程序计数一下是否有 100 个响应回来),并使用Wireshark 抓取数据包,观察实际发出的数据包个数。

开始:

结束:

开始:

结束:

多个客户端同时连接服务器,同时发送时间请求(程序内自动连续调用 100 次 send),服务器和客户端的运行截图

资源下载地址:https://download.csdn.net/download/sheziqiong/85836435
资源下载地址:https://download.csdn.net/download/sheziqiong/85836435

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1002024.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号