windows网络编程C++

该代码示例展示了如何在Windows环境下使用Winsock库建立服务器端和客户端的TCP连接。首先,通过WSAStartup初始化Winsock,然后创建套接字,绑定IP地址和端口,监听连接请求。当有客户端连接时,服务器接收并发送数据。客户端则通过输入IP地址和端口号连接服务器,发送和接收数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器端

#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;
}

测试
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值