一、创建服务器套接字(create)。
二、服务器套接字进行信息绑定(bind),并开始监听连接(listen)。
三、接受来自用户端的连接请求(accept)。
四、开始数据传输(send/receive)。
五、关闭套接字(closesocket)。
1. WSAStartup函数
int WSAStartup
(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
使用Socket的程序在使用Socket之前必须调用WSAStartup函数。其中,第一个参数指明程序请求使用的Socket版本,第二个参数是指向lpWSADATA的指针。
案例:(WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
MAKEWORD(2,2)表示使用WINSOCK2.2版本,data用来存储系统传回的关于WINSOCK的资料。
2. socket函数
函数定义:
SOCKET socket(int domain, int type, int protocol);
domain:协议簇也,就是 IP 地址类型。常用的有 AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
type:协议类型,数据传输方式/套接字类型。常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)
protocol:协议编号,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
注意:Windows 不把套接字作为普通文件对待,而是返回 SOCKET 类型的句柄
3. bind函数
经过socket函数创建套接字后,必须将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。类似地,客户端也要用 connect() 函数建立连接。
函数定义:
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); //Windows
sock:socket 文件描述符。
addr:sockaddr 结构体变量的指针。
addrlen:addr 变量的大小。
案例:下面的代码,将创建的套接字与IP地址 127.0.0.1、端口 1234 绑定:
//创建套接字
SOCKET serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
sockaddr_in 结构体:
struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
in_addr 结构体:
struct in_addr{
in_addr_t s_addr; //32位的IP地址
};
4. listen函数
对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态。
函数定义:
int listen(SOCKET sock, int backlog); //Windows
sock:需要进入监听状态的套接字。
backlog:请求队列的最大长度。
被动监听:是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。
5. accept() 函数
当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
函数定义:
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
sock:服务器端套接字。
addr:sockaddr_in 结构体变量。
addrlen:参数 addr 的长度。
注意:accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
说明:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
6. send() 和recv()函数
由于Windows 区分普通文件和套接字,所以定义了专门的接收recv()和发送send()的函数.
send() 函数
函数定义:
int send(SOCKET sock, const char *buf, int len, int flags);
sock:要发送数据的套接字。
buf:要发送的数据的缓冲区地址。
len:要发送的数据的字节数。
flags:发送数据时的选项, flags 一般设置为 0 或 NULL。
成功则返回写入的字节数,失败则返回 -1。
recv() 函数
函数定义:
int recv(SOCKET sock, char *buf, int len, int flags);
sock:要读取的文件的描述符。
buf:要接收数据的缓冲区地址。
nbytes:要读取的数据的字节数。
flags:接收数据时的选项, flags 一般设置为 0 或 NULL。
7. closesocket()函数
函数定义:
int WSACleanup (void);
closesocket()函数为windows下用于释放系统分配给套接字的资源(linux下为close()函数)。
8. WSACleanup()函数
应用程序在完成对请求的Socket库的使用后,要调用WSACleanup()函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
int main()
{
WSADATA data{};//存放windows socket初始化信息
if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
{
std::cout << "WSAStartup func error, error num is :" << WSAGetLastError() << std::endl;
return -1;
}
SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (servSock == INVALID_SOCKET)
{
std::cout << "socket func error, error num is : " << WSAGetLastError() << std::endl;
WSACleanup();
return -1;
}
sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
//htons htonl
//htons 就是 host to net short
servAddr.sin_port = htons(2345);
//htonl host to net long
inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr.S_un.S_addr);
if (bind(servSock, (sockaddr*)&servAddr, sizeof(sockaddr)) == SOCKET_ERROR)
{
std::cout << "bind func error, error num is : " << WSAGetLastError() << std::endl;
closesocket(servSock);
WSACleanup();
return -1;
}
if (listen(servSock, 128) == -1)
{
std::cout << "listen func error, error num is : " << WSAGetLastError() << std::endl;
closesocket(servSock);
WSACleanup();
return -1;
}
SOCKET clitsock = accept(servSock, nullptr, nullptr);
if (clitsock == INVALID_SOCKET)
{
std::cout << "accept func error, error num is : " << WSAGetLastError() << std::endl;
closesocket(servSock);
WSACleanup();
return -1;
}
const char* msg = "hello world";
char recvBuf[128]{};
if (send(clitsock, msg, strlen(msg), 0) < 0)
{
std::cout << "send func error, error num is : " << WSAGetLastError() << std::endl;
closesocket(servSock);
closesocket(clitsock);
WSACleanup();
return -1;
}
int recvRet = recv(clitsock, recvBuf, 128, 0);
if (recvRet == 0)
{
std::cout << "client drop the connection gracefully" << std::endl;
}
if (recvRet < 0)
{
int errorNum = WSAGetLastError();
if (errorNum == 10054)
{
std::cout << "客户端强制断开连接" << errorNum << std::endl;
}
else
{
std::cout << "recv func error, error num is : " << errorNum << std::endl;
closesocket(servSock);
closesocket(clitsock);
WSACleanup();
return -1;
}
}
std::cout << recvBuf << std::endl;
closesocket(servSock);
closesocket(clitsock);
return 0;
}
一、加载socket库(WSADATA wsdata;)
二、创建socket套接字(socket函数)
三、初始化客户端和服务端的地址包
四、与服务器创建连接(connect函数)
五、开始传输数据(send/recv函数)
六、关闭套接字(closesock函数)
1. connect() 函数
connect() 函数用来建立连接
函数定义:
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
sock:socket 文件描述符。
addr:sockaddr 结构体变量的指针。
addrlen:addr 变量的大小。
其余相关函数均与服务器模块相同,这里不再赘述。
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA data{};
if (WSAStartup(MAKEWORD(2, 2), &data) == SOCKET_ERROR)
{
std::cout << "WSAStartup func error, error num is :" << WSAGetLastError() << std::endl;
return -1;
}
SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (servSock == INVALID_SOCKET)
{
std::cout << "socket func error, error num is : " << WSAGetLastError() << std::endl;
WSACleanup();
return -1;
}
//目的ip和目的端口
sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(2345);
inet_pton(AF_INET, "127.0.0.1", &servAddr.sin_addr.S_un.S_addr);
if (connect(servSock, (sockaddr*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
std::cout << "connect func error, error num is : " << WSAGetLastError() << std::endl;
closesocket(servSock);
WSACleanup();
return -1;
}
char recvBuf[128]{};
int recvLen = recv(servSock, recvBuf, 128, 0);
if (recvLen == 0)
{
std::cout << "client drop the connection gracefully" << std::endl;
}
if (recvLen < 0)
{
int errorNum = WSAGetLastError();
if (errorNum == 10054)
{
std::cout << "客户端强制断开连接" << errorNum << std::endl;
}
else
{
std::cout << "recv func error, error num is : " << errorNum << std::endl;
closesocket(servSock);
WSACleanup();
return -1;
}
}
if (send(servSock, recvBuf, recvLen, 0) < 0)
{
std::cout << "send func error, error num is : " << WSAGetLastError() << std::endl;
closesocket(servSock);
WSACleanup();
return -1;
}
send(servSock, recvBuf, recvLen, 0);
closesocket(servSock);
WSACleanup();
system("pause");
return 0;
}
参考链接