UDP/IP 基本C/S模型

UDP/IP 与 TCP/IP的区别:

TCP/IP协议的特点:

        面向连接的,可靠的,基于字节流的传输层协议。

UDP/IP协议的特点:

        面向非连接的,不可靠的,基于数据报的传输层协议。

基本C/S模型

        TCP/IP 服务端与客户端是 1对1关系;UDP/IP 服务端与客户端 可以是1对1,1对多 关系。

基本C/S模型中服务端代码:

        UDP/IP:

                是面向非连接的,所以不进行listen、accept;与客户端进行收发消息调用的函数是:recvfrom、sendto。

        TCP/IP:

                是面向连接的,要进行listen、accept;与客户端进行收发消息调用的函数是:recv、send。

实现步骤:

服务端

1.打开网络库

int WSAStartup(
  WORD      wVersionRequired,
  LPWSADATA lpWSAData
);

2.检验版本

if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))

3.创建SOCKET

SOCKET  socket(
  int af,
  int type,        // 填 SOCK_DGRAM
  int protocol     // 填 IPPROTO_UDP
);

4.绑定地址与端口号

int bind(
  SOCKET              s,
  const sockaddr *addr,
  int                 namelen
);

5.与客户端进行收消息:

int recvfrom(
  SOCKET   s,
  char     *buf,
  int      len,
  int      flags,
  sockaddr *from,
  int      *fromlen
);


recvfrom函数:
    作用:得到当前服务端接收到的消息
    本质:复制    将协议缓存区的数据复制黏贴进自定义的buf
    与recv函数一样是进行接收消息,不同的是recv是傻等,recvfrom是死等
参数1:服务端的socket
    特点:
        TCP是获取指定的,recv函数与客户端是1对1的关系 -- 傻等
        UDP是无差别获取,recvfrom函数与客户端是1对多的关系 -- 死等
参数2:客户端消息的存储空间,也就是一个字符数组
    广域网:各级路由器上的MTU最小值是576字节
        tcp = 576 - 20(TCP包头) - 20(IP包头) = 536;
        udp = 576 - 8(UDP包头) - 20(IP包头) = 548;
参数3:想要读取的字节个数, 参数2的字节数
参数4:数据的读取方式
        0            从协议缓存区取一个数据报,然后就删除掉
        MSG_PEEK    取出数据报,但是协议缓存内不删除,一直残留,导致影响后面的数据读取
        MSG_OOB        带外数据
参数5:对方的IP地址端口号
        此处结构体为相应的IP地址和端口号 struct sockaddr* sockClient; 填 &sockClient;
参数6:参数5结构体的大小 int len = sizeof(&sockClient);    填 &len;

6.与客户端进行发消息

int sendto(
  SOCKET         s,
  const char     *buf,
  int            len,
  int            flags,
  const sockaddr *to,
  int            tolen
);

sendto函数:
    作用:向目标发送数据报
    本质:send函数将我们的数据复制黏贴进行系统的协议发送缓冲区,计算机伺机发出去
参数1:当前服务端的socket(自己的socket)
参数2:给接收方发送的字节串  UDP统一字节数 548
参数3:参数2的大小(字节个数 548)
参数4:填0
参数5:对方IP地址与端口号结构体
参数6:参数5的大小

客户端

与服务端代码基本一致,客户端不进行调用bind地址与端口号,因为服务端IP地址与端口号是固定的,所以只需创建服务端的地址与端口号结构体,在sendto 函数中参数5传递就可以了

struct sockaddr_in sockServer;

服务端代码:

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

#pragma comment(lib, "ws2_32.lib")

/*
	UDP/IP (User Datagram Protocol/Internet Protocol) 用户数据报协议
	TCP/IP 协议的特点:
		面向连接的,可靠的,基于字节流的传输层协议。
	UDP/IP 协议的特点:
		面向非连接的,不可靠的,基于数据报的传输层协议。
	基本C/S模型中服务端代码:
		UDP/IP 与 TCP/IP 的区别:
			UDP/IP: 
				是面向非连接的,不用进行listen(进行监听)、不进行accept(接收连接)
				与客户端进行收发消息调用的是 recvfrom、sendto函数	
			TCP/IP:
				是面向连接的,进行listen(进行监听)、进行accept(接收连接 本质创建客户端socket)
				与客户端进行收发消息调用的是 recv、send函数

*/

