上一篇文章实现了一个最简单的socket连接,但只能进行一对一通讯,所以本篇文章将会将功能扩展下,实现一对多的通讯方式。上篇实现代码中,感谢@rf_versace指出服务器接受数据的容器太小,容易溢出,这点之前没有想到过,目前解决的方法就是设置大一点,一般来说消息都会有序列化和反序列化,关于序列化和反序列化之后再写一篇文章来说下。
和上一篇文章一样,我们初始化socket,设置IP、端口,绑定监听,然后接下来就不一样了,我们不再是阻塞等待客户端->建立连接->接受消息->断开客户端->阻塞等待客户端的形式了,因为我们想与已经连接的客户端保持通讯,所以我们创建一个多线程管理函数,它专门用来分配已连接的socket到一个收发消息的线程,这样客户端就不需要等待上一个socket断开才能连接服务器,从而实现一对多的通讯。
//ServerTest.h
#pragma once //和#ifdef效果一样
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
typedef struct serverThread
{
std::thread *t1 = nullptr;
bool isRuning = false;
int threadID = -1;
SOCKET csocket = -1;
}Sthread;
class SocketServerTest
{
public:
SocketServerTest();
~SocketServerTest();
bool CreateSocket();//创建socket
bool BandSocket(const char* ip, const unsigned short prot);//绑定本地地址和端口号
bool ListenSocket();//初始化监听并设置最大连接等待数量
void AcceptSocketManager();//接受请求连接的请求,并返回句柄
void AddClientSocket(SOCKET &sClient);//循环检查客户端连接
void ThreadClientRecv(Sthread *sthread);//客户端线程接受数据
void CloseMySocket();//关闭连接
void OutputMessage(const char *outstr);//输出文字
private:
SOCKET m_nServerSocket;//绑定本地地址和端口号的套接口
std::vector m_Vecthread; //保存线程数据的一个vector
int m_CsocketCount = 0;//线程编号
};
ServerTest.cpp
#include "ServerTest.h"
#include
SocketServerTest::SocketServerTest():m_nServerSocket(-1)
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
OutputMessage("Socket版本错误");
}
SocketServerTest::~SocketServerTest()
{
CloseMySocket();
}
void SocketServerTest::CloseMySocket()
{
//退出所有线程
for (auto it : m_Vecthread)
{
it->isRuning = false;
}
if (m_nServerSocket != -1)
closesocket(m_nServerSocket); //关闭socket连接
m_nServerSocket = -1;
WSACleanup(); //终止ws2_32.lib的使用
}
bool SocketServerTest::CreateSocket()
{
if (m_nServerSocket == -1)
{
m_nServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //设定TCP协议接口;失败返回INVALID_SOCKET
if (m_nServerSocket != INVALID_SOCKET)
{
OutputMessage("服务器启动成功");
return true;
}
}
return false;
}
bool SocketServerTest::BandSocket(const char* ip, const unsigned short prot)
{
int nRet = -1;
if (m_nServerSocket != -1)
{
sockaddr_in Serveraddr;
memset(&Serveraddr, 0, sizeof(sockaddr_in*));
Serveraddr.sin_family = AF_INET;
Serveraddr.sin_addr.s_addr = inet_addr(ip);
Serveraddr.sin_port = htons(prot);
nRet = bind(m_nServerSocket, (sockaddr *)&Serveraddr, sizeof(Serveraddr)); //绑定服务器地址和端口号;成功,返回0,否则为SOCKET_ERROR
}
if (nRet == 0)
{
OutputMessage("绑定IP和端口成功");
return true;
}
OutputMessage("绑定IP和端口失败");
return false;
}
bool SocketServerTest::ListenSocket()
{
int nRet = -1;
if (m_nServerSocket != -1)
{
nRet = listen(m_nServerSocket, 5);//设定接受连接的套接字,以及设定连接队列长度;成功返回0,失败返回-1
}
if (nRet == SOCKET_ERROR)
{
OutputMessage("监听绑定失败");
return false;
}
OutputMessage("监听绑定成功");
return true;
}
void SocketServerTest::AcceptSocketManager()
{
while (m_nServerSocket != -1)
{
sockaddr_in nClientSocket;//如果要保存客户端的IP和端口号,就存在本地
int nSizeClient = sizeof(nClientSocket);
SOCKET sClient = accept(m_nServerSocket, (sockaddr*)&nClientSocket, &nSizeClient);//接受客户端连接,阻塞状态;失败返回-1
if (sClient == SOCKET_ERROR)
{
OutputMessage("当前与客户端连接失败");
return;
}
else
{
AddClientSocket(sClient);
}
Sleep(25);
}
}
void SocketServerTest::AddClientSocket(SOCKET& sClient)
{
Sthread *it = new Sthread();
it->threadID = ++m_CsocketCount;
it->isRuning = true;
it->csocket = sClient;
std::thread t(&SocketServerTest::ThreadClientRecv, this, it);
t.detach();
it->t1 = &t;
m_Vecthread.push_back(it);
char str[50];
sprintf_s(str, "%dthread connect is success", it->threadID);
OutputMessage(str);
char mess[] = "sercer:与服务器连接成功!";
send(sClient, mess, sizeof(mess), 0);//发送消息给客户端
}
void SocketServerTest::ThreadClientRecv(Sthread *sthread)
{
while (sthread->isRuning == true)
{
// 从客户端接收数据
char buff[65535];
int nRecv = recv(sthread->csocket, buff, 65535, 0);//从客户端接受消息
if (nRecv > 0)
{
char str[50];
sprintf_s(str, "%dthread send message", sthread->threadID);
OutputMessage(str);
OutputMessage(buff);
char mess[] = "server:收到了你的消息。";
send(sthread->csocket, mess, sizeof(mess), 0);
}
else
{
char str[50];
sprintf_s(str, "ID%d is exit", sthread->threadID);
OutputMessage(str);
sthread->isRuning = false;
}
}
return;
}
void SocketServerTest::OutputMessage(const char *outstr)
{
std::cout << outstr << std::endl;
}
相较于服务器,客户端这边改动有点打,我将接受服务器消息的函数设置成了一个单独的线程,发送服务器消息的函数也被我提出来了。不过总体思路和上篇的没多大区别。
//multipartiteClientSocket.h
#pragma once
#include
#include
#include
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
typedef struct thread1
{
std::thread *t1 = nullptr;
bool isRuning = false;
} Mythread;
class MultipartiteClientSocketTest
{
public:
MultipartiteClientSocketTest();
~MultipartiteClientSocketTest();
bool CreateSocket();
void CloseSocket();
bool Myconnect(const char* ip, const unsigned short prot);
void Mysend();
void Myrecv();
bool InitMyrecv();
void OutputMessage(const char* outstr);
private:
char m_message[256];
SOCKET m_nLocalSocket;
Mythread recvThread;
};
multipartiteClientSocket.cpp
#include "multipartiteClientSocket.h"
MultipartiteClientSocketTest::MultipartiteClientSocketTest()
{
m_nLocalSocket = -1;
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
OutputMessage("Socket版本加载失败");
}
MultipartiteClientSocketTest::~MultipartiteClientSocketTest()
{
CloseSocket();
}
void MultipartiteClientSocketTest::CloseSocket()
{
if (m_nLocalSocket != -1)
closesocket(m_nLocalSocket); //关闭socket连接
m_nLocalSocket = -1;
WSACleanup(); //终止ws2_32.lib的使用
}
//创建一个socket
bool MultipartiteClientSocketTest::CreateSocket()
{
if (m_nLocalSocket == -1)
{
m_nLocalSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_nLocalSocket != INVALID_SOCKET)
{
OutputMessage("客服端socket启动成功");
return true;
}
else
{
OutputMessage("客服端socket启动失败");
return false;
}
}
OutputMessage("客服端socket已启动");
return true;
}
bool MultipartiteClientSocketTest::Myconnect(const char* ip, const unsigned short prot)
{
int nRet = SOCKET_ERROR;
if (m_nLocalSocket != -1)
{
sockaddr_in m_nServeraddr;
memset(&m_nServeraddr, 0, sizeof(m_nServeraddr));
m_nServeraddr.sin_family = AF_INET;
m_nServeraddr.sin_port = htons(prot);
m_nServeraddr.sin_addr.s_addr = inet_addr(ip);
nRet = connect(m_nLocalSocket, (sockaddr*)&m_nServeraddr, sizeof(m_nServeraddr));//成功返回0。否则返回SOCKET_ERROR
if (nRet == SOCKET_ERROR)
{
OutputMessage("服务器连接失败!");
return false;
}
OutputMessage("服务器连接成功!");
InitMyrecv();
return true;
}
return false;
}
bool MultipartiteClientSocketTest::InitMyrecv()
{
if (m_nLocalSocket == -1)
return false;
if (recvThread.t1 == nullptr)
{
recvThread.isRuning = true;
std::thread t(&MultipartiteClientSocketTest::Myrecv, this);
t.detach();
recvThread.t1 = &t;
}
else
{
OutputMessage("recvThread is failed!");
return false;
}
Mysend();
return true;
}
void MultipartiteClientSocketTest::Myrecv()
{
if (m_nLocalSocket != -1)
{
int resultRecv = -1;
while (recvThread.isRuning == true)
{
resultRecv = recv(m_nLocalSocket, m_message, sizeof(m_message), 0);
if (resultRecv > 0)
{
//输出消息
OutputMessage(m_message);
memset(m_message, '\0', sizeof(m_message));
}
else
{
//这几种错误码,认为连接是正常的,继续接收
if ((resultRecv < 0) && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
{
continue;//继续接收数据
}
OutputMessage("与服务器连接中断!");
break;//跳出接收循环
}
}
}
else
{
OutputMessage("当前与服务器未连接!");
}
recvThread.t1 = nullptr;
return;
}
void MultipartiteClientSocketTest::Mysend()
{
if (m_nLocalSocket != -1)
{
char tempstr[256];
while (std::cin >> tempstr)
{
send(m_nLocalSocket, tempstr, sizeof(tempstr), 0);
}
}
else
{
OutputMessage("当前与服务器未连接");
}
return;
}
void MultipartiteClientSocketTest::OutputMessage(const char * outstr)
{
std::cout << outstr << std::endl;
}
虽然解决了一对多的问题,但是当有大量客户端连接服务器的时候,你就需要开这么多线程来对应客户端连接,而如此多的线程必定会大大降低效率,所以这种方式是不科学的。那我们能不能在一个或几个线程里来处理这么多客户端连接呢?答案肯定是:能。也就是大家所说的IO多路复用。因为在windows的环境,所以下一篇会写一个select模型的socket通讯。