TCP是写通讯软件常用的一种通信方式,以前实习就写过这个,现在工作中基本上都是作为C/S模型中的客户端去跟服务端对接的,今天趁项目还没开始忙,把一个完整的服务端一对多模型记录一下(后面有个封装成类的接口,感兴趣的可以自己复制粘贴玩玩);
对于TCP服务端,用代码构建有以下几个步骤:
(1)启动网络库;
(2)绑定服务端套接字;
(3)监听服务端套接字;
(4)服务端套接字接收客户端连接,之后即可自由通信;
对于客户端,步骤就比较简单了:
(1)启动网络库;
(2)连接服务端套接字,连接成功即可自由通信。
服务端代码:
#include
#include
#include
#include
#include
void ReceiveAllClients(LPVOID para)
{
/*这一步很重要,拿到了客户端的SOCKET套接字地址,要先解引用把它的值取出来,
不要直接对这个指针操作,因为所有的客户端套接字地址都是通过这个指针传进来的*/
SOCKET newClient = *((SOCKET *)para);
char msg[200] = {0};
sockaddr_in clientMsg;
int len = sizeof(sockaddr);
getpeername(newClient, (sockaddr *)&clientMsg, &len);/*获取客户端的socket的ip和端口信息,方便查看谁连了上来*/
std::cout << clientMsg.sin_addr.S_un.S_addr << ":" << clientMsg.sin_port << " has connected!\n";
while (1)
{
memset(msg, '\0', sizeof(msg));
if (recv(newClient, msg, sizeof(msg), NULL) < 0)
{
std::cout << "receive message fail!\n";
return;
}
std::cout << "Client(" << clientMsg.sin_port << "): " << msg << std::endl;
send(newClient, "收到,请加大力度", 100, NULL);
/*其实一般项目,服务端收到客户端消息后,都是需要解析这个消息并返回对应报文的,肯定不会这么随意*/
}
}
int main(int argc, _TCHAR* argv[])
{
SOCKET server;
SOCKET client;
sockaddr_in serverAddr;
WSADATA wsaData;
int wsaRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
/*windows编译器必须要启动这个网络库,否则后面的步骤都会执行失败,但是linux操作系统好像不需要这个*/
if (wsaRet)
{
std::cout << "WSAStartup fail!\n";
return 0;
}
else
{
std::cout << "WSAStartup succeed!\n";
}
server = socket(AF_INET, SOCK_STREAM, 0);
//bind
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(23);
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (bind(server, (sockaddr *)&serverAddr, sizeof(sockaddr)) == SOCKET_ERROR)
{
std::cout << "bind fail!\n";
return 0;
}
else
{
std::cout << "bind succeed!\n";
}
//listen
if (listen(server, 5) == SOCKET_ERROR)
{
std::cout << "listen fail!\n";
return 0;
}
else
{
std::cout << "listen succeed!\n";
}
//accept
while (1)
{
client = accept(server,NULL, NULL);
if (client >= 0)
{
/*accept和recv函数都是阻塞函数,这种情况基本上都要开多线程的*/
_beginthread(ReceiveAllClients, NULL, &client);
}
else
{
std::cout << "accept fail!\n";
}
}
system("pause");
return 0;
}
客户端代码:
#include
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
void ReceiveFromServer(LPVOID para)
{
if (para == nullptr)
{
std::cout << "Server is null!\n";
return;
}
char msg[200] = {0};
SOCKET server = *((SOCKET *)(para));
while(1)
{
memset(msg, '\0', sizeof(msg));
if (recv(server, msg, 200, NULL) < 0)
{
std::cout << "recv fail!\n";
return;
}
std::cout << "(Server): " << msg << std::endl;
}
}
int main(int argc, _TCHAR* argv[])
{
SOCKET server;
sockaddr_in serverAddr;
int serverLen = sizeof(serverAddr);
WSADATA wsaData;
char msg[200] = {0};
if (!WSAStartup(MAKEWORD(2, 2), &wsaData))
{
std::cout << "WSAStartup succeed!\n";
}
else
{
std::cout << "WSAStartup fail!\n";
return 0;
}
server = socket(AF_INET, SOCK_STREAM, NULL);
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(23);
if (SOCKET_ERROR == connect(server, (SOCKADDR *)&serverAddr, sizeof(serverAddr)))
{
std::cout << "fail!\n";
}
else
{
_beginthread(ReceiveFromServer, NULL, &server);
std::cout << "succeed!\n";
}
while(1)
{
memset(msg, '\0', sizeof(msg));
std::cin.getline(msg, 100);
send(server, msg, 100, NULL);
}
system("pause");
return 0;
}
/*************************************/
下面是TCP服务端封装成类的接口形式:
.h文件
#pragma once
#include
class TcpServer
{
public:
TcpServer(void);
virtual ~TcpServer(void);
BOOL OpenTcp();
protected:
BOOL WsaStartUp();
BOOL Bind();
BOOL Listen();
BOOL Accept();
BOOL ReceiveAllClients();
static void StartAcceptThreadDo(LPVOID para);
static void StartReceiveThreadDo(LPVOID para);
private:
SOCKET m_Server;
SOCKET m_Client;
sockaddr_in m_ServerAddr;
WSADATA m_WsaData;
};
.cpp文件
#include
#include
#include "TcpServer.h"
#pragma comment(lib, "ws2_32.lib")
TcpServer::TcpServer(void)
{
}
TcpServer::~TcpServer(void)
{
}
BOOL TcpServer::OpenTcp()
{
if (!WsaStartUp())
{
return FALSE;
}
if (!Bind())
{
return FALSE;
}
if (!Listen())
{
return FALSE;
}
_beginthread(StartAcceptThreadDo, NULL, this);
return TRUE;
}
BOOL TcpServer::WsaStartUp()
{
int wsaRet = WSAStartup(MAKEWORD(2, 2), &m_WsaData);
if (wsaRet)
{
std::cout << "WSAStartup fail!\n";
return FALSE;
}
else
{
std::cout << "WSAStartup succeed!\n";
}
return TRUE;
}
BOOL TcpServer::Bind()
{
m_Server = socket(AF_INET, SOCK_STREAM, NULL);
//bind
m_ServerAddr.sin_family = AF_INET;
m_ServerAddr.sin_port = htons(23);
m_ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (bind(m_Server, (sockaddr *)&m_ServerAddr, sizeof(sockaddr)) == SOCKET_ERROR)
{
std::cout << "bind fail!\n";
return 0;
}
else
{
std::cout << "bind succeed!\n";
}
return TRUE;
}
BOOL TcpServer::Listen()
{
//listen
if (listen(m_Server, 5) == SOCKET_ERROR)
{
std::cout << "listen fail!\n";
return FALSE;
}
else
{
std::cout << "listen succeed!\n";
}
return TRUE;
}
BOOL TcpServer::Accept()
{
//accept
while (1)
{
m_Client = accept(m_Server,NULL, NULL);
if (m_Client >= 0)
{
_beginthread(StartReceiveThreadDo, NULL, this);
}
else
{
std::cout << "accept fail!\n";
return FALSE;
}
}
return TRUE;
}
BOOL TcpServer::ReceiveAllClients()
{
SOCKET newClient = m_Client;
char msg[200] = {0};
sockaddr_in clientMsg;
int len = sizeof(sockaddr);
getpeername(newClient, (sockaddr *)&clientMsg, &len);/*获取客户端的socket的ip和端口信息,方便查看谁连了上来*/
std::cout << clientMsg.sin_addr.S_un.S_addr << ":" << clientMsg.sin_port << " has connected!\n";
while (1)
{
memset(msg, '\0', sizeof(msg));
if (recv(newClient, msg, sizeof(msg), NULL) < 0)
{
std::cout << "receive message fail!\n";
return FALSE;
}
std::cout << "Client(" << clientMsg.sin_port << "): " << msg << std::endl;
send(newClient, "收到,请加大力度", 100, NULL);
/*其实一般项目,服务端收到客户端消息后,都是需要解析这个消息并返回对应报文的,肯定不会这么随意*/
}
return TRUE;
}
void TcpServer::StartAcceptThreadDo(LPVOID para)
{
TcpServer *server = static_cast<TcpServer *>(para);
server->Accept();
}
void TcpServer::StartReceiveThreadDo(LPVOID para)
{
TcpServer *server = static_cast<TcpServer *>(para);
server->ReceiveAllClients();
}
这样封装后,主函数里的调用就比较方便了;
main.cpp
#include
#include
#include
#include
#include "TcpServer.h"
int main(int argc, _TCHAR* argv[])
{
TcpServer sss666;
sss666.OpenTcp();/*调一个接口函数,就打开TCP连接了*/
while (1)/*这个while循环只是代替了后面的业务逻辑代码,其实后面要做什么都无所谓*/
{
getchar();
}
system("pause");
return 0;
}
客户端比较简单,就懒得写了,总之最后的运行效果跟之前是一样的。
/*
分
割
线
*/
其中有几个重要知识点得说明一下:
(1)TCP的三次握手建立连接,其实跟accept这种函数没关系,哪怕accept执行失败,还是可以建立连接的,accept只是代码层面的逻辑;
(2)同理,recv函数也需要注意一下,哪怕服务端不调用这个函数,也能收到客户端发来的消息,因为在与客户端建立连接后,这个端口就已经被系统一直监视着并接收消息了,recv函数只是从系统接收的消息里去取出这些消息,它同样只是代码层面的应用。