Socket即套接字,用于网络通讯,有三种模式:
- 流式套接字(SOCK_STREAM)
- 数据报套接字(SOCK_DGRAM)
- 原始套接字(SOCK_RAW)
辅助函数
Windows下提供了WSAXXX系列函数来辅助Socket的开发。为了使用这些函数需要:
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <Mswsock.h>
#pragma comment(lib, "Ws2_32.lib")
初始化
在使用Socket接口前,需要先做初始化,并在完成时做清理工作:
void initSocket() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
int nError = WSAGetLastError();
throw XuException("WSAStartup fail", nError);
}
}
void uninitSocket() {
WSACleanup();
}
地址与端口
通过Socket句柄,可以方便地获取本地与对端的连接地址与端口:
std::string peerAddress(SOCKET sock_, int *pPort_) {
struct sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock_, (struct sockaddr*)&addr, &addrLen) == SOCKET_ERROR) {
int nError = WSAGetLastError();
throw XuException("getpeername fail", nError);
}
char szBuff[INET6_ADDRSTRLEN] = { 0 };
if (inet_ntop(addr.sin_family, &addr.sin_addr, szBuff, INET6_ADDRSTRLEN) == nullptr) {
int nError = WSAGetLastError();
throw XuException("inet_ntop fail", nError);
}
if(nullptr!= pPort_)
*pPort_ = ntohs(addr.sin_port);
return szBuff;
}
std::string localAddress(SOCKET sock_, int *pPort_) {
struct sockaddr_in addr;
int addrLen = sizeof(addr);
if (getsockname(sock_, (struct sockaddr*)&addr, &addrLen) == SOCKET_ERROR) {
int nError = WSAGetLastError();
throw XuException("getpeername fail", nError);
}
char szBuff[INET6_ADDRSTRLEN] = { 0 };
if (inet_ntop(addr.sin_family, &addr.sin_addr, szBuff, INET6_ADDRSTRLEN) == nullptr) {
int nError = WSAGetLastError();
throw XuException("inet_ntop fail", nError);
}
if (nullptr != pPort_)
*pPort_ = ntohs(addr.sin_port);
return szBuff;
}
连接时长
通过Socket句柄,也可以方便地获取已连接的时长:
int connectedSeconds(SOCKET sock_){
DWORD dwSeconds = 0;
int nLen = sizeof(dwSeconds);
if (SOCKET_ERROR == getsockopt(sock_, SOL_SOCKET, SO_CONNECT_TIME, (char*)&dwSeconds, &nLen)) {
return -1;
}
return (int)dwSeconds;
}
TCP服务端
TCP是面向连接的,使用的是流式套接字。为了完成实现连接,服务端需要:
- 建立Socket;
- 绑定端口与地址;
- 侦听,并等待连接;
所有客户端连接通过map来保存,以下是相关定义:
bool g_bStop = false;
SOCKET g_sockListen = INVALID_SOCKET;
map<int, SOCKET> g_mapConnected;
侦听等待
为了建立通讯连接,服务端需要先侦听,等待客户端连接的到来。
若要在指定地址上侦听,则需要明确设定要侦听的地址;若要在本机所有地址上侦听,则只需设定侦听地址为INADDR_ANY
即可。
void acceptClientConnect(unsigned short nPort,
function<void(const string&, int)> funError,
function<void(int, const string&)> funMsg) {
g_mapConnected.clear();
string strError;
SOCKET sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == sockListen) {
strError = "socket";
goto FUN_CLEAN_UP;
}
SOCKADDR_IN sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(nPort);
// 侦听回环地址
//string strIP = "127.0.0.1";
//inet_pton(AF_INET, strIP.c_str(), &(sockAddr.sin_addr));
sockAddr.sin_addr.s_addr = INADDR_ANY; // 侦听所有(0.0.0.0)
if (::bind(sockListen, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) {
strError = "bind";
goto FUN_CLEAN_UP;
}
if (listen(sockListen, 1) == SOCKET_ERROR) {
strError = "listen";
goto FUN_CLEAN_UP;
}
g_sockListen = sockListen;
// to accept
{
string strAddr = localAddress(sockListen);
cout << "Listen at " << strAddr << ":" << nPort << endl;
while (!g_bStop) {
SOCKET sockConn = accept(sockListen, NULL, NULL);
if (INVALID_SOCKET == sockConn) {
strError = "accept";
goto FUN_CLEAN_UP;
}
// to start sock handler
thread thrClient(handleClientSocket, sockConn, funError, funMsg);
thrClient.detach();
}
}
FUN_CLEAN_UP:
int nError = WSAGetLastError();
//cout << strError << " fail: " << nError << endl;
if (nullptr != funError) {
funError(strError, nError);
}
return;
}
接收消息
客户端连接成功后,启动一个独立的线程来接收消息;并把Socket句柄保存到map中,方便需要时(如给指定客户端发消息)获取。
void handleClientSocket(SOCKET sock,
function<void(const string&, int)> funError,
function<void(int, const string&)> funMsg) {
int nPort = 0;
string strAddr = peerAddress(sock, &nPort);
if (nullptr != funMsg) {
funMsg(nPort, "New connection from " + strAddr + ":" + std::to_string(nPort));
}
const int MaxBuffSize = 255;
char szBuffer[MaxBuffSize + 1] = { 0 };
g_mapConnected[nPort] = sock;
while (!g_bStop) {
int nRet = recv(sock, szBuffer, MaxBuffSize, 0);
if (nRet > 0) {
string strRecv(szBuffer, nRet);
sendMessage(sock, MSG_Ack);
if (nullptr != funMsg) {
funMsg(nPort, strRecv);
}
}
else {
int nError = 0;
string strError = "Connection " + strAddr + ":" + std::to_string(nPort);
if (0 == nRet) {
strError += " gracefully closed";
}
else {
strError += " recv fail";
nError = WSAGetLastError();
}
if (nullptr != funError) {
funError(strError, nError);
}
break;
}
}
g_mapConnected[nPort] = INVALID_SOCKET;
}
发送消息
通过send可以直接发送消息到指定客户端:
void sendMessage(SOCKET sock, string strMsg) {
strMsg += MSG_Delim;
int nLen = send(sock, strMsg.c_str(), strMsg.length(), 0);
if (SOCKET_ERROR == nLen) {
int nError = WSAGetLastError();
cout << "!!!Send " << strMsg << " fail: " << nError << endl;
}
}