Table of Contents

  • 准备工作
    • socket
    • C/S模式
  • 源代码
    • 服务端
    • 客户端
    • 源码分析
    • 数据传输
    • 关闭连接
  • 符号解释
    • WSAStartup
    • sin_family
    • sin_port
    • inet_addr
    • htonl / htons
    • af
    • type
  • C/S 通信

准备工作

Windows网络编程一般是指 Windows Socket 编程(winsocket),它从UNIX Socket 发展而来。进行Windows网络编程,首先需要添加依赖库WS2_32.libWSOCK_32.lib,加载动态库ws2_32.dll,放入C:/Windows/System32。然后使用时在源文件中包含头文件:

#include <WinSock2.h>
// #include <MSWSOCK.h>
// #include <winsock.h>

说明:
有些接口已经弃用,采用新的接口,具体是哪些,后面会慢慢指出。

  • 如何引入依赖库?
#pragma comment(lib, "ws2_32.lib");    // 源文件中添加

也可以在配置文件中添加:属性----链接器----输入----ws2_32.lib.

socket

socket 套接字是应用层到传输层的接口,表示一个连接的两端,每个端由IP地址和端口port组成,即socket是由两端点的ip和端口port组成的

  • 套接字类型 SOCKET 定义

    typedef unsigned int SOCKET;   // 句柄
    
  • 端口

    端口是传输层的概念,每个端口对应一个 process 进程,因此一条连接表示一个进程与另一个进程建立联系。

  • 套接字类型

    一般使用两种套接字:TCP 流套接字,UDP 数据报套接字。前者提供可靠的、无重复的、有序的数据流服务,后者提供不可靠传输。

C/S模式

winsocket 一般采用C/S模式

  • Server 端流程

1、初始化winsocket
2、建立socket
3、绑定服务端地址(bind)
4、开始监听(listen)
5、然后与客户端建立连接(accept)
6、然后与客户端进行通信(send, recv)
7、当通信完成以后,关闭连接
8、释放winsocket的有关资源

  • Client 端流程

1、初始化winsocket
2、建立socket
3、与服务器进行连接(connect)
4、与服务器进行通信(send, recv)
5、当通信完成以后,关闭连接
6、释放winsocket占用的资源

话不多说,先上一段代码,再小段分析

源代码

源码亲测可以运行

服务端

// win_server.cpp
// compiler with: VS2017
#include "pch.h"
#include <stdio.h>
#include <winsock2.h>  // 必须包含windwos.h之前
#include <Windows.h>
// #include <process.h>  /* _beginthreadex */// 指定依赖库目录
#pragma comment(lib,"ws2_32.lib")
// 设置端口号
constexpr auto PORT = 6000;// C/S 端连接情况分析
// Server 端收发数据情况
DWORD WINAPI clientProc(LPARAM lparam)
{SOCKET sockClient = (SOCKET)lparam;char buf[1024];while (TRUE){memset(buf, 0, sizeof(buf));// 接收客户端的一条数据 int ret_recv = recv(sockClient, buf, sizeof(buf), 0);//检查是否接收失败if (SOCKET_ERROR == ret_recv){printf("socket recv failed\n");closesocket(sockClient);return -1;}// 0 代表客户端主动断开连接if (ret_recv == 0){printf("client close connection\n");closesocket(sockClient);return -1;}// 发送数据int ret_send = send(sockClient, buf, strlen(buf), 0);//检查是否发送失败if (SOCKET_ERROR == ret_send){printf("socket send failed\n");closesocket(sockClient);return -1;}}closesocket(sockClient);return 0;
}// 网络环境初始化
// 加载 dll,初始化socket
bool InitNetEnv()
{WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){printf("WSAStartup failed\n");return false;}return true;
}int main(int argc, char * argv[])
{if (!InitNetEnv()){return -1;}// 初始化完成,创建一个TCP的socketSOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//检查是否创建失败if (sServer == INVALID_SOCKET){printf("socket failed\n");return -1;}printf("Create socket OK\n");//进行绑定操作SOCKADDR_IN addrServ;addrServ.sin_family = AF_INET; // 协议簇为IPV4的// 端口  因为本机是小端模式,网络是大端模式,调用htons把本机字节序转为网络字节序addrServ.sin_port = htons(PORT); // ip地址,INADDR_ANY表示绑定电脑上所有网卡IPaddrServ.sin_addr.S_un.S_addr = INADDR_ANY; //完成绑定操作int ret = bind(sServer, (sockaddr *)&addrServ, sizeof(sockaddr));//检查绑定是否成功if (SOCKET_ERROR == ret){printf("socket bind failed\n");WSACleanup(); // 释放网络环境closesocket(sServer); // 关闭网络连接return -1;}printf("socket bind OK\n");// 绑定成功,进行监听ret = listen(sServer, 10);//检查是否监听成功if (SOCKET_ERROR == ret){printf("socket listen failed\n");WSACleanup();closesocket(sServer);return -1;}printf("socket listen OK\n");// 监听成功sockaddr_in addrClient; // 用于保存客户端的网络节点的信息int addrClientLen = sizeof(sockaddr_in);while (TRUE){//新建一个socket,用于客户端SOCKET *sClient = new SOCKET;//等待客户端的连接*sClient = accept(sServer, (sockaddr*)&addrClient, &addrClientLen);if (INVALID_SOCKET == *sClient){printf("socket accept failed\n");WSACleanup();closesocket(sServer);delete sClient;return -1;}//创建线程为客户端做数据收发//_beginthreadex(NULL, 0, &clientProc, NULL, CREATE_SUSPENDED, (LPVOID)*sClient);CreateThread(0, 0, (LPTHREAD_START_ROUTINE)clientProc, (LPVOID)*sClient, 0, 0);}closesocket(sServer);WSACleanup();return 0;
}

