本文主要是介绍网络学了点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,写个聊天室,还得改进的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!