TCP是寫通訊軟件常用的一種通信方式,以前實習就寫過這個,現在工作中基本上都是作爲C/S模型中的客戶端去跟服務端對接的,今天趁項目還沒開始忙,把一個完整的服務端一對多模型記錄一下(後面有個封裝成類的接口,感興趣的可以自己複製粘貼玩玩);
對於TCP服務端,用代碼構建有以下幾個步驟:
(1)啓動網絡庫;
(2)綁定服務端套接字;
(3)監聽服務端套接字;
(4)服務端套接字接收客戶端連接,之後即可自由通信;
對於客戶端,步驟就比較簡單了:
(1)啓動網絡庫;
(2)連接服務端套接字,連接成功即可自由通信。
服務端代碼:
#include <WinSock2.h>
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include <process.h>
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 <WinSock2.h>
#include <tchar.h>
#include <Windows.h>
#include <iostream>
#include <process.h>
#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 <Windows.h>
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 <iostream>
#include <process.h>
#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 <WinSock2.h>
#include <tchar.h>
#include <iostream>
#include <process.h>
#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函數只是從系統接收的消息裏去取出這些消息,它同樣只是代碼層面的應用。