基于udp协议的网络通信(windows客户端版+简易聊天室版),重定向到终端

目录

和windows通信

引入

思路 

WSADATA

代码

运行情况

简单的聊天室

思路

重定向

代码

terminal.hpp -- 重定向函数

服务端

客户端

运行情况


和windows通信

引入

linux和windows都需要联网,虽然他们系统设计不同,但网络部分一定是相同的,所以套接字也是一样的

  • 这里我们只需要写出windows风格的客户端即可,服务端仍然在linux上跑
  • 当然,除去套接字的部分,他们使用的接口和规则肯定是有区别的

思路 

套接字的部分不变,处理一下头尾即可

首先要引入winsock2.h头文件,并引入库文件

定义一个WSADATA结构并初始化(不同版本,看到的接口+底层代码也不同) 

WSADATA

  • 用于在 Windows 操作系统上开发网络应用程序时管理套接字(sockets)库的初始化和配置
  • 包含了关于 Winsock 环境的信息,例如 Winsock 版本、所支持的特性等
  • 使用WSAStartup初始化,WSACleanup来释放资源并并终止 Winsock 环境

修改完之后,就可以让linux和windows通信了

代码

这里用的是vs2019,加了两个define,防止报错(vs太安全了,汗)

可以看出来,中间的socket+收发数据绝大部分都是一样的,只有那么一两个类型的命名不同:

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>#include<iostream>
#include<string>#pragma comment(lib,"ws2_32.lib") //引入库文件int main()
{//初始化网络环境WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){printf("WSAStartup failed\n");return -1;}//建立一个udp的socketSOCKET socked = socket(AF_INET, SOCK_DGRAM, 0);if (socked == INVALID_SOCKET){printf("create socket failed\n");return -1;}int port = 8080;std::string ip = "47.108.135.233";//创建结构体sockaddr_in addr = { 0 };addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());std::string info;char buffer[1024];memset(buffer, 0, sizeof(buffer));//收发数据while (true) {std::cout << "Please enter:";std::getline(std::cin, info);//发送数据int n = sendto(socked, info.c_str(), info.size(), 0, (SOCKADDR*)&addr, sizeof(SOCKADDR));if (n == 0){printf("send failed\n");return -1;}sockaddr_in t = { 0 };int len = sizeof(sockaddr_in);// 接收数据n = recvfrom(socked, buffer, sizeof(buffer) - 1, 0, (SOCKADDR*)&t, &len);buffer[n] = 0;std::cout << buffer << std::endl;memset(buffer, 0, sizeof(buffer));}//关闭SOCKET连接closesocket(socked);//清理网络环境WSACleanup();return 0;
}

运行情况

我们成功在windows终端上与在linux下的服务端进行通信:

简单的聊天室

前面写的echo版就已经有聊天室的影子了,聊天软件的服务器的作用也就是向用户转发消息

这里我们修改一下就差不多了

思路

这里以ip地址和端口号作为每个人的标识(类似于用户名的作用),在echo版里其实就已经实现过了

但是echo版每个客户端的消息都是独立的

  • 聊天室的话,每个人在自己的客户端上都可以看见彼此发出的消息
  • 就需要我们将每条消息发送给所有运行起来的客户端
  • 可以考虑创建一个在线用户表(ip,结构体对象) -- 每收到一个消息,就转发给所有注册在表中的用户
  • 如果有登录功能的话,应该是登录后转发
  • 这里稍微模拟一下登录过程 -- 当客户端运行起来后,有一句打印,且直接将该条打印语句发送给服务器,并且直接注册在表中(简易版嘛)

服务器修改好后,客户端就出现问题了

  • 还记得我们的客户端吗,它的第一个函数就是getline
  • 如果不发送消息的话,就会卡在那里不会往下走,也就无法调用下面的recvfrom函数,也就无法看见其他用户发送的数据
  • 而udp协议是全双工的(它支持边读边写)
  • 所以我们可以将客户端修改为多线程,一个读,一个写,这样就互不干扰了

