当前位置: 首页 > news >正文

网络学了点socket,写个聊天室,还得改进

目录

第一版:

common

服务端:

客户端

第一版问题总结:

第二版

服务端:

客户端:

改进:

Windows客户端

一些小问题

还可以进行的改进


这篇文章我就先不讲网络基础的东西了,我讲讲在我进行制作我这个拉跨聊天室中遇到的问题,并写了三版代码.

第一版:

common

#pragma once
#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <ctime>
#include <cstdarg>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "LockGuard.hpp"
enum
{SOCKET_ERROR = 1,BIND_ERROR,USAGE_ERROR
};void Setserver(struct sockaddr_in &server,std::string &serverip,uint16_t &serverport)
{bzero(&server, sizeof(server));server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());server.sin_family=AF_INET;
}class InetAddr
{void GetAddress(uint16_t *Port, std::string *Ip){*Port = ntohs(_Addr.sin_port);*Ip = inet_ntoa(_Addr.sin_addr);}public:InetAddr(struct sockaddr_in &Addr): _Addr(Addr){GetAddress(&_Port, &_Ip);}uint16_t Port(){return _Port;}std::string Ip(){return _Ip;}private:struct sockaddr_in _Addr;uint16_t _Port;std::string _Ip;
};

服务端:

#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include "Common.hpp"
const static int defaultfd = -1;class Udpserver
{public:Udpserver(uint16_t port): _socketfd(defaultfd), _prot(port), _isrunning(false){}void InitServer(){_socketfd = socket(AF_INET, SOCK_DGRAM, 0);if (_socketfd < 0){LOG(WARNING, "%s", "sockfd创建失败");}LOG(INFO, "%s", "sock创建成功");// 填充sockaddr_in结构struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;    // 设置家族协议local.sin_port = htons(_prot); // 设置端口号 换成网络序列// port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列// a. 字符串风格的点分十进制的IP地址转成 4 字节IP// b. 主机序列,转成网络序列// in_addr_t inet_addr(const char *cp) -> 同时完成 a & b// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IPlocal.sin_addr.s_addr = INADDR_ANY; // 随机ip地址 一般不能绑定确定ip地址// 开始绑定int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "%s", "bind error");exit(BIND_ERROR);}LOG(INFO, "%s", "bind success");}void Start(){_isrunning = true;while (_isrunning){char Inbuffer[1024];struct sockaddr_in peer;socklen_t socklen = sizeof(peer); // 初始化为sock// 让server收取数据 获取客户端socketssize_t n = recvfrom(_socketfd, Inbuffer, sizeof(Inbuffer), 0, (struct sockaddr *)&peer,&socklen); // 接受if (n > 0){std::cout <<"client say:";std::cout << Inbuffer << std::endl; // 成功就打印出来// sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);LOG(DEBUG, "建立 客户IP:%s 连接端口:%s", netAddr.Ip().c_str(), netAddr.Port());// sendto(_socketfd,buffer,sizeof(buffer),0,(struct sockaddr*)&sockaddr_in,socklen);//发送}/////发///InetAddr netAddr(peer);std::string message;std::cout << "server:";getline(std::cin, message);sendto(_socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&peer, socklen); // 发送}_isrunning = false;}private:int _socketfd;uint16_t _prot;bool _isrunning;
};

客户端

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"
#include "Common.hpp"static bool isrunning = false;
static std::string Clientname;
void useagge(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){useagge(argv[0]);exit(USAGE_ERROR);}std::string serverip = argv[1];uint16_t serverport = std::atoi(argv[2]);std::cout << "你是?" << std::endl;std::cin >> Clientname;// 创建socket;int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){LOG(WARNING, "%s", "Sock错误创建失败");}LOG(INFO, "%s", "sock创建成功");isrunning = true;struct sockaddr_in server;Setserver(server, serverip, serverport);struct sockaddr_in local;bzero(&local, sizeof(local));std::cout << "可以进行通信了!" << std::endl;while (isrunning){std::cout << "server:";std::string message;getline(std::cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, socklen); // 发送struct sockaddr_in peer;socklen_t socklen = sizeof(peer); // 初始化为sockchar buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &socklen);if (n > 0){buffer[n] = 0;std::cout << "server: " << buffer << std::endl;}}return 0;
}

第一版问题总结:

我一开始是想着这个流程,因为一开始服务端只是接受客户端,服务端不会发消息给客户端,所以我想在原基础上,让两端都可以接受和发送,当时就有想可以多线程实行接受和发的任务,但是觉得上线程太麻烦就决定是服务端发->客户端收->客户端发->服务器收,这一条链路实行,但是问题是,我把收发是写在循环里,而 recvfrom是非阻塞等待的,所以双方实际上永远等不到对方信息

所以实际上仍然是要让多线程实行接受和发的任务

第二版

