套接字有两种概念:服务器和客户端
下面是用于创建流式 (steam)TCP/IP 服务器和客户端的常规模型。
服务器
- 初始化 Winsock。
- 创建套接字。
- 绑定套接字。
- 在套接字上监听客户端。
- 接受来自客户端的连接。
- 接收和发送数据。
- 断开连接
客户端
- 初始化 Winsock。
- 创建套接字。
- 连接到该服务器。
- 发送和接收数据。
- 断开连接
很明显, 1, 2, 还有 断开连接 步骤完全相同
0.检测环境
用vs, 调试下面代码, 判断环境是否准备充分
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
printf("Hello World");
return 0;
}
1. 初始化
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
printf("Hello World");
return 0;
}
2. 服务器端创建套接字
首先为服务器创建套接字, 这样接下来的客户端就可以连接调试
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define DEFAULT_PORT "9501" // 服务器监听的端口
int main() {
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
// 创建socket对象
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
return 0;
}
每个步骤尽量都要调试一番, 已检查是否错误
3.绑定套接字
若要使服务器接受客户端连接,它必须绑定到服务器的网络地址。
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define DEFAULT_PORT "9501" // 服务器监听的端口
int main() {
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
// 创建socket对象
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 绑定套接字 start
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 3. 绑定套接字 end
return 0;
}
4.在套接字上监听客户端
将套接字绑定到系统上的 IP 地址和端口之后,服务器必须在该 IP 地址和端口上侦听传入的连接请求
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define DEFAULT_PORT "9501" // 服务器监听的端口
int main() {
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
// 创建socket对象
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 绑定套接字 start
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 3. 绑定套接字 end
// 4.在套接字上监听客户端 start
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
// SOMAXCONN定义了此套接字允许最大连接
printf("Listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 4.在套接字上监听客户端 end
return 0;
}
备注: window10第一次调试这一步骤会让用户给予防火墙权限
5. 接受来自客户端的连接。
当套接字侦听连接后,程序必须处理该套接字上的连接请求
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#define DEFAULT_PORT "9501" // 服务器监听的端口
int main() {
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
// 创建socket对象
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 绑定套接字 start
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 3. 绑定套接字 end
// 4.在套接字上监听客户端 start
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
// SOMAXCONN定义了此套接字允许最大连接
printf("Listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 4.在套接字上监听客户端 end
// 5.接受来自客户端的连接 start
SOCKET ClientSocket; //临时 套接字 对象,以接受来自客户端的连接
// 这里没有考虑多线程并发接受多个客户端 只接受一个
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 5.接受来自客户端的连接 end
return 0;
}
备注:运行这一步时, 控制台似乎没有显示任何东西, 其实 是accept 将逻辑流程卡住 等待 客户端连接, 如下图所示
6.在服务器上接收和发送数据
服务器接收的数据来自客户端, 发送也是向客户端发送数据, 故而需要等下面的客户端socket编写完毕才能进行最终的功能测试.
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
#define DEFAULT_PORT "9501" // 服务器监听的端口
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
// 创建socket对象
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 绑定套接字 start
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 3. 绑定套接字 end
// 4.在套接字上监听客户端 start
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
// SOMAXCONN定义了此套接字允许最大连接
printf("Listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 4.在套接字上监听客户端 end
// 5.接受来自客户端的连接 start
SOCKET ClientSocket; //临时 套接字 对象,以接受来自客户端的连接
// 这里没有考虑多线程并发接受多个客户端 只接受一个
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 5.接受来自客户端的连接 end
// 6.在服务器上接收和发送数据 start
#define DEFAULT_BUFLEN 512 // 字符缓冲区长度
char recvbuf[DEFAULT_BUFLEN]; // 字符缓冲区数组
int iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// recvbuf参数表示: 这里为了简单将客户端发送过来的消息再发送给客户端
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// 6.在服务器上接收和发送数据 end
return 0;
}
备注: 这一步相当于完成了服务器的书写,但为了保险, 还是要关闭连接
7.断开连接
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
#define DEFAULT_PORT "9501" // 服务器监听的端口
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
// 创建socket对象
SOCKET ListenSocket = INVALID_SOCKET;
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 绑定套接字 start
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 3. 绑定套接字 end
// 4.在套接字上监听客户端 start
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
// SOMAXCONN定义了此套接字允许最大连接
printf("Listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 4.在套接字上监听客户端 end
// 5.接受来自客户端的连接 start
SOCKET ClientSocket; //临时 套接字 对象,以接受来自客户端的连接
// 这里没有考虑多线程并发接受多个客户端 只接受一个
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// 5.接受来自客户端的连接 end
// 6.在服务器上接收和发送数据 start
#define DEFAULT_BUFLEN 512 // 字符缓冲区长度
char recvbuf[DEFAULT_BUFLEN]; // 字符缓冲区数组
int iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// recvbuf参数表示: 这里为了简单将客户端发送过来的消息再发送给客户端
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// 6.在服务器上接收和发送数据 end
// 7.断开连接 start
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// 7.断开连接 end
return 0;
}
备注: 这里没有写控制台输入判断 进行关闭服务器 而是等客户端传输完数据后自动执行关闭逻辑
1.初始化
这一步和服务端一样, 见最上面
2.客户端创建套接字
需要重新用vs创建一个新项目当做客户端
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
printf("启动客户端");
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ConnectSocket = INVALID_SOCKET;
ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
return 0;
}
3.客户端连接到该服务器
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
printf("启动客户端");
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ConnectSocket = INVALID_SOCKET;
ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 连接到套接字 start
for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
// 3. 连接到套接字 end
freeaddrinfo(result);
return 0;
}
4.客户端发送和接收数据
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
printf("启动客户端");
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ConnectSocket = INVALID_SOCKET;
ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 连接到套接字 start
for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
// 3. 连接到套接字 end
freeaddrinfo(result);
// 4.在客户端上发送和接收数据 start
#define DEFAULT_BUFLEN 512
int recvbuflen = DEFAULT_BUFLEN;
const char* sendbuf = "Hello World";
char recvbuf[DEFAULT_BUFLEN];
// 发送
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
// 关闭
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// 接收
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
else if (iResult == 0)
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);
// 4.在客户端上发送和接收数据 end
return 0;
}
5.客户端断开连接
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
int main() {
printf("启动客户端");
// 1. 初始化 start
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}
// 1. 初始化 end
// 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
struct addrinfo*
result = NULL,
* ptr = NULL,
hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
hints.ai_flags = AI_PASSIVE;
// 从本机中获取ip地址等信息为了sockcet 使用
iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
SOCKET ConnectSocket = INVALID_SOCKET;
ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// 2. 创建套接字 end
// 3. 连接到套接字 start
for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
// 3. 连接到套接字 end
freeaddrinfo(result);
// 4.在客户端上发送和接收数据 start
#define DEFAULT_BUFLEN 512
int recvbuflen = DEFAULT_BUFLEN;
const char* sendbuf = "Hello World";
char recvbuf[DEFAULT_BUFLEN];
// 发送
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
// 关闭
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// 接收
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
else if (iResult == 0)
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);
// 4.在客户端上发送和接收数据 end
// 5.断开连接 start
// 这里和服务器断开连接写在最后不同, 客户端断开连接写在 发送后 和 接收前
// shutdown(ConnectSocket, SD_SEND) SD_SEND表示socket的发送数据端虽然关闭(为了服务器释放客户端连接资源), 但是仍然能接收服务端的数据
//iResult = shutdown(ClientSocket, SD_SEND);
//if (iResult == SOCKET_ERROR) {
// printf("shutdown failed: %d\n", WSAGetLastError());
// closesocket(ClientSocket);
// WSACleanup();
// return 1;
//}
closesocket(ConnectSocket);
WSACleanup();
// 5.断开连接 end
return 0;
}
结果
启动服务端 和客户端 可以看到, 双方都能接收到
备注: 本文内容来源于 [微软官方文档] (https://docs.microsoft.com/zh-cn/windows/win32/winsock/using-winsock)
因为文档 内容繁多 ,特做笔记