Socket协议的工作原理
Socket又称套接字,它是TCP/IP网络环境下应用程序与底层通信驱动程序之间运行的开发接口,它可以将应用程序与具体的TCP/IP隔离开,使得应用程序不需要了解TCP/IP的细节,就能够实现传输。
TCP协议使用的send、recv、WSASend和WSARecv,
UDP协议使用的sendto、recvfrom、WSASendTo 和WSARecvFrom。
什么是WSASend?
作用:在一个已连接的套接口上发送数据。
需要包括的库
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib ")
用到的函数
1. WSAStartup 初始化Ws2_32.dll的函数
int WSAStartup(
__in WORD wVersionRequested,
__out LPWSADATA lpWSAData
);
wVersionRequested
标识了用户调用的Winsock的版本号。高字节指明辅版本编号,低字节指明主版本编号。通常使用MAKEWORD来生成一个版本号。 当前Winsock sockets的版本号为2.2,用到的dll是 Ws2_32.dll。
lpWSAData
指向WSADATA结构体的指针,lpWSAData返回了系统对Windows Sockets 的描述
Return Value
如果调用成功,WSAStartup 函数返回0。否则,将返回五种错误代码之一
2. WSACleanup 释放Ws2_32.dl的l函数
int WSACleanup(void);
返回值0表示正常退出,返回值SOCKET_ERROR表示异常。返回值是SOCKET_ERROR,可以调用 WSAGetLastError.查看错误代码。需要注意的是,在多线程环境下,WSACleanup 函数将终止所有线程的socket操作。
3. socket 创建socket的函数
SOCKET WSAAPI socket(
__in int af,
__in int type,
__in int protocol
);
af ( address family)
指明地址簇类型,常用的地址簇如下,其余地址簇在Winsock2.h中定义。
AF_UNSPEC(未指明)、
AF_INET(IPv4)、
AF_NETBIOS(NETBIOS地址簇)、
AF_INET6(IPv6)、
AF_IRDA(Infrared Data Association (IrDA)地址簇)、
AF_BTM(Bluetooth)。
type
指明socket的类型,Windows Sockets 2常见类型如下:
SOCK_STREAM(流套接字,使用TCP协议)、
SOCK_DGRAM(数据报套接字,使用UDP协议)、
SOCK_RAW(原始套接字)、
SOCK_RDM(提供可靠的消息数据报文,reliable message datagram)、
SOCK_SEQPACKET(Provides a pseudo-stream packet based on datagrams,在UDP的基础上提供了伪流数据包)。
protocol
指明数据传输协议,该参数取决于af和type参数的类型。protocol参数在Winsock2.h and Wsrm.h定义。通常使用如下3中协议:
IPPROTO_TCP(TCP协议,使用条件,af是AF_INET or AF_INET6、type是SOCK_STREAM )
IPPROTO_UDP(UDP协议,使用条件,af是AF_INET or AF_INET6、type是SOCK_DGRAM)
IPPROTO_RM(PGM(Pragmatic General Multicast,实际通用组播协议)协议,使用条件,af是AF_INET 、type是SOCK_RDM)
4. bind 服务端将socket与地址关联
5. listen 服务端网络监听
6. accept 服务端connect接收
7. connect 客户端请求服务端连接
8. send 发送数据
TCP连接流程
TCP服务端代码
// WinServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD wVersion = MAKEWORD(2, 2);
WSADATA wsaData = { 0 };
//WinSocket初始化
if (WSAStartup(wVersion, &wsaData))
{
std::cout << "WSAStartup Error" << std::endl;
return 1;
}
//创建socket
SOCKET nServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (nServerSocket == INVALID_SOCKET)
{
std::cout << "create socket Error" << std::endl;
WSACleanup();//释放WinSocket
return 1;
}
sockaddr_in serverAddr = { 0 };
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8888); //绑定端口
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //绑定ip
//绑定ip和端口
if (SOCKET_ERROR == bind(nServerSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)))
{
std::cout << "bind Error" << std::endl;
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
return 1;
}
//监听 最大连接数为5
if (SOCKET_ERROR == listen(nServerSocket, 5))
{
std::cout << "listen Error" << std::endl;
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
return 1;
}
SOCKET nClientSocket = { 0 };
sockaddr_in clientAddr = { 0 };
int nLen = sizeof(clientAddr);
//接受客户端连接请求
nClientSocket = accept(nServerSocket, (sockaddr*)&clientAddr, &nLen);
if (INVALID_SOCKET == nClientSocket)
{
std::cout << "accept Error" << std::endl;
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
}
char szBuffer[255] = { 0 };
int nRet = 0;
//接收客户端数据
while (true)
{
memset(szBuffer, 0, 255);
//接受客户端发来的数据
nRet = recv(nClientSocket, szBuffer, 255, 0);
if (nRet == SOCKET_ERROR)
{
std::cout << "recv Error" << std::endl;
closesocket(nClientSocket);
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
return 1;
}
std::cout << szBuffer << std::endl;
nRet = send(nClientSocket, szBuffer, 255, 0);
if (nRet == SOCKET_ERROR)
{
std::cout << "send Error" << std::endl;
closesocket(nClientSocket);
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
return 1;
}
}
closesocket(nClientSocket);
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
return 0;
}
TCP客户端代码
// WinClient.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <WS2tcpip.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData = { 0 };
SOCKET nClientSocket = { 0 };
int nRet = 0;
char szBuffer[255];
//WinSocket初始化
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "WSAStartup failed!\n";
return 1;
}
//创建socket
nClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (nClientSocket == INVALID_SOCKET)
{
std::cout << "socket failed!\n";
//释放
WSACleanup();
return 1;
}
SOCKADDR_IN addrServ = { 0 };
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(8888); //绑定端口
inet_pton(AF_INET, "127.0.0.1", &addrServ.sin_addr); //服务器ip
//连接服务器
nRet = connect(nClientSocket, (LPSOCKADDR)&addrServ, sizeof(SOCKADDR_IN));
if (nRet == SOCKET_ERROR)
{
std::cout << "connect failed!\n";
closesocket(nClientSocket);
WSACleanup();
return 1;
}
while (true)
{
memset(szBuffer, 0, 255);
std::cout << "Please input a string:";
std::cin >> szBuffer;
//发送数据
nRet = send(nClientSocket, szBuffer, strlen(szBuffer), 0);
if (nRet == SOCKET_ERROR)
{
std::cout << "send failed!\n";
closesocket(nClientSocket);
WSACleanup();
return -1;
}
memset(szBuffer, 0, sizeof(szBuffer));
//接收数据
nRet = recv(nClientSocket, szBuffer, sizeof(szBuffer), 0);
if (nRet == SOCKET_ERROR)
{
std::cout << "recv failed!\n";
closesocket(nClientSocket);
WSACleanup();
return 1;
}
std::cout << szBuffer << std::endl;
}
closesocket(nClientSocket);
WSACleanup();
return 0;
}
UDP
sendto
int sendto(
__in SOCKET s, //指定套接字(可能已连接)
__in const char *buf, //指向将要发送数据的缓冲区
__in int len, //缓冲区大小
__in int flags, //指定数据传输方式
__in const struct sockaddr *to, //可选的指针,指向存储目标套接字地址信息的sockaddr结构
__in int tolen //to参数指向结构的字节大小
);
返回值:
成功时,返回发送的字节数;
失败时,返回SOCKET_ERROR,调用WSAGetLastError函数查看进一步错误信息。
recvfrom
recvfrom函数用于接收数据报并存储发送方源地址信息:
int recvfrom(
__in SOCKET s, //指定一个绑定的socket
__out char *buf, //存放接到的数据的缓冲区
__in int len, //缓冲区字节大小
__in int flags, //指定传输控制方式
__out struct sockaddr *from, //可选的指针,函数成功返回后,
//指向存储源套接字地址信息的sockaddr结构
__inout_opt int *fromlen //from的字节大小
);
返回值:
成功时,返回接收的字节数;
失败时,返回SOCKET_ERROR,调用WSAGetLastError函数查看进一步错误信息。
UDP服务端
// WinServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WORD wVersion = MAKEWORD(2, 2);
WSADATA wsaData = { 0 };
//WinSocket初始化
if (WSAStartup(wVersion, &wsaData))
{
std::cout << "WSAStartup Error" << std::endl;
return 1;
}
//创建socket
SOCKET nServerSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (nServerSocket == INVALID_SOCKET)
{
std::cout << "create socket Error" << std::endl;
WSACleanup();//释放WinSocket
return 1;
}
sockaddr_in serverAddr = { 0 };
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8888); //绑定端口
serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //绑定ip
//绑定ip和端口
if (SOCKET_ERROR == bind(nServerSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)))
{
std::cout << "bind Error" << std::endl;
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
return 1;
}
sockaddr clientAddr = { 0 };
int nLen = sizeof(clientAddr);
char szBuffer[255] = { 0 };
int nRet = recvfrom(nServerSocket, szBuffer, 255, 0, &clientAddr, &nLen);
if (nRet)
{
std::cout << szBuffer << std::endl;
}
closesocket(nServerSocket);
WSACleanup();//释放WinSocket
getchar();
return 0;
}
UDP客户端
// WinClient.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <WS2tcpip.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData = { 0 };
SOCKET nClientSocket = { 0 };
//WinSocket初始化
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
std::cout << "WSAStartup failed!\n";
return 1;
}
//创建socket
nClientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (nClientSocket == INVALID_SOCKET)
{
std::cout << "socket failed!\n";
//释放
WSACleanup();
return 1;
}
sockaddr_in addrServ = { 0 };
addrServ.sin_family = AF_INET;
addrServ.sin_port = htons(8888); //服务器端口
inet_pton(AF_INET, "127.0.0.1", &addrServ.sin_addr); //服务器ip
char szBuffer[255] = "123456";
int nRet = sendto(nClientSocket, szBuffer, 255, 0, (sockaddr*)&addrServ, sizeof(addrServ));
if (nRet)
{
std::cout << "send success" << std::endl;
}
closesocket(nClientSocket);
WSACleanup();
getchar();
return 0;
}