服务端:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <thread>
#include <mutex>
#include "log.hpp"
#include "Common.hpp"
const static int defaultfd = -1;public:Udpserver(uint16_t port): _socketfd(defaultfd), _prot(port), _isrunning(false){_socklen = sizeof(_peer);InitServer();}void InitServer(){_socketfd = socket(AF_INET, SOCK_DGRAM, 0);if (_socketfd < 0){LOG(WARNING, "%s", "sockfd创建失败");}LOG(INFO, "%s", "sock创建成功");// 填充sockaddr_in结构struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;    // 设置家族协议local.sin_port = htons(_prot); // 设置端口号 换成网络序列// port要经过网络传输给对面,先到网络,_port:主机序列-> 主机序列,转成网络序列// a. 字符串风格的点分十进制的IP地址转成 4 字节IP// b. 主机序列,转成网络序列// in_addr_t inet_addr(const char *cp) -> 同时完成 a & b// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // "192.168.3.1" -> 字符串风格的点分十进制的IP地址 -> 4字节IPlocal.sin_addr.s_addr = INADDR_ANY; // 随机ip地址 一般不能绑定确定ip地址// 开始绑定int n = bind(_socketfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "%s", "bind error");exit(BIND_ERROR);}LOG(INFO, "%s", "bind success");_isrunning = true;}void Start()void receive(){_isrunning = true;while (_isrunning){char Inbuffer[1024] = {0}; // 初始化缓冲区struct sockaddr_in tempPeer;socklen_t tempSocklen = sizeof(tempPeer);ssize_t n = recvfrom(_socketfd, Inbuffer, sizeof(Inbuffer) - 1, 0, (struct sockaddr *)&tempPeer, &tempSocklen);if (n > 0){std::cout <<"client say:";std::cout << Inbuffer << std::endl; // 成功就打印出来// sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);LOG(DEBUG, "建立 客户IP:%s 连接端口:%s", netAddr.Ip().c_str(), netAddr.Port());// sendto(_socketfd,buffer,sizeof(buffer),0,(struct sockaddr*)&sockaddr_in,socklen);//发送Inbuffer[n] = 0;std::lock_guard<std::mutex> lock(_peerMutex);_peer = tempPeer;_socklen = tempSocklen;std::cout << "client says:" << Inbuffer << std::endl;}/////发///InetAddr netAddr(peer);else{perror("recvfrom error");}}}void sent(){while (_isrunning){std::string message;std::cout << "server: ";std::cin >> message;std::lock_guard<std::mutex> lock(_peerMutex);ssize_t sent = sendto(_socketfd, message.c_str(), message.size(), 0, (struct sockaddr *)&_peer, _socklen);if (sent == -1){perror("sendto error");}}_isrunning = false;}void Start(){std::thread recvThread(&Udpserver::receive, this);std::thread sendThread(&Udpserver::sent, this);recvThread.detach();sendThread.detach();while (_isrunning){std::this_thread::sleep_for(std::chrono::seconds(1));}close(_socketfd);}private:int _socketfd;uint16_t _prot;bool _isrunning;//struct sockaddr_in _peer;socklen_t _socklen;std::mutex _peerMutex;
};

客户端:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include <thread>
#include "log.hpp"
#include "Common.hpp"static bool isrunning = false;
static std::string Clientname;class Client
{
public:Client(const std::string &server_ip, uint16_t server_port){_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd == -1){perror("socket creation failed");exit(EXIT_FAILURE);}memset(&_serverAddr, 0, sizeof(_serverAddr));_serverAddr.sin_family = AF_INET;_serverAddr.sin_port = htons(server_port);if (inet_pton(AF_INET, server_ip.c_str(), &_serverAddr.sin_addr) <= 0){perror("inet_pton failed");close(_sockfd);exit(EXIT_FAILURE);}_isrunning = true;}void start(){std::cout << "你是? ";std::cin >> _clientName;std::cout << "可以进行通信了!" << std::endl;std::thread recvThread(&Client::receive, this);std::thread sendThread(&Client::send, this);recvThread.detach();sendThread.detach();while (_isrunning){std::this_thread::sleep_for(std::chrono::seconds(1));}close(_sockfd);}private:void receive(){while (_isrunning){char buffer[1024] = {0};struct sockaddr_in peer;socklen_t socklen = sizeof(peer);ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &socklen);if (n > 0){buffer[n] = 0;std::cout << "server: " << buffer << std::endl;}else if (n == -1){perror("recvfrom error");}}}void send(){while (_isrunning){std::cout << _clientName << ": ";std::string message;std::cin >> message;ssize_t sent = sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&_serverAddr, sizeof(_serverAddr));if (sent == -1){perror("sendto error");}}}private:int _sockfd;struct sockaddr_in _serverAddr;bool _isrunning;std::string _clientName;
};

改进:

这一版上,我添加了多线程和锁,能让客户端服务端进行并发的运行,并收发消息

Windows客户端

由于我想让Windows朋友也能与我建立通信,所以我在客户端上进行了修改成Windows版本

 #define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <thread>
