Windows网络编程基础(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Davincdada/article/details/86644668

准备工作

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占用的资源

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

源代码

源码亲测可以运行

服务端 Server.cpp

#include "pch.h"
#include <iostream>
#include <WinSock2.h>
//#include <MSWSock.h>
using namespace std;

// 指定依赖库路径
#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA wsaData;           // 声明一个结构体
	SOCKET  listeningSocket;   // 声明一个监听句柄
	SOCKET  newConnection;     // 声明一个连接句柄
	SOCKADDR_IN serverAddr;    // 端点结构体
	SOCKADDR_IN clientAddr;
	int port = 5150;           // 端口号

	// 初始化socket
	WSAStartup(MAKEWORD(2,2), &wsaData);
	// 创建监听socket相应客户端请求
	listeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 填写服务器地址信息
	// port=5150
	// ip 地址为INNADDR_ANY,注意使用 hton 将 IP地址转换为网络格式
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(port);
	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	// 绑定监听端口
	bind(listeningSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
	// 开始监听,指定最大同时连接数
	listen(listeningSocket, 5);
	// 接受新连接
	int clientAddrLen = sizeof(clientAddr);
	newConnection = accept(listeningSocket, (SOCKADDR *)&clientAddr, &clientAddrLen);

	// 新连接建立后,就可以开始通信了
	cout << "========= new client has been connected ========" << endl;

	// 
	// 这里放通信代码
	// 

	// 通讯结束后,关闭连接
	// 并关闭监听socket,然后退出程序
	closesocket(newConnection);
	closesocket(listeningSocket);

	// 释放window socket dll 资源
	WSACleanup();

	return 0;                   
}

客户端 Client.cpp

#include "pch.h"
#include <WinSock2.h>
#include <iostream>
#include <WS2tcpip.h>
using namespace std;

int main()
{
	WSADATA wsaData;
	SOCKET clientSocket;
	SOCKADDR_IN serverAddr;
	int port = 5150;

	// 加载 dll,初始化socket 2.2
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	// 创建一个新连接
	clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_port = htons(port);
	serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");      // 与本机通信

	// 向服务器发送连接请求
	int result = connect(clientSocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr));

	if (result == 0)
	{
		cout << "client has connect to server..." << endl;
	}

	// 关闭套接字,释放资源
	closesocket(clientSocket);
	WSACleanup();
	return 0;
}

源码分析

  • WSAData 结构体
typedef struct WSAData {
        WORD                    wVersion;       // 版本号
        WORD                    wHighVersion;
#ifdef _WIN64
        unsigned 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 

猜你喜欢

转载自blog.csdn.net/Davincdada/article/details/86644668