// 1.打开网络库 2.校验版本号
void OpenAndCheckWSAStartup();

// 3.创建服务端socket
SOCKET Sock_Server();

// 全局声明要创建 创建服务端socket
SOCKET socketServer;

// 4.绑定地址与端口号
void Bind(SOCKET sock);

// 5.与客户端进行发收数据
void RecvFromAndSendTo(SOCKET sock);

// 6.监视关闭窗口按钮函数所需的参数1 回调函数
BOOL WINAPI fun(DWORD CtrlType)
{
	switch (CtrlType)
	{
	case CTRL_CLOSE_EVENT:
		// 关闭socket
		closesocket(socketServer);
		// 关闭网络库
		WSACleanup();
		break;
	}
	return TRUE;
}

int main()
{
	// 1.打开网络库 2.校验版本号
	OpenAndCheckWSAStartup();
	
	// 3.创建服务端socket
	socketServer = Sock_Server();
	
	// 4.绑定地址与端口号
	Bind(socketServer);

	// 5.与客户端进行发收数据
	RecvFromAndSendTo(socketServer);

	// 6.监视关闭窗口
	SetConsoleCtrlHandler(fun, TRUE);

	// 关闭socket
	closesocket(socketServer);
	// 关闭网络库
	WSACleanup();
	system("pause");
	return 0;
}

// 1.打开网络库 2.校验版本号
void OpenAndCheckWSAStartup()
{
	// 定义WSAStartup函数中参数1 所需的版本号
	WORD wVersionRequestd = MAKEWORD(2, 2);	// 设置2.2版本号
	// 声明WSAStartup函数中参数1 所需的结构体
	WSADATA WSAData;
	// 打开网络库
	int nRet = WSAStartup(wVersionRequestd, &WSAData);
	if (0 != nRet)
	{
		//出错了
		int a = WSAGetLastError();
		printf("网络库打开失败 %d\n", a);
		return;
	}
	// 检验版本号
	if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
	{
		printf("校验版本号错误\n");
		// 关闭网络库
		WSACleanup();
		return;
	}
}
// 3.创建服务端socket
SOCKET Sock_Server()
{
	// 创建服务端 socket
	SOCKET sock_server = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	// 判断服务端socket是否成功创建
	if (INVALID_SOCKET == sock_server)
	{
		//出错了
		int a = WSAGetLastError();
		printf("创建服务端socket失败,获取的错误码为 %d\n", a);
		//关闭网络库
		WSACleanup();
		return 0;
	}
	return sock_server;
}
// 4.绑定地址与端口号
void Bind(SOCKET sock)
{
	// 声明bind函数参数2所需的 结构体
	struct sockaddr_in sockMsg;
	// 给结构体成员赋值
	sockMsg.sin_family = AF_INET;
	sockMsg.sin_port = htons(12345);
	sockMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	// 绑定地址与端口号
	int nRet = bind(sock, (struct sockaddr*)&sockMsg, sizeof(sockMsg));
	// 判断是否成功绑定地址与端口号
	if (SOCKET_ERROR == nRet)
	{
		//出错了
		int a = WSAGetLastError();
		printf("绑定地址与端口号失败,获取的错误码为 %d\n", a);
		//关闭socket
		closesocket(sock);
		//关闭网络库
		WSACleanup();
		return;
	}
}
// 5.与客户端进行发收数据
void RecvFromAndSendTo(SOCKET sock)
{
/*int recvfrom(
  SOCKET   s,
  char     *buf,
  int      len,
  int      flags,
  sockaddr *from,
  int      *fromlen
);
recvfrom函数:
	作用:得到当前服务端接收到的消息
	本质:复制	将协议缓存区的数据复制黏贴进自定义的buf
	与recv函数一样是进行接收消息,不同的是recv是傻等,recvfrom是死等
参数1:服务端的socket 
	特点:
		TCP是获取指定的,recv函数与客户端是1对1的关系 -- 傻等
		UDP是无差别获取,recvfrom函数与客户端是1对多的关系 -- 死等
参数2:客户端消息的存储空间,也就是一个字符数组
	广域网:各级路由器上的MTU最小值是576字节
		tcp = 576 - 20(TCP包头) - 20(IP包头) = 536;
		udp = 576 - 8(UDP包头) - 20(IP包头) = 548;
参数3:想要读取的字节个数, 参数2的字节数
参数4:数据的读取方式
		0			从协议缓存区取一个数据报,然后就删除掉
		MSG_PEEK	取出数据报,但是协议缓存内不删除,一直残留,导致影响后面的数据读取
		MSG_OOB		带外数据
参数5:对方的IP地址端口号
		此处结构体为相应的IP地址和端口号 struct sockaddr* sockClient; 填 &sockClient;
参数6:参数5结构体的大小 int len = sizeof(&sockClient);	填 &len;
*/
/*int sendto(
  SOCKET         s,
  const char     *buf,
  int            len,
  int            flags,
  const sockaddr *to,
  int            tolen
);
sendto函数:
	作用:向目标发送数据报
	本质:send函数将我们的数据复制黏贴进行系统的协议发送缓冲区,计算机伺机发出去
参数1:当前服务端的socket(自己的socket)
参数2:给接收方发送的字节串  UDP统一字节数 548
参数3:参数2的大小(字节个数 548)
参数4:填0 
参数5:对方IP地址与端口号结构体
参数6:参数5的大小

*/
	// 循环与客户端进行收发消息
	while (1)
	{
		// 1.收消息
		// 定义recvfrom 函数所需的接收字节数组
		char buf[548] = { 0 };
		// 声明recvfrom函数中参数5所需的 结构体相应的IP地址与端口号
		struct sockaddr sockClient;	// 对方的IP地址与端口号
		// 定义recvfrom函数中参数6所需的 参数5结构体的大小
		int len = sizeof(sockClient);
		int nRet = recvfrom(sock, buf, 548, 0, &sockClient, &len);
		// 判断是否成功接收消息
		if (SOCKET_ERROR == nRet)
		{
			// 出错了
			int a = WSAGetLastError();
			printf("接收消息失败,获取的错误码为:%d\n", a);
			continue;
		}
		// 进行接收消息
		printf("client say: %s\n", buf);

		// 2.发消息
		int nSet = sendto(sock, "send ok", sizeof("send ok"), 0, &sockClient, sizeof(sockClient));
		// 判断是否成功发送消息
		if (SOCKET_ERROR == nSet)
		{
			// 出错了
			int a = WSAGetLastError();
			printf("发送消息失败,获取的错误码为:%d\n", a);
			continue;
		}
	}
}