虽然解决了收发消息的问题,但是客户端仅有一个窗口,这样直接打印的话,会导致输入消息和输出的消息混在一块

  • 而聊天室一般是分为上下两部分,上面是所有人发送的消息,下面是自己的输入框
  • 综合我们是在终端上显示,可以开俩终端,拼接在一起作为我们的界面,输入和输出在不同终端上工作
  • 实现的话 -- /dev/pts里是终端文件
  • 当我们打开xshell:
  • 如果再开一个会话:

重定向

  • 我们如果试着将数据重定向(dup2函数)到终端文件里,就可以看见自己的终端显示出了数据:

  • 这样,我们就可以通过重定向,先确定当前终端属于哪个文件

  • 然后就可以利用这个(也就是将数据重定向到终端文件里,而不是显示器),实现聊天室的分块显示

  • 但如果输出数据时将fd=1的显示器重定向到终端文件1里,那么输入数据时/其他时候的打印,都会到那个终端文件里,而不会像我们预想的那样分成两个模块

  • 所以,我们将标准错误重定向到其中一个终端文件里,另一个终端运行客户端,这样cout时会默认打印到当前终端里,就不会互相影响了

  • 重定向既可以在代码中使用dup2函数,也可以直接在命令行中重定向(a>b)

代码

terminal.hpp -- 重定向函数

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>std::string terminal = "/dev/pts/2";void my_dup()
{int fd = open(terminal.c_str(), O_WRONLY);if (fd < 0){perror("open");exit(1);}dup2(fd, 2);close(fd);
}

服务端

