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

2024-06-10 05:44

本文主要是介绍网络学了点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.目前还没有将用户的名字传输给服务端,后续可以加上

这篇关于网络学了点socket,写个聊天室,还得改进的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1047353

相关文章

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

python写个唤醒睡眠电脑的脚本

《python写个唤醒睡眠电脑的脚本》这篇文章主要为大家详细介绍了如何使用python写个唤醒睡眠电脑的脚本,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 环境:win10python3.12问题描述:怎么用python写个唤醒睡眠电脑的脚本?解决方案:1.唤醒处于睡眠状

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

poj 3181 网络流,建图。

题意: 农夫约翰为他的牛准备了F种食物和D种饮料。 每头牛都有各自喜欢的食物和饮料,而每种食物和饮料都只能分配给一头牛。 问最多能有多少头牛可以同时得到喜欢的食物和饮料。 解析: 由于要同时得到喜欢的食物和饮料,所以网络流建图的时候要把牛拆点了。 如下建图: s -> 食物 -> 牛1 -> 牛2 -> 饮料 -> t 所以分配一下点: s  =  0, 牛1= 1~

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了

poj 2112 网络流+二分

题意: k台挤奶机,c头牛,每台挤奶机可以挤m头牛。 现在给出每只牛到挤奶机的距离矩阵,求最小化牛的最大路程。 解析: 最大值最小化,最小值最大化,用二分来做。 先求出两点之间的最短距离。 然后二分匹配牛到挤奶机的最大路程,匹配中的判断是在这个最大路程下,是否牛的数量达到c只。 如何求牛的数量呢,用网络流来做。 从源点到牛引一条容量为1的边,然后挤奶机到汇点引一条容量为m的边

配置InfiniBand (IB) 和 RDMA over Converged Ethernet (RoCE) 网络

配置InfiniBand (IB) 和 RDMA over Converged Ethernet (RoCE) 网络 服务器端配置 在服务器端,你需要确保安装了必要的驱动程序和软件包,并且正确配置了网络接口。 安装 OFED 首先,安装 Open Fabrics Enterprise Distribution (OFED),它包含了 InfiniBand 所需的驱动程序和库。 sudo