客户端

// win_client.cpp
// compile with: VS2017
#include "pch.h"
#include <stdio.h>
#include <winsock2.h>
#include <Windows.h>#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")
constexpr auto PORT = 6000;int main(int argc, char * argv[])
{//初始化网络环境WSADATA wsa;if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0){printf("WSAStartup failed\n");return -1;}// 初始化完成,创建一个TCP的socketSOCKET sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (sServer == INVALID_SOCKET){printf("socket failed\n");return -1;}//指定连接的服务端信息SOCKADDR_IN addrServ;addrServ.sin_family = AF_INET;addrServ.sin_port = htons(PORT);//客户端只需要连接指定的服务器地址,127.0.0.1是本机的回环地址addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");// 服务器 Bind 客户端是进行连接int ret = connect(sServer, (SOCKADDR*)&addrServ, sizeof(SOCKADDR));//开始连接if (SOCKET_ERROR == ret){printf("socket connect failed\n");WSACleanup();closesocket(sServer);return -1;}//连接成功后,就可以进行通信了char szBuf[1024];memset(szBuf, 0, sizeof(szBuf));sprintf_s(szBuf, sizeof(szBuf), "Hello server");//当服务端是recv的时候,客户端就需要send,若两端同时进行收发则会卡在这里,因为recv和send是阻塞的ret = send(sServer, szBuf, strlen(szBuf), 0);if (SOCKET_ERROR == ret){printf("socket send failed\n");closesocket(sServer);return -1;}ret = recv(sServer, szBuf, sizeof(szBuf), 0);if (SOCKET_ERROR == ret){printf("socket recv failed\n");closesocket(sServer);return -1;}printf("%s\n", szBuf);closesocket(sServer);WSACleanup();return 0;
}

源码分析

  • WSAData 结构体
typedef struct WSAData {WORD                    wVersion;       // 版本号WORD                    wHighVersion;
#ifdef _WIN64unsigned short          iMaxSockets;unsigned short          iMaxUdpDg;char FAR *              lpVendorInfo;char                    szDescription[WSADESCRIPTION_LEN+1];char                    szSystemStatus[WSASYS_STATUS_LEN+1];
#endif
} WSADATA, FAR * LPWSADATA;
  • SOCKADDR_IN 结构体
typedef struct sockaddr_in {USHORT sin_port;IN_ADDR sin_addr;CHAR sin_zero[8];
} SOCKADDR_IN;
  • 服务端需要 bind 的原因

无连接(connect)的服务端、客户端和面向连接的服务端通过 bind 来配置本地信息;而有连接的客户端通过调用 connect 函数在socket 数据结构中保存本地和远端信息,不需要调用 bind()。

  • 需要初始化 WASStartup()的原因

之所以需要初始化winsocket,是因为Winsock的服务是以动态连接库Winsock DLL形式实现的,所以必须先调用初始化函数(WSAStartup)对Winsock DLL进行初始化,协商Winsock的版本支持,并分配必要的资源; // 在Linux环境中不需要该初始化步骤。