这里增加了用户表和chat函数(聊天室专用启动函数)

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <cstring>#include <string>
#include <functional>
#include <map>
#include <iostream>#include "Log.hpp"extern std::string get_time();Log lg;const int buff_size = 1024;
using func_t = std::function<std::string(const std::string &)>;enum
{SOCKET_ERR = 1,BIND_ERR = 2
};// 启动服务器时,传入ip地址和端口号
// 手动启动class udp_server
{
public:udp_server(const uint16_t port = 8080, const std::string ip = "0.0.0.0"): ip_(ip), port_(port), sockfd_(0){}void run(func_t func){init();// 开始收发数据char buffer[buff_size];std::string message;while (true){memset(buffer, 0, sizeof(buffer));struct sockaddr_in src_addr;socklen_t src_len = sizeof(src_addr);// 获取数据ssize_t n = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&src_addr), &src_len);if (n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}buffer[n] = 0;std::string id = generate_id(inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));message = id + "sever recvfrom success";lg(INFO, message.c_str());// 处理数据std::string echo_info = func(buffer);// 响应给发送端sendto(sockfd_, echo_info.c_str(), echo_info.size(), 0, reinterpret_cast<const struct sockaddr *>(&src_addr), src_len);message = id + "sever sendto success";lg(INFO, message.c_str());}}void chat(){init();// 开始收发数据char buffer[buff_size];memset(buffer, 0, sizeof(buffer));std::string message;while (true){struct sockaddr_in src_addr;socklen_t src_len = sizeof(src_addr);// 获取数据ssize_t n = recvfrom(sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&src_addr), &src_len);char ip[30];// std::cout << inet_ntop(AF_INET, &(src_addr.sin_addr), ip, sizeof(ip) - 1)<<std::endl;if (n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}buffer[n] = 0;// std::cout << buffer << std::endl;usr_[src_addr.sin_addr.s_addr] = src_addr; // 注册用户表// for (auto it : usr_)// {//     std::cout << inet_ntop(AF_INET, &((it.second).sin_addr), ip, sizeof(ip) - 1) << std::endl;// }std::string id = generate_id(inet_ntoa(src_addr.sin_addr), ntohs(src_addr.sin_port));message = id + "sever recvfrom success";lg(INFO, message.c_str());// 处理数据std::string time_stamp = get_time();std::string echo_info = id + time_stamp + buffer;memset(buffer, 0, sizeof(buffer));// 响应给所有用户端send_all(echo_info);message = id + "sever sendto success";lg(INFO, message.c_str());}}~udp_server(){if (sockfd_ > 0){close(sockfd_);}}static std::string get_id(){udp_server obj;return obj.generate_id(obj.ip_, obj.port_);}private:std::string generate_id(const std::string ip, const uint16_t port){return "[" + ip + ":" + std::to_string(port) + "]";}void init(){// 创建套接字文件sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd_ < 0){lg(FATAL, "socket create error, sockfd : %d,%s", sockfd_, strerror(errno));exit(SOCKET_ERR);}// 创建sockaddr结构struct sockaddr_in addr;socklen_t len = sizeof(addr);bzero(&addr, len);addr.sin_addr.s_addr = inet_addr(ip_.c_str());addr.sin_family = AF_INET;addr.sin_port = htons(port_);// 绑定套接字信息int res = bind(sockfd_, reinterpret_cast<const struct sockaddr *>(&addr), len);if (res < 0){lg(FATAL, "bind error, sockfd : %d,%s", sockfd_, strerror(errno));exit(BIND_ERR);}lg(INFO, "bind success, sockfd : %d", sockfd_);}void send_all(const std::string &echo_info){char ip[30];for (auto it : usr_){// std::cout << inet_ntop(AF_INET, &((it.second)->sin_addr), ip, sizeof(ip) - 1)<<std::endl;sendto(sockfd_, echo_info.c_str(), echo_info.size(), 0, reinterpret_cast<const struct sockaddr *>(&(it.second)), sizeof(it.second));}}private:int sockfd_;std::string ip_;uint16_t port_;std::map<in_addr_t, struct sockaddr_in> usr_; //不能是指针,这样下次循环时,指针就换成新的客户端了
};

客户端

分出了写函数和读函数,chat函数中创建两个线程,让他们运行

#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <functional>
#include <strings.h>
#include <cstring>#include <string>
#include <iostream>#include <pthread.h>#include "Log.hpp"
#include "terminal.hpp"const int buff_size = 1024;Log lg;enum
{SOCKET_ERR = 1,BIND_ERR = 2
};// 客户端需要提前知道服务端的套接字地址信息
// 日常生活中,我们一般直接通过网址进入,网址就是ip地址,且它会直接和端口号绑定
// 所以,这里我们只能自己手动提供服务端的ip和端口号// 客户端不需要手动创建套接字,os会自动为我们提供(在首次发送数据时)
struct data
{int sockfd_;struct sockaddr_in *paddr_;socklen_t len_;
};class udp_client
{
public:udp_client(const uint16_t port = 8080, const std::string ip = "47.108.135.233"): ip_(ip), port_(port), sockfd_(0){}void run(){data *d = init();std::string info;char buffer[buff_size];memset(buffer, 0, sizeof(buffer));while (true){std::cout << "Please enter:";std::getline(std::cin, info);// 将消息发送给服务器sendto(d->sockfd_, info.c_str(), info.size(), 0, reinterpret_cast<const struct sockaddr *>(d->paddr_), d->len_);info.clear();struct sockaddr_in addr; // 仅用于填充参数,拿到自己的地址信息没啥意义socklen_t len = sizeof(addr);// 获取数据ssize_t n = recvfrom(d->sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&addr), &len);if (n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}buffer[n] = 0;std::cout << buffer << std::endl;memset(buffer, 0, sizeof(buffer));}}void chat(){data *d = init();pthread_t r = 0, w = 0;pthread_create(&r, nullptr, input, d);pthread_create(&w, nullptr, output, d);pthread_join(r, nullptr);pthread_join(w, nullptr);}private:data *init(){// 创建套接字文件int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){lg(FATAL, "socket create error, sockfd : %d", sockfd);exit(SOCKET_ERR);}// 创建sockaddr结构struct sockaddr_in *svr_paddr = new sockaddr_in;socklen_t svr_len = sizeof(*svr_paddr);bzero(svr_paddr, svr_len);inet_aton(ip_.c_str(), &(svr_paddr->sin_addr));svr_paddr->sin_family = AF_INET;svr_paddr->sin_port = htons(port_);return new data({sockfd, svr_paddr, svr_len});}static void *input(void *args){data *d = reinterpret_cast<data *>(args);char ip[30];inet_ntop(AF_INET, &((d->paddr_)->sin_addr), ip, sizeof(ip) - 1);std::string welcome = "comming...";sendto(d->sockfd_, welcome.c_str(), welcome.size(), 0, reinterpret_cast<const struct sockaddr *>(d->paddr_), d->len_);std::string info;while (true){std::cout << "Please enter:";std::getline(std::cin, info);// 将消息发送给服务器sendto(d->sockfd_, info.c_str(), info.size(), 0, reinterpret_cast<const struct sockaddr *>(d->paddr_), d->len_);info.clear();}return nullptr;}static void *output(void *args){data *d = reinterpret_cast<data *>(args);// my_dup();char buffer[buff_size];memset(buffer, 0, sizeof(buffer));while (true){struct sockaddr_in addr; // 仅用于填充参数,拿到自己的地址信息没啥意义socklen_t len = sizeof(addr);// 获取数据(所有用户的消息都会获取)ssize_t n = recvfrom(d->sockfd_, buffer, sizeof(buffer) - 1, 0, reinterpret_cast<struct sockaddr *>(&addr), &len);if (n < 0){lg(WARNING, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}buffer[n] = 0;std::cerr << buffer << std::endl;memset(buffer, 0, sizeof(buffer));}return nullptr;}private:int sockfd_;std::string ip_;uint16_t port_;
};

两个cpp函数之间构建相应cs的对象+调用chat函数即可 

运行情况

手动重定向(这个适合在其他主机上运行客户端,因为每个人打开的终端不一定正好有2,测试后进行手动重定向最好)

在代码内重定向:

下图是两个云服务器之间进行通信:

大家也可以下载文件试试,只要有两个执行文件+client文件执行时进行手动重定向(分好两个终端屏幕,确定好各自的编号),就能通信

(也就是说总共需要运行三个终端)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/747280.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matplotlib如何设置中文为宋体,英文为新罗马Times New Roman

问题描述 论文附图通常需要将中文设置为宋体&#xff0c;英文设置为新罗马字体&#xff08;Times New Roman&#xff09;。matplotlib中可以这样设置字体&#xff1a; plt.rcParams[font.sans-serif] [SimSun] plt.rcParams[font.sans-serif] [Times New Roman]但是这样设置…

7-Eleven用工数字化:零售哲学下的人效管理实践

2014年&#xff0c;一本《零售的哲学》在中国掀起热潮&#xff0c;揭示了7-Eleven便利店的新零售坪效管理秘诀。而对大部分零售企业来说&#xff0c;劳动力效率是坪效背后的主要支柱。近期&#xff0c;国内领先的劳动力管理云服务提供商盖雅工场发布了《聚焦人效、重塑组织&…

wps珠海市政府版本

功能 无广告&#xff0c;安装直接使用&#xff0c;word,excel,ppt功能齐全 步骤 双击exe文件&#xff0c;更改安装步骤即可&#xff0c;任意选择一个部门就可以了 获取资源 链接&#xff1a;https://pan.baidu.com/s/1IVfNVgLwsp5QBT2uX-yROQ?pwdme6f 提取码&#xff1a;me…

基于51单片机的微波炉温度控制器设计[proteus仿真]

基于51单片机的微波炉温度控制器设计[proteus仿真] 温度检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的微波炉温度控制器设计 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xff…

嵌入式驱动学习第三周——container_of()宏

前言 Linux内核编程中&#xff0c;会经常看见一个宏函数container_of&#xff0c;那么这究竟是什么呢&#xff0c;本篇博客记录学习container_of的过程。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程&#xff0c;未来预计四个月将高强度更新本专栏&#xff0c;喜欢的可…

Claude3 正式发布,支持多模态(附注册使用教程)

免费使用教程请看到最后&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; AnthropicAI 官推发布消息&#xff0c;正式推出Claude 3&#xff0c;沉寂了很久的Anthropic 终于亮剑放了大招。Claude 3 系列模型&#xff0c;包括Claude 3 Opus、Claude 3 Sonnet 和 C…

鸿蒙Next学习-Flex布局

Entry Component struct FlexCase {build() {//需要在构造参数上传Flex({ direction: FlexDirection.Row,justifyContent:FlexAlign.Center }) {//flex布局Row().width(100).height(100).backgroundColor(Color.Red)Row().width(100).height(100).backgroundColor(Color.Yellow…

L1-7 机工士姆斯塔迪奥【Java】

在 MMORPG《最终幻想14》的副本“乐欲之所瓯博讷修道院”里&#xff0c;BOSS 机工士姆斯塔迪奥将会接受玩家的挑战。 你需要处理这个副本其中的一个机制&#xff1a;NM 大小的地图被拆分为了 NM 个 11 的格子&#xff0c;BOSS 会选择若干行或/及若干列释放技能&#xff0c;玩家…

办公自动化的得力助手 —— 定时执行专家

目录 一、软件简介 二、办公应用场景 1、自动化文件处理 2、定时提醒与日程管理 3、网络操作与远程控制 4、系统维护与优化 三、使用体验 四、总结 在快节奏的现代办公环境中&#xff0c;如何高效地管理任务、节省时间并提升工作效率成为了每个职场人士关注的焦点。今天…

字符设备驱动编写

文章目录 环境一、添加驱动&#xff08;/sys/bus/i2c/drivers/mpu6050_1&#xff09;驱动和设备树扯上关系二、注册一个&#xff08;种/类&#xff1f;&#xff09;字符设备&#xff08;/proc/devices&#xff0c;243 mpu6050_1&#xff09;三、手动创建一个字符设备&#xff0…

web部署 三

案例: 1/在其中一个网站目录下创建\software文件夹&#xff0c;里面放txtppt/mp4/iso,文件类型。 2/web站点目录浏览启动应用。 3/用win10客户机浏览software目录下文件&#xff0c;并下载。txtppt/mp4/iso&#xff0c;发现问题并解决。 首先我们先建立一个software的文件夹并…

微服务初识

1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.1.单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打…

力扣串题:反转字符串中的元音字母

​​​​​​​ 双指针&#xff0c;注意判断是否为元音的操作 bool IsVowel(char s){if(sa||se||si||so||su||sA||sE||sI||sO||sU) return true;return false; }char * reverseVowels(char * s){int len strlen(s),i0;while(i<len-1){if(IsVowel(s[i])&&IsVowel(s…

【C语言】五种方法实现C语言中大小写字母的转化

文章目录 &#x1f4dd;tolower/toupper函数&#x1f309;tolower&#x1f320; toupper &#x1f320; ASCII码关系&#x1f309;位操作&#x1f309;宏定义 &#x1f320;小巧第五位&#x1f6a9;总结 &#x1f4dd;tolower/toupper函数 &#x1f309;tolower tolower函数是…

YOLOv7改进 | 更换主干网络之PP-LCNet

前言:Hello大家好,我是小哥谈。PP-LCNet是一个由百度团队针对Intel-CPU端加速而设计的轻量高性能网络。它是一种基于MKLDNN加速策略的轻量级卷积神经网络,适用于多任务,并具有提高模型准确率的方法。与之前预测速度相近的模型相比,PP-LCNet具有更高的准确性。此外,对于计…

掘根宝典之C++普通迭代器和反向迭代器详解

简介 迭代器是一种用于遍历容器元素的对象。它提供了一种统一的访问方式&#xff0c;使程序员可以对容器中的元素进行逐个访问和操作&#xff0c;而不需要了解容器的内部实现细节。 C标准库里每个容器都定义了迭代器&#xff0c;这迭代器的名字就叫容器迭代器 迭代器的作用类…

数字电子技术笔记——组合逻辑功能

1.Adder&#xff08;加法器&#xff09; Half-Adder&#xff08;半加器&#xff09; Full-Adder&#xff08;全加器&#xff09; 74LS283(4-bit parallel adders) 74LS283 4-bit parallel adders 81 input 41 output carry look-ahead adder &#xff08;超前进位加法器&a…

牛客 NC266925 我不是大富翁(dp)

原题 首先记录这一道题的目的是提醒自己&#xff1a;动态规划的属性并不是只有 m a x max max&#xff0c; m i n min min 和 c o u n t count count&#xff0c;同时还有布尔类型的dp 这题不能考虑在距离的维度上思考&#xff0c;比如说看走几步走到哪里了&#xff0c;如果…

C++进阶之路---手把手带你学习AVL树

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#…

图像处理与视觉感知---期末复习重点(3)

文章目录 一、空间域和频率域二、傅里叶变换三、频率域图像增强 一、空间域和频率域 1. 空间域&#xff1a;即所说的像素域&#xff0c;在空间域的处理就是在像素级的处理&#xff0c;如在像素级的图像叠加。通过傅立叶变换后&#xff0c;得到的是图像的频谱&#xff0c;表示图…