#include <memory>#pragma comment(lib, "Ws2_32.lib")static bool isrunning = false;
static std::string Clientname;class Client
{
public:Client(const std::string& server_ip, uint16_t server_port){WSADATA wsaData;int result = WSAStartup(MAKEWORD(2, 2), &wsaData);if (result != 0){std::cerr << "WSAStartup failed: " << result << std::endl;exit(EXIT_FAILURE);}_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd == INVALID_SOCKET){std::cerr << "socket creation failed: " << WSAGetLastError() << std::endl;WSACleanup();exit(EXIT_FAILURE);}memset(&_serverAddr, 0, sizeof(_serverAddr));_serverAddr.sin_family = AF_INET;_serverAddr.sin_port = htons(server_port);if (inet_pton(AF_INET, server_ip.c_str(), &_serverAddr.sin_addr) <= 0){std::cerr << "inet_pton failed: " << WSAGetLastError() << std::endl;closesocket(_sockfd);WSACleanup();exit(EXIT_FAILURE);}_isrunning = true;}void start(){std::cout << "你是? ";std::cin >> _clientName;std::cout << "可以进行通信了!" << std::endl;std::thread recvThread(&Client::receive, this);std::thread sendThread(&Client::send, this);recvThread.detach();sendThread.detach();while (_isrunning){std::this_thread::sleep_for(std::chrono::seconds(1));}closesocket(_sockfd);WSACleanup();}private:void receive(){while (_isrunning){char buffer[1024] = { 0 };struct sockaddr_in peer;int peerlen = sizeof(peer);int n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);if (n > 0){buffer[n] = 0;std::cout << "server: " << buffer << std::endl;}else if (n == SOCKET_ERROR){std::cerr << "recvfrom error: " << WSAGetLastError() << std::endl;}}}void send(){while (_isrunning){std::cout << _clientName << ": ";std::string message;std::cin >> message;int sent = sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&_serverAddr, sizeof(_serverAddr));if (sent == SOCKET_ERROR){std::cerr << "sendto error: " << WSAGetLastError() << std::endl;}}}private:SOCKET _sockfd;struct sockaddr_in _serverAddr;bool _isrunning;std::string _clientName;
};void useagge(const std::string& proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}int main()
{std::string serverip;std::string portStr;std::cout << "输入服务器ip: ";std::cin >> serverip;std::cout << "输入服务器端口号: ";std::cin >> portStr;uint16_t serverport;try{serverport = static_cast<uint16_t>(std::stoi(portStr));}catch (const std::exception& e){std::cerr << "无效的端口号: " << e.what() << std::endl;return EXIT_FAILURE;}std::unique_ptr<Client> csvr = std::make_unique<Client>(serverip, serverport); // C++14csvr->start();return 0;
}

一些小问题

由于我用的云服务器,大部分端口号是默认禁用的,所以端口号要自己进行开放,我用的阿里云,开放端口的地方在这里

还可以进行的改进

1.目前服务端和客户端仍然是1对1的关系,如果有第二个用户上线,就会挤占第一个用户,所以这里可以用一个vector来对用户的ip进行管理,来统一收所有用户消息

2.目前还没有将用户的名字传输给服务端,后续可以加上

相关文章:

  • 使用proteus仿真51单片机的流水灯实现
  • Codesys中根据时间生成随机数字
  • Java后端开发常见的框架以及组件
  • 欢乐钓鱼大师攻略:buff大全讲解,云手机托管使用教程!
  • 本地知识库问答系统搭建(基于langchain+LLM)
  • ISO 19115-3:2023 关于元数据最小实例的允许命名空间的详细说明
  • IDEA创建Mybatis项目
  • 【MySQL】(基础篇五) —— 排序检索数据
  • (亲测有效)推荐2024最新的免费漫画软件app,无广告,聚合全网资源!
  • 地推数据不再迷茫,Xinstall助你一臂之力!
  • Docker与低代码跨平台开发:实现高效跨平台开发的新范式
  • Linux基础(2)基础命令与vim
  • C# WPF入门学习主线篇(十一)—— 布局管理
  • C++和C语言到底有什么区别?
  • Vue2后台管理:项目开发全流程(一)
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 2017年终总结、随想
  • CEF与代理
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • iOS高仿微信项目、阴影圆角渐变色效果、卡片动画、波浪动画、路由框架等源码...
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • jquery cookie
  • Python_OOP
  • SQLServer插入数据
  • SSH 免密登录
  • Vue 重置组件到初始状态
  • 大型网站性能监测、分析与优化常见问题QA
  • 给初学者:JavaScript 中数组操作注意点
  • 批量截取pdf文件
  • 前端知识点整理(待续)
  • 突破自己的技术思维
  • 新手搭建网站的主要流程
  • Hibernate主键生成策略及选择
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​【经验分享】微机原理、指令判断、判断指令是否正确判断指令是否正确​
  • ​如何使用QGIS制作三维建筑
  • #if #elif #endif
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • $.ajax()方法详解
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (C++二叉树05) 合并二叉树 二叉搜索树中的搜索 验证二叉搜索树
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (四)库存超卖案例实战——优化redis分布式锁
  • (转)JVM内存分配 -Xms128m -Xmx512m -XX:PermSize=128m -XX:MaxPermSize=512m
  • ./和../以及/和~之间的区别
  • .bashrc在哪里,alias妙用
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .NET Micro Framework 4.2 beta 源码探析
  • .net 按比例显示图片的缩略图
  • .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换