1. Windows网络编程(C++ Socket编程)

文章目录

        • 基于TCP/IP协议的C/S模型
        • 服务端
          • 1. 打开网络库
          • 2. 校验版本
          • 3. 创建一个socket
          • 4. 绑定地址与端口
          • 5. listen监听网络端口
          • 6. accept等待客户端连接(创建客户端socket)
          • 7. 与客户端收发消息
        • 客户端

基于TCP/IP协议的C/S模型

特点:面向连接的、可靠的、基于字节流的传输层协议。

C/S 即 客户端/服务器 模型。

socket:套节字

服务端

网络头文件,网络库:

#include               // 网络头文件,Windows socket第二版
#pragma comment(lib, "ws2_32.lib") // 加载网络库,Windows socket第二版,32位;								                                        // 不管是32位还是64位环境,是第一版还是第二版,都用ws2_32.lib
1. 打开网络库
//函数原型
WSAStartup(
    WORD wVersionRequested,
    LPWSADATA lpWSAData
    );

功能:打开/启动网络库启动了这个库,库里的函数才可以使用。

参数一:

使用的库的版本,类型是WORD(由 unsigned short 转定义而来)

定义一个WORD: WORD verSion = MAKEWORD(2, 2); 其中MAKEWORD(主版本,副版本)

参数二:

类型为LPWSADATA lpWSAData,系统获取网络配置信息,然后返回给此参数;当参数前有LP前缀时,应传对应类型变量地址

定义一个WSADATA结构体,然后传入它的地址 WSADATA dat;

返回值:

返回值为int类型,如果打开网络库成功,返回0,失败返回各种错误码

WORD verSion = MAKEWORD(2, 2);
WSADATA dat;
int nRes = WSAStartup(verSion, &dat);
//最后程序结束前一定要关闭网络库:WSACleanup();
2. 校验版本
if (2 != HIBYTE(dat.wVersion) || 2 != LOBYTE(dat.wVersion))
	{
		// 版本不对
		WSACleanup(); //关闭库
		return 0;
	}
3. 创建一个socket

socket:将复杂的协议体系,执行流程封装成的一个接口,将复杂的协议与编程分开,直接操作socket,方便了网络程序开发;

​ 其本质是一种数据类型,就是一个整数,表示着当前应用程序,协议等信息

//函数原型
socket(
    int af,
    int type,
    int protocol
    );

参数一:地址类型

是IPv4地址:AF_INET, IPv6地址:AF_INET6(两种常用的)

参数二:套节字类型

常用的有:

SOCK_STREAM,一种支持数据报的套节字类型,可靠、双向、基于字节流,使用的协议为TCP协议

SOCK_DGRAM, 支持数据报的套节字类型,使用的协议为UDP协议

参数三:协议类型

常用的有:

IPPROTO_TCP:TCP协议;IPPROTO_UDP:UDP协议

返回值:

成功返回可用的socket,socket不用时,一定要销毁:closesocket()

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
4. 绑定地址与端口
//函数原型
int
bind(
    SOCKET s,
    const struct sockaddr *name,
    int namelen
    );

作用:

​ 给创建的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()

返回值:成功返回0,失败返回SOCKET_ERROR,具体错误码通过 int WSAGetLastError(void) 获得

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");
	
	//2 bind 绑定用于接受客户端链接的网络端口
	if (SOCKET_ERROR == bind(sock, (sockaddr*)&sin, sizeof(sin)))
	{
		cout << "绑定网络端口失败" << endl;
	}
	else {
		cout << "绑定网络端口成功" << endl;
	}
5. listen监听网络端口
int
listen(
    SOCKET s,
    int backlog
    );

作用:

将套节字置入正在传入侦听连接的状态

参数一:服务器端socket

参数二:挂起连接队列的最大长度

比如有100个用户连接请求,系统一次只能处理20个,剩下的80个就会进入此队列,一般填写SOMAXCONN,让系统自动选择

返回值:成功返回0,失败返回SOCKET_ERROR

if (SOCKET_ERROR == listen(sock, 5))
	{
		cout << "监听网络端口失败" << endl;
	}
	else {
		cout << "监听网络端口成功" << endl;
	}
6. accept等待客户端连接(创建客户端socket)
SOCKET
accept(
    SOCKET s,
    struct sockaddr * addr,
    int * addrlen
    );

作用:

listen监听客户端的连接请求,accept将客户端的信息绑定到一个socket上,然后通过返回值返回

参数一:自己创建的socket

参数二:客户端的地址端口信息结构体,和bind函数的第二个参数一样

参数三:参数二的大小

PS:参数二,三都可以设置为NULL,不直接得到客户端的地址,端口等信息

返回值:成功返回客户端socket,失败返回INVALID_SOCKET

	sockaddr_in clientAddr = {};
	int nAddrLen = sizeof(sockaddr_in);
	SOCKET cSock = INVALID_SOCKET;
	cSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
	if (INVALID_SOCKET == cSock)
	{
		cout << "错误,接受到无效客户端SOCKET..." << endl;
	}
7. 与客户端收发消息