客户端代码:

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>

#pragma comment(lib, "ws2_32.lib")

/*
	UDP/IP 基本C/S模型 客户端:
		与 UDP/IP 基本C/S模型 服务端代码的区别:
		代码基本一致,只有bind 函数有区别
		服务端需要进行绑定地址与端口号,因为服务端是一对多
		客户端只需要创建服务端地址与端口结构体,与服务端发送数据报时,
		  直接传递的是创建的结构体就可以了,不用进行绑定,因为服务端的ip与端口号固定不变的	

*/
// 1.打开网络库 2.校验版本号
void OpenAndCheckWSAStartup();

// 3.创建服务端socket
SOCKET Sock_Server();

// 全局声明要创建 创建客户端socket
SOCKET socketClient;

// 4.与客户端进行发收数据
void SendToAndRecvFrom(SOCKET sock);

int main()
{
	// 1.打开网络库 2.校验版本号
	OpenAndCheckWSAStartup();

	// 3.创建服务端socket
	SOCKET socketClient = Sock_Server();

	// 4.与客户端进行发收数据
	SendToAndRecvFrom(socketClient);

	//释放socket 
	closesocket(socketClient);
	//清理网络库
	WSACleanup();
	system("pause");
	return 0;
}

// 1.打开网络库 2.校验版本号
void OpenAndCheckWSAStartup()
{
	// 定义WSAStartup函数中参数1 所需的版本号
	WORD wVersionRequestd = MAKEWORD(2, 2);	// 设置2.2版本号
	// 声明WSAStartup函数中参数1 所需的结构体
	WSADATA WSAData;
	// 打开网络库
	int nRet = WSAStartup(wVersionRequestd, &WSAData);
	if (0 != nRet)
	{
		//出错了
		int a = WSAGetLastError();
		printf("网络库打开失败 %d\n", a);
		return;
	}
	// 检验版本号
	if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
	{
		printf("校验版本号错误\n");
		// 关闭网络库
		WSACleanup();
		return;
	}
}
// 3.创建服务端socket
SOCKET Sock_Server()
{
	// 创建服务端 socket
	SOCKET sock_server = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	// 判断服务端socket是否成功创建
	if (INVALID_SOCKET == sock_server)
	{
		//出错了
		int a = WSAGetLastError();
		printf("创建服务端socket失败,获取的错误码为 %d\n", a);
		//关闭网络库
		WSACleanup();
		return 0;
	}
	return sock_server;
}
// 4.与客户端进行发收数据
void SendToAndRecvFrom(SOCKET sock)
{
	/*int recvfrom(
	  SOCKET   s,
	  char     *buf,
	  int      len,
	  int      flags,
	  sockaddr *from,
	  int      *fromlen
	);
	recvfrom函数:
		作用:得到当前服务端接收到的消息
		本质:复制	将协议缓存区的数据复制黏贴进自定义的buf
		与recv函数一样是进行接收消息,不同的是recv是傻等,recvfrom是死等
	参数1:客户端的socket
		特点:
			TCP是获取指定的,recv函数与客户端是1对1的关系 -- 傻等
			UDP是无差别获取,recvfrom函数与客户端是1对多的关系 -- 死等
	参数2:客户端消息的存储空间,也就是一个字符数组
		广域网:各级路由器上的MTU最小值是576字节
			tcp = 576 - 20(TCP包头) - 20(IP包头) = 536;
			udp = 576 - 8(UDP包头) - 20(IP包头) = 548;
	参数3:想要读取的字节个数, 参数2的字节数
	参数4:数据的读取方式
			0			从协议缓存区取一个数据报,然后就删除掉
			MSG_PEEK	取出数据报,但是协议缓存内不删除,一直残留,导致影响后面的数据读取
			MSG_OOB		带外数据
	参数5:对方的IP地址端口号
			此处结构体为相应的IP地址和端口号 struct sockaddr* sockClient; 填 &sockClient;
	参数6:参数5结构体的大小 int len = sizeof(&sockClient);	填 &len;
	*/
	/*int sendto(
	  SOCKET         s,
	  const char     *buf,
	  int            len,
	  int            flags,
	  const sockaddr *to,
	  int            tolen
	);
	sendto函数:
		作用:向目标发送数据报
		本质:send函数将我们的数据复制黏贴进行系统的协议发送缓冲区,计算机伺机发出去
	参数1:当前客户端的socket(自己的socket)
	参数2:给接收方发送的字节串  UDP统一字节数 548
	参数3:参数2的大小(字节个数 548)
	参数4:填0
	参数5:对方IP地址与端口号结构体
	参数6:参数5的大小

	*/
	// 声明sendto函数中参数5所需的 服务端地址与端口号结构体
	struct sockaddr_in sockServer;
	// 给服务端地址与端口号结构体成员赋值
	sockServer.sin_family = AF_INET;
	sockServer.sin_port = htons(12345);
	sockServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	// 循环与客户端进行收发消息
	while (1)
	{
		// 定义要发送的字节数组
		char strBuf[548] = { 0 };
		// 手动发送
		scanf_s("%s", strBuf, 548);
		// 设置输入0时,将关闭客户端
		if ('0' == strBuf[0])
		{
			break;
		}

		// 1.发消息
		int nSet = sendto(sock, strBuf, sizeof(strBuf), 0, (struct sockaddr*)&sockServer, sizeof(sockServer));
		// 判断是否成功发送消息
		if (SOCKET_ERROR == nSet)
		{
			// 出错了
			int a = WSAGetLastError();
			printf("发送消息失败,获取的错误码为:%d\n", a);
			continue;
		}

		// 2.收消息
		// 定义recvfrom 函数所需的接收字节数组
		char buf[548] = { 0 };
		// 声明recvfrom函数中参数5所需的 结构体相应的IP地址与端口号
		struct sockaddr sockClient;	// 对方的IP地址与端口号
		// 定义recvfrom函数中参数6所需的 参数5结构体的大小
		int len = sizeof(sockClient);
		int nRet = recvfrom(sock, buf, 548, 0, &sockClient, &len);
		// 判断是否成功接收消息
		if (SOCKET_ERROR == nRet)
		{
			// 出错了
			int a = WSAGetLastError();
			printf("接收消息失败,获取的错误码为:%d\n", a);
			continue;
		}
		// 进行接收消息
		printf("Server say: %s\n", buf);	
	}
}

程序运行结果:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值