数据传输

在建立起连接的基础上,发送数据可以用接口 send / WSASend,接收数据可以用 recv / WSARecv

  • 对于 send 而言,发送数据的长度一般有限制,因为缓冲区或者 TCP/IP 的窗口大小有所限制,所以需要根据窗口大小来设定发送数据的长度。
  • 对于 recv 而言,流套接字是一个不间断的数据流,在读取它时,应用程序通常不会关心应该读取多少数据,如果所有消息长度都一样,这应该简单处理,如读取 1024 字节。
char recvBuff[2048];
int ret;    // 读取的数据长度
int nLeft;  // 剩余空间
int idx;    // 缓冲区数组下标
nLeft = 1024;
idx = 0;
while (nLeft > 0)
{ret = recv(socket1, &recvBuff[idx], nLfet, 0);if (ret == SOCKET_ERROR){// error 读取失败std::cout << "Error when receive message.";}idx += ret;nLeft -= ret;
}
  • 如果接收的消息长度不同,则按照发送端的协议来通知接收端,告知接收端即将到来的消息长度多少;比如,在消息的前几个字节设定标记,表示数据长度。

关闭连接

数据传输完成,关闭套接字,释放资源。

shutdown();    // 中断连接
closeSocket(socket_name);
WASCleanup();  // 释放 dll

符号解释

WSAStartup

初始化 DLL,加载 socket,在Windows中,socket 以 dll 形式实现,dll 内部有一个计数器,第一次调用是真正加载 dll,后面再次调用 WSAStartup 是计数器加 1 ;与 WSAStartup 绑定使用的是 WSACleanup(),相反的,该函数只有最后一次调用才是真正卸载 dll,释放资源,前面的每次调用都是计数器减 1。

WSAStartup() 定义如下

int WSAAPI WSAStartup(              // WSAStartup 结构体_In_ WORD wVersionRequested,    // 高字节:指出副版本号,低字节:主版本号_Out_ LPWSADATA lpWSAData       // 指向win socket 实现的细节);

sin_family

表示地址家族。使用 TCP/IP 协议的应用程序必须设置 AF_INET,来告诉系统使用 IP 地址家族 。

sin_port

指定服务的端口号。1024–49151范围内的数据被作为服务端口号,可以由用户自定义。 sin_zero字段作为填充字段。以便使得该结构与SOCKADDR结构长度相同。

inet_addr

把本机IP的主机字节序转化为网络字节序。

该接口已经弃用,采用 inet_pton 或其他代替。

htonl / htons

host to net long/short htonl 和 htons 函数实现主机字节顺序和网络字节序的转换功能。H代表host,主机。N代表net,L代表long,S代表short。不能使用htonl转换short。

同理,网络字节序—> 主机字节序 :ntohl 、ntohs

af

表示协议使用的地址家族,创建TCP或UDP的套接字时使用AF_INET 地址家族。

type

socket套接字类型,有三种套接字类型:SOCK_STREAM / SOCKET_DGRAM / SOCK_ARM,分别表示数据流,数据包,原始套接字。

C/S 通信

ClientServer 要进行通信,首先需要建立连接;而客户端要连上服务端,首先需要开启服务端 Serverbind好服务端的IP地址和端口port,并设置监听listen,这时才能运行客户端程序,连接 上服务端,进行数据传输。

在Windows下进行的C/S通信,需要开启两个编译器分别编译运行 server.cpp 和 client.cpp,一般都有用 vs 吧,那就同时开启两个 vs ,不要将客户端和服务端程序写在同一个项目中。

以上仅为个人所学所得,仅供参考,欢迎不吝指正。

Windows网络编程(一)基础相关推荐

  1. windows网络编程——telnet协议

     1.windows网络编程--telnet协议 Telnet是最老的Internet应用,起源于1969年的ARPANET,名字是"电信网络协议(TelecommuicationNetwo ...

  2. 高等学校计算机科学与技术教材:tcp/ip网络编程技术基础,TCP/IP网络编程技术基础...

    TCP/IP网络编程技术基础 语音 编辑 锁定 讨论 上传视频 <TCP/IP网络编程技术基础>是2012年北京交通大学出版社出版的图书,作者是王雷. 书    名 TCP/IP网络编程技 ...

  3. python六十七课——网络编程(基础知识了解)

    网络编程: 什么是网络编程? 网络:它是一种隐形的媒介:可以将多台计算机使用(将它们连接到一起) 网络编程:将多台计算机之间可以相互通信了(做数据交互) 一旦涉及到网络编程,划分为两个方向存在,一方我 ...

  4. 十年一遇的奇葩故障--Windows网络编程接口故障:telnet显示无法加载或初始化请求的服务提供程序...

    现象:某同事的笔记本win7x64系统,当初故障是无法使用小乌龟连接到svn服务器,但又可以正常上网,并且svn服务器端是正常的. 后来我进一步测试,发觉该电脑也不能连接到远程windows. net ...

  5. python网络编程知识_python六十七课——网络编程(基础知识了解)

    网络编程: 什么是网络编程? 网络:它是一种隐形的媒介:可以将多台计算机使用(将它们连接到一起) 网络编程:将多台计算机之间可以相互通信了(做数据交互) 一旦涉及到网络编程,划分为两个方向存在,一方我 ...

  6. Windows网络编程案例教程-董相志 学习记录 第一个网络程序hostent

    <Windows网络编程案例教程>-董相志 学习记录 第一个网络程序hostent 第一章 网络编程概述 1.3.5第一个网络程序--hostent 对主机的名称和地址解析 WinSock ...

  7. 网络游戏《丛林战争》开发与学习之(一):网络编程的基础知识

    <丛林战争>是一款完整的网络游戏案例,运用U3D开发客户端,Socket开发服务端,其中涉及到了网络编程.数据库和Unity的功能实现,之前通过U3D开发了一个单机游戏<黑暗之光&g ...

  8. Windows 网络编程

    欢迎访问我的博客首页. Windows 网络编程 1. 交替收发 1.1 客户端 1.2 服务器 2. 粘包问题 2.1 客户端 2.2 服务器 3. 双工通信 4. 附录 4.1 Java 中 in ...

  9. c++ windows网络编程--udpSocket之recfrom()函数阻塞问题

    在windows网络编程中,创建udp socket,然后调用recfrom()函数进行数据接收,发现当没有数据发送来时,会一直阻塞在recfrom()里面.为了避免因阻塞而占用CPU资源,所以不能一 ...

最新文章

  1. SAP 修改物料价格那些事
  2. mybatis应用(三)优化
  3. 内存映射获取行数_使用内存映射文件获取巨大的矩阵
  4. Skywalking-13:Skywalking模块加载机制
  5. 【编撰】linux IPC 002 - 匿名管道PIPE和有名管道FIFO的概念和实例,以及应用比较
  6. vim打开出现的文档^M什么
  7. python xpath爬取电影top100_进击的爬虫-002-xpath实现猫眼电影前100爬取
  8. 【Python:统一时间格式YYYY-MM-DD】时间数据、格式处理、并根据时间合并dataframe
  9. 南阳oj-----Binary String Matching(string)
  10. python控制步进电机驱动器_怎样用树莓派和L298N电机驱动器模块控制步进电机
  11. 运算放大器(运放)选型、参数分析以及应用OPA2350
  12. WTL for MFC Programmers, Part V - Advanced Dialog UI Classes
  13. java高级(java高级工程师证书)
  14. celeste第二章_蔚蓝全剧情全流程攻略 全关卡详解图文攻略
  15. Stratified Transformer复现和调试记录,ubuntu20复现S3DIS数据集(点云语义分割)
  16. Kubernetes1.3:QoS服务质量管理
  17. win10企业版无法访问共享文件夹
  18. 阈值、阙值 有没有阀值?
  19. 【万字拆解】ChatGPT各项能力的起源
  20. 液晶屏接口协议 MIPI LVDS 概览

热门文章

  1. mongodb数据库定时任务自动备份
  2. 对双绞线8芯线的学习
  3. 关于数据库冗余字段设计的利与弊
  4. 2002年图灵奖--利维斯、沙米尔和阿德勒曼简介
  5. 无线路由器组网设服务器,闲来无事开一贴说说无线路由和家庭组网
  6. P2P技术详解:NAT详解、P2P简介
  7. 手写实现timeline时间线组件
  8. 探究性学习任务1:关于“视觉暂留”现象的探究性学习
  9. CC2530学习(三)定时器定时(轮询)
  10. 集合 —— 集合与集合运算