接收消息:得到指定客户端(参数一)发来的消息,原理为调用recv,通过socket找到协议的缓冲区,并把数据拷贝进参数二(用户自己设置的缓冲区)

int
recv(
    SOCKET s,
    char * buf,
    int len,
    int flags
    ); // recv接收消息过程是阻塞的

参数一:客户端的socket

参数二:接收消息的存储空间(协议规定网络最大传输单元为1500字节)

参数三:想要读取的字节数,一般为参数二字节数减一

参数四:一般为0:表示将协议缓冲区中的数据拷贝到我们的Buf中,然后协议的缓冲区就删除这一部分已拷贝的数据(读出来就删除)

​ MSG_PEEK:数据被复制到Buf中,但不会从协议的缓冲区中删除

​ MSG_OOB:传输一段数据,在外带一个额外的特殊数据

​ MSG_WAITALL:知道协议的缓冲区中数据的字节数满足参数三请求的字节数,才开始读取

返回值:读出来的字节数大小,若客户端下线,返回0,若执行失败,返回SOCKET_ERROR

int nlen = recv(cSock, recvBuf, 128, 0);
if (nlen == 0)
{
	cout << "客户端退出..." << endl;
}

发送消息:向目标发送数据,send函数将我们的数据复制进系统的协议发送缓冲区中,然后系统发送出去,最大传输单元1500字节

int
send(
    SOCKET s,
    const char * buf,
    int len,
    int flags
    );  

参数一:目标的socket

参数二:给对方发送的数据

参数三:字节个数

惨数四:一般为0,表示正常的发送

​ MSG_OOB:意义同recv

​ MSG_DONTROUTE:指定数据应不受路由限制

返回值:成功则返回写入的字节数,失败返回SOCKET_ERROR

send(cSock, msg.c_str(), msg.length(), 0);

服务端代码:

#define WIN32_LEAN_AND_MEAN
#include 
#include 
#include 
#include 
#pragma comment(lib, "ws2_32.lib")
using namespace std;

int main()
{
	// 启动Windows socket2 环境
	//初始化套节字
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);

	// 校验版本
	if (2 != HIBYTE(dat.wVersion) || 2 != LOBYTE(dat.wVersion))
	{
		// 版本不对
		WSACleanup();
		return 0;
	}

	//1 建立一个socket
	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	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");
	
	//2 bind 绑定用于接受客户端链接的网络端口
	if (SOCKET_ERROR == bind(sock, (sockaddr*)&sin, sizeof(sin)))
	{
		cout << "绑定网络端口失败" << endl;
	}
	else {
		cout << "绑定网络端口成功" << endl;
	}
	//3. listen监听网络端口
	if (SOCKET_ERROR == listen(sock, 5))
	{
		cout << "监听网络端口失败" << endl;
	}
	else {
		cout << "监听网络端口成功" << endl;
	}

	//4 accept 等待客户端链接
	sockaddr_in clientAddr = {};
	int nAddrLen = sizeof(sockaddr_in);
	SOCKET cSock = INVALID_SOCKET;
	string msg = "Hello, I'm Server";
	cSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
	if (INVALID_SOCKET == cSock)
	{
		cout << "错误,接受到无效客户端SOCKET..." << endl;
	}
	cout << "新客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
	//5 send 向客户端发送数据
	char recvBuf[128] = {};
	while (true)
	{
		int nlen = recv(cSock, recvBuf, 128, 0);
		if (nlen <= 0)
		{
			cout << "客户端退出..." << endl;
			break;
		}
		send(cSock, msg.c_str(), msg.length(), 0);
	}
	

	//6 关闭套接字closesocket
	closesocket(sock);
	//清楚Windows socket环境
	WSACleanup();
}

客户端

  1. 打开网络库

  2. 校验版本

  3. 创建socket

  4. 连接到服务器

    int
    connect(
     SOCKET s,
     const struct sockaddr * name,
     int namelen
     );
    

    作用:连接服务器并将服务器信息与服务器socket绑定到一起

    参数一:服务器socket

    参数二:服务器IP地址和端口号的结构体

    参数三:参数二结构体大小

    返回值:成功返回0,失败返回SOCKET_ERROR

  5. 与服务器收发消息

客户端代码:

#define WIN32_LEAN_AND_MEAN
#include 
#include 
#include 
#include 
#pragma comment(lib, "ws2_32.lib")
using namespace std;

int main()
{
	// 启动Windows socket2 环境
	//初始化套节字
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);

	//1 创建一个socket
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sock)
	{
		cout << "错误,建立socket失败" << endl;
	}
	else
	{
		cout << "建立socket成功.." << endl;
	}
	//2 连接服务器
	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");
	if (SOCKET_ERROR == connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)))
	{
		cout << "建立连接失败..." << endl;
	}
	else {
		cout << "建立连接成功..." << endl;
	}
	//3 接收服务器信息
	char recvBuf[256] = {};
	int nlen = recv(sock, recvBuf, 256, 0);
	if (nlen > 0)
	{
		cout << "接受到数据:" << recvBuf << endl;
	}
	//4 关闭套节字closesocket
	closesocket(sock);
	getchar();
	//清楚Windows socket环境
	WSACleanup();
}

你可能感兴趣的