自媒体交易网站开发,最新的购物网站 开,有什么做网兼的网站,网站文站加入别人网站的链接是否对自己网站不好目录 和windows通信
引入
思路
WSADATA
代码
运行情况
简单的聊天室
思路
重定向
代码
terminal.hpp -- 重定向函数
服务端
客户端
运行情况 和windows通信 引入 linux和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#includeiostream
#includestring#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函数)到终端文件里,就可以看见自己的终端显示出了数据: 这样,我们就可以通过重定向,先确定当前终端属于哪个文件 然后就可以利用这个(也就是将数据重定向到终端文件里,而不是显示器),实现聊天室的分块显示 但如果输出数据时将fd1的显示器重定向到终端文件1里,那么输入数据时/其他时候的打印,都会到那个终端文件里,而不会像我们预想的那样分成两个模块 所以,我们将标准错误重定向到其中一个终端文件里,另一个终端运行客户端,这样cout时会默认打印到当前终端里,就不会互相影响了 重定向既可以在代码中使用dup2函数,也可以直接在命令行中重定向(ab) 代码 terminal.hpp -- 重定向函数 #include iostream
#include string
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.hstd::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.hppextern std::string get_time();Log lg;const int buff_size 1024;
using func_t std::functionstd::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_caststruct 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_castconst 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_caststruct 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_castconst 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_castconst struct sockaddr *((it.second)), sizeof(it.second));}}private:int sockfd_;std::string ip_;uint16_t port_;std::mapin_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.hppconst 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_castconst 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_caststruct 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_castdata *(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_castconst 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_castconst struct sockaddr *(d-paddr_), d-len_);info.clear();}return nullptr;}static void *output(void *args){data *d reinterpret_castdata *(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_caststruct 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文件执行时进行手动重定向(分好两个终端屏幕,确定好各自的编号),就能通信
(也就是说总共需要运行三个终端)