服务器端
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 这些函数都被微软定为不安全函数,想正常使用就必须在代码最前面定义宏
#include <iostream>
#include <string>
#include <winsock2.h> // 网络头文件,Windows socket第二版
#pragma comment(lib, "ws2_32.lib") // 加载网络库,Windows socket第二版,32位
// 服务器端
using namespace std;
// 主函数
int main() {
// 1:初始化Winsock库/打开网络库启动了这个库,库里的函数才可以使用
// 参数一:WORD(由 unsigned short 转定义而来) WORD verSion = MAKEWORD(2, 2); 其中MAKEWORD(主版本,副版本)
// 参数二:类型为LPWSADATA lpWSAData,系统获取网络配置信息,然后返回给此参数;当参数前有LP前缀时
// 参数二:应传对应类型变量地址 定义一个WSADATA结构体,然后传入它的地址 WSADATA wsa;
// 返回值:函数原型WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); 返回0表示成功
WORD verSion = MAKEWORD(2, 2); //初始化套节字
WSADATA wsa; //初始化套节字
int is = WSAStartup(verSion, &wsa); //初始化套节字
if (is != 0) {
std::cerr << "fail to init Winsock library" << std::endl;
WSACleanup(); // 关闭库
return 1;
}
// 2:校验版本
if (2 != HIBYTE(wsa.wVersion) || 2 != LOBYTE(wsa.wVersion)) {
// 版本不对
WSACleanup(); // 关闭库
return 0;
}
// 3:创建服务器端socket
// socket:将复杂的协议体系,执行流程封装成的一个接口,将复杂的协议与编程分开,直接操作socket,方便了网络程序开发;
// 其本质是一种数据类型,就是一个整数,表示着当前应用程序,协议等信息
// 参数一:地址类型:是IPv4地址:AF_INET, IPv6地址:AF_INET6(两种常用的)
// 参数二:套节字类型:SOCK_STREAM,一种支持数据报的套节字类型,可靠、双向、基于字节流,使用的协议为TCP协议
// 参数二:套节字类型:SOCK_DGRAM, 支持数据报的套节字类型,使用的协议为UDP协议
// 参数三:协议类型:IPPROTO_TCP:TCP协议;IPPROTO_UDP:UDP协议
// 返回值:函数原型 socket(int af,int type,int protocol); 成功返回可用的socket
SOCKET server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == INVALID_SOCKET) {
std::cerr << "fail to create server socket" << std::endl;
WSACleanup(); // 关闭库
return 1;
}
// socket不用时,一定要销毁:closesocket()
// 4:绑定地址与端口/绑定服务器端socket到IP地址和端口号
// bind 给创建的socket绑定地址与端口号
// 参数一:自己创建的socket
// 参数二:一个sockaddr结构体的指针,存储着地址类型、IP地址、端口号信息
// 参数二:使用方法:为了使用方便,使用sockaddr_in类型
// sockaddr_in sin = {};
// sin.sin_family = AF_INET; 地址类型
// sin.sin_port = htons(4567); 端口号
// sin.sin_addr.S_un.S_addr = inet_addr(“192.168.1.4”); IP地址
/*
使用的端口号必须是本机没有占用的端口号,如不确定,可使用如下方法查看:
打开cmd 输入 netstat - ano 查看被使用的所有端口
输入netstat - ano | findstr “12345” 查看是否被占用
*/
// 最后将sockaddr_in类型强转成sockaddr* 即(sockaddr*)&sin
// 参数三:参数二的类型大小,常用sizeof()
// 返回值: 函数原型 int bind(SOCKET s, const struct sockaddr *name, int namelen);
sockaddr_in server_addr;
server_addr.sin_family = AF_INET; // 地址类型
server_addr.sin_port = htons(12345); // 端口号为12345
server_addr.sin_addr.s_addr = INADDR_ANY; // 接受来自任何地址的连接
int is2 = bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr));
if (is2 == SOCKET_ERROR) {
std::cerr <<
"fail to cennect server socket to IP address port number"<<std::endl;
closesocket(server_socket);
WSACleanup(); // 关闭库
return 1;
}
// h : home
// n : network
// s : short
// l : long
// htons:意思就是本机字节序转到网络字节序,short类型的长度
// ntohs:意思就是网络字节序转到本机字节序,short类型的长度
// inet_addr:负责将我们平时看到的网络地址127.0.0.1等转化为网络字节序
// inet_ntoa:负责将网络字节序还原为我们平时看到的字符串127.0.0.1等
// 5:监听网络端口/开始监听连接请求
// listen 将套节字置入正在传入侦听连接的状态
// 参数一:服务器端socket
// 参数二:挂起连接队列的最大长度 比如有100个用户连接请求,系统一次只能处理20个,剩下的80个就会进入此队列,一般填写SOMAXCONN,让系统自动选择
// 返回值:函数原型 int listen(SOCKET s,int backlog); 成功返回0,失败返回SOCKET_ERROR
int is3 = listen(server_socket, SOMAXCONN);
if (is3 == SOCKET_ERROR) {
std::cerr << "fail to start Listening Connections" << std::endl;
closesocket(server_socket);
WSACleanup(); // 关闭库
return 1;
}
// 6:等待客户端连接(创建客户端socket)/接受客户端连接请求
// accept: listen监听客户端的连接请求,accept将客户端的信息绑定到一个socket上,然后通过返回值返回
// 参数一:自己创建的socket
// 参数二:客户端的地址端口信息结构体,和bind函数的第二个参数一样
// 参数三:参数二的大小
// PS:参数二,三都可以设置为NULL,不直接得到客户端的地址,端口等信息
// 返回值:函数原型 SOCKET accept(SOCKET s, struct sockaddr * addr, int * addrlen); 成功返回客户端socket,失败返回INVALID_SOCKET
std::cout << "accepting clint connect ..." << std::endl;
SOCKET client_socket = accept(server_socket, nullptr, nullptr);
if (client_socket == INVALID_SOCKET) {
std::cerr << "fail to accepting clint connect" << std::endl;
closesocket(server_socket);
WSACleanup(); // 关闭库
return 1;
}
std::cout << "Connected" << std::endl;
// 7:与客户端收发消息/接收和发送数据
// recv 接收消息:得到指定客户端(参数一)发来的消息,原理为调用recv,通过socket找到协议的缓冲区,并把数据拷贝进参数二(用户自己设置的缓冲区)
// 参数一:客户端的socket
// 参数二:接收消息的存储空间(协议规定网络最大传输单元为1500字节)
// 参数三:想要读取的字节数,一般为参数二字节数减一
// 参数四:一般为0:表示将协议缓冲区中的数据拷贝到我们的Buf中,然后协议的缓冲区就删除这一部分已拷贝的数据(读出来就删除)
// MSG_PEEK:数据被复制到Buf中,但不会从协议的缓冲区中删除
// MSG_OOB:传输一段数据,在外带一个额外的特殊数据
// MSG_WAITALL:知道协议的缓冲区中数据的字节数满足参数三请求的字节数,才开始读取
// 返回值:函数原型 int recv(SOCKET s,char* buf, int len, int flags); recv 接收消息过程是阻塞的读出来的字节数大小,若客户端下线,返回0,若执行失败,返回SOCKET_ERROR
/*
int recv_bytes = recv(client_socket, buffer, sizeof(buffer), 0); // 成功则返回接收消息过程是阻塞的读出来的字节数大小
if (recv_bytes == 0) // 若客户端下线,返回0
{
cout << "客户端退出..." << endl;
}
*/
// send 发送消息:向目标发送数据,send函数将我们的数据复制进系统的协议发送缓冲区中,然后系统发送出去,最大传输单元1500字节
// 参数一:客户端的socket
// 参数二:给对方发送的数据
// 参数三:字节个数
// 惨数四:一般为0,表示正常的发送
// MSG_OOB:意义同recv
// MSG_DONTROUTE:指定数据应不受路由限制
// 返回值:函数原型 int send(SOCKET s,const char* buf,int len,int flags); send 成功则返回写入的字节数,失败返回SOCKET_ERROR
/*
int send_bytes = send(client_socket, msg.c_str(), msg.length(), 0); // 成功则返回写入的字节数
*/
char buffer[1024]; // 接收消息的存储空间(协议规定网络最大传输单元为1500字节) / 给对方发送的数据
while (true) {
// 从客户端接收的字节大于0则一直进行
int recv_bytes = recv(client_socket, buffer, sizeof(buffer), 0);
// 成功
if (recv_bytes > 0) {
string recv_str(buffer,
recv_bytes); // string 构造方法/将string对象初始化为s指向的C字符串的前n个字符,即使超过了s的结尾
std::cout << "byte number " << recv_bytes << " string from clint " << recv_str
<< std::endl;
// 向客户端发送数据
int send_bytes = send(client_socket, recv_str.c_str(), recv_bytes,
0); // recv_str.c_str() 也可用 buffer代替
// 失败返回SOCKET_ERROR
if (send_bytes == SOCKET_ERROR) {
std::cerr << "fail to send data to clint" << std::endl;
closesocket(client_socket);
closesocket(server_socket);
WSACleanup(); // 关闭库
return 1;
}
std::cout << "byte number " << send_bytes << " string from server " << recv_str
<<
std::endl;
}
// 若客户端下线,返回0
else if (recv_bytes == 0) {
std::cout << "stop connect" << std::endl;
}
// 失败
else {
std::cerr << "fail to accept data from clint" << std::endl;
closesocket(client_socket);
closesocket(server_socket);
WSACleanup();
break;
return 1;
}
}
// 关闭socket
closesocket(client_socket);
closesocket(server_socket);
// 关闭Winsock库
WSACleanup();
return 0;
}
客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 这些函数都被微软定为不安全函数,想正常使用就必须在代码最前面定义宏
#include <iostream>
#include <string>
#include <string.h>
#include <winsock2.h> // 网络头文件,Windows socket第二版
#pragma comment(lib, "ws2_32.lib") // 加载网络库,Windows socket第二版,32位
// 客户端
using namespace std;
// 5:与服务器端收发消息
int sendMessages(SOCKET sock) {
// 向服务器端发送数据
cout << "input sending data" << endl;
string inputStr;
cin >> inputStr;
const char*sendData = inputStr.c_str();
int is = send(sock, sendData, strlen(sendData), 0);
if (is == SOCKET_ERROR) {
std::cerr << "fail to send data" << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
// 向服务器端接收数据
char recvData[1024];
int recvLen = recv(sock, recvData, sizeof(recvData), 0);
if (recvLen == SOCKET_ERROR) {
std::cerr << "fail to accept" << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
// 输出接收到的数据
string recv_str(recvData,
recvLen); // string 构造方法/将string对象初始化为s指向的C字符串的前n个字符,即使超过了s的结尾
std::cout << "data of server " << recv_str << std::endl;
}
// 主函数
int main() {
// 1:初始化Winsock库/打开网络库启动了这个库,库里的函数才可以使用
WORD verSion = MAKEWORD(2, 2); //初始化套节字
WSADATA wsa; //初始化套节字
int is = WSAStartup(verSion, &wsa); //初始化套节字
if (is != 0) {
std::cerr << "fail to init Winsock library" << std::endl;
WSACleanup(); // 关闭库
return 1;
}
// 2:校验版本
if (2 != HIBYTE(wsa.wVersion) || 2 != LOBYTE(wsa.wVersion)) {
// 版本不对
WSACleanup(); // 关闭库
return 0;
}
// 3:创建客户端socket/创建 TCP socket
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client_socket == INVALID_SOCKET) {
std::cerr << "fail to create TCP socket" << std::endl;
WSACleanup(); // 关闭库
return 1;
}
// 4:设置服务器地址和端口并连接服务器
// connect 作用:连接服务器并将服务器信息与服务器socket绑定到一起
// 参数一:服务器socket
// 参数二:服务器IP地址和端口号的结构体
// 参数三:参数二结构体大小
// 返回值:函数原型 int connect(SOCKET s,const struct sockaddr * name,int namelen);
// 成功返回0,失败返回SOCKET_ERROR
string IP;
cout << "input Ip address" << endl;
cin >> IP;
int Number;
cout << "input Port number" << endl;
cin >> Number;
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(Number);
serverAddr.sin_addr.s_addr = inet_addr(
IP.c_str()); // 将 string 类型的IP地址转换为字符数组传到inet_addr()函数中
int is2 = connect(client_socket, (sockaddr*)&serverAddr, sizeof(serverAddr));
if (is == SOCKET_ERROR) {
std::cerr << "fail to connect server" << std::endl;
closesocket(client_socket);
WSACleanup(); // 关闭库
return 1;
}
// h : home
// n : network
// s : short
// l : long
// htons:意思就是本机字节序转到网络字节序,short类型的长度
// ntohs:意思就是网络字节序转到本机字节序,short类型的长度
// inet_addr:负责将我们平时看到的网络地址127.0.0.1等转化为网络字节序
// inet_ntoa:负责将网络字节序还原为我们平时看到的字符串127.0.0.1等
// 5:与服务器端收发消息
for (int i = 0; i < 10; i++) {
int the = sendMessages(client_socket);
}
// 关闭socket
closesocket(client_socket);
// 关闭Winsock库
WSACleanup();
return 0;
}
测试