网络编程
UDP 编程和 TCP编程的区别:
-
TCP是面向连接的,UDP是无连接的
-
TCP是可靠的,UDP是不可靠的
-
TCP是面向字节流的,UDP是面向数据报文的
-
TCP只支持点对点通信,UDP支持一对一,一对多,多对多
TCP编程 可以看这个。
由于 UDP 是无连接的,而且是不可靠的,和 TCP 相比,并不需要建立连接,所有 UDP 只需要知道往哪里发,直接发送就好。
1、UDP 编程
1.1、前期准备
#include <WinSock32.h> // 包含头文件
#pragma comment(lib, "ws2_32.lib") // 添加静态链接库
int main() {
// 网络环境初始化
WSADATA data;
WORD virsion = MAKEWORD(2, 2);
int err = WSAStartup(virsion, &data);
if (err != 0)
{
printf("WSAStartup errorNum = %d", err);
return err;
}
if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2)
{
printf("LOBYTE errorNum = %d", GetLastError());
WSACleanup();
return -1;
}
WSACleanup(); // 清除网络环境
}
1.2、服务器编程
1.2.1、示例代码
int main() {
// 一、初始化网络环境
....;
// 二、创建服务器
SOCKET sockSer = socket(AF_INET, SOCK_DGRAM, 0);
if (sockSer == INVALID_SOCKET)
{
printf("socket errorNum = %d\n", GetLastError());
return -1;
}
// 三、配置网络地址和端口号
SOCKADDR_IN addrSer = {};
addrSer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSer.sin_family = AF_INET; // 始终设置为 AF_INET
addrSer.sin_port = htons(5201);
// 四、绑定网络地址和端口号
if (SOCKET_ERROR == bind(sockSer, (SOCKADDR*)&addrSer, sizeof(SOCKADDR)))
{
printf("bind errorNum = %d\n", GetLastError());
return -1;
}
SOCKADDR_IN addrCli = {};
int len = sizeof(SOCKADDR_IN);
char recvBuff[100] = { 0 }; // 接收数据
char sendBuff[100] = { 0 }; // 发送数据
int iLen = 0; // 接收的数据数
// 五、循环发送数据
while (TRUE)
{
iLen = recvfrom(sockSer, recvBuff, 100, 0, (SOCKADDR*)&addrCli, &len);
if (iLen == SOCKET_ERROR)
{
printf("recvfrom errorNum = %d\n", GetLastError());
return -1;
}
else if (iLen == 0) break; // 正常关闭连接,退出循环
printf_s("%s\n", recvBuff); // 正常接收,打印信息
// 在 sendBuff 中配置要发送的信息
iLen = sendto(sockSer, sendBuff, strlen(sendBuff), 0, (SOCKADDR*)&addrCli, len);
if (iLen == SOCKET_ERROR)
{
printf("sendto errorNum = %d\n", GetLastError());
return -1;
}
}
// 六、关闭连接
closesocket(sockSer);
WSACleanup();
return 0;
}
1.2.2、主要函数
-
创建服务器套接字:
socket()
SOCKET WSAAPI socket(int af, int type, int protocol );
第一个参数为 地址系列规范,使用IPv4 为 AF_INET,使用IPv6为AF_INET6
第二个参数为 套接字的类型规范,地址系列为为IPv4或IPv6的情况下,TCP协议则为SOCK_STREAM,UDP协议为SOCK_DGRAM
第三个参数为 要使用的协议,为 0 则调用方不希望指定协议,服务提供商将选择要使用的 协议 。
-
绑定本机 IP 和端口:
bind()
int bind(
SOCKET s, // 创建的socket
sockaddr * name, // 包含地址和端口的结构体
int namelen // sockaddr 结构长度
);
// 返回值:返回SOCKET_ERROR失败,该宏被定义为-1,否则成功,返回值为0
-
发送信息:
sendto()
-
接收信息:
recvfrom()
int sendto(
SOCKET s,
const char *buf, // 要发送的数据
int len, // 内容长度
int flags, // 一般为0
const sockaddr *to, // 要发送的网络地址和端口信息
int tolen // 由 to 参数指向的地址的大小
);
// 返回值:宏SOCKET_ERROR 表示发送失败,否则返回发送成功的字节数
int recvfrom(
SOCKET s, // 套接字
char *buf, // 接受数据的缓存区
int len, // 缓存区大小
int flags, // 标志,一般填0
sockaddr *from, // 要接收的网络地址和端口信息
int *fromlen // 由 from 参数指向的地址的大小
);
// 如果未发生错误, recvfrom 将返回收到的字节数。 如果连接已正常关闭,则返回值为零。 否则,将返回值 SOCKET_ERROR
-
网络环境初始化:
WSAStartup()
-
关闭 socket :
closesocket()
-
清除网络环境:
WSACleanup()
1.3、客户端编程
1.3.1、示例代码
int main()
{
// 一、初始化网络环境
// ....;
// 二、创建服务器
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
if (sockCli == INVALID_SOCKET)
{
printf("socket errorNum = %d\n", GetLastError());
return -1;
}
// 三、配置要发送到的网络地址和端口号
SOCKADDR_IN addrCli = {};
inet_pton(AF_INET, "127.0.0.1", &addrCli); // 使用网络回环地址
addrCli.sin_family = AF_INET; // 始终设置为 AF_INET
addrCli.sin_port = htons(5201);
int len = sizeof(SOCKADDR);
int iLen = 0; // 接收的数据数
char sendBuff[100] = { 0 }; // 发送数据
char recvBuff[100] = { 0 }; // 接收数据
// 四、发送数据
// 在 sendBuff 中配置要发送的数据
iLen = sendto(sockCli, sendBuff, 100, 0, (SOCKADDR*)&addrCli, len);
if (iLen == SOCKET_ERROR)
{
printf("sendto errorNum = %d\n", GetLastError());
return -1;
}
printf("send: %s\n", sendBuff);
// 五、接收数据
iLen = recvfrom(sockCli, recvBuff, 100, 0, (SOCKADDR*)&addrCli, &len);
if (iLen == SOCKET_ERROR)
{
printf("recvfrom errorNum = %d\n", GetLastError());
return -1;
}
printf("recv: %s\n", recvBuff);
system("pause");
return 0;
}
1.3.2、主要函数
在2.2.2、主要函数中均出现过