网络编程——UDP(用户数据报)
udp特征:
1.数据传输不可靠(丢包率);
2.低延迟;
3.网络开销小;
4.无连接 //无连接 意味着在通信双方开始传输数据之前,不需要预先建立一条专用的通信通道(即连接)
数据报特征:
1.数据与数据之间有边界
2.发送的次数和接收的次数需要对应(保持一致)
3.如果发送太快,就会丢包 //原因:硬盘读的快写的慢
操作流程
头文件:#include <sys/types.h>
#include <sys/socket.h>#include <netinet/ip.h> /* bind() */
#include <arpa/inet.h> /* inet_addr() */
服务端:
socket() ==》bind() ==》recvfrom() ==》sendto()
!!!伪代码逻辑
//1.创建一个UDP套接字
socket(AF_INET, SOCK_DGRAM, 0);
// 2. 构建服务器地址 (Server Address Setup)
// (服务器的IP和Port)
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT); // 服务器端口,如8080
inet_addr("ser.ip"); // 服务器IP,如"192.168.1.100"
//3.将该套接字绑定到服务器的IP和某个端口(如 8080)
bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//4.阻塞等待,直到有客户端发来数据。cli_addr 会被填充为客户端的地址信息
recvfrom(sockfd, buf, buf_size, 0, (struct sockaddr*)&cli_addr, &cli_addr_len);
//5.处理完请求后,使用 recvfrom 得到的客户端地址 cli_addr,将回复数据发送回去
sendto(sockfd, response_buf, response_len, 0, (struct sockaddr*)&cli_addr, cli_addr_len);
1、socket() 创建通信节点(打开网络设备)
原型: int socket(int domain, int type, int protocol);
功能: 创建一个通信端点,并返回一个文件描述符(socket descriptor)
参数: int domain
: 指定协议族(Protocol Family)
int type
: 指定通信语义(套接字类型)
int protocol
: 通常设置为 0
,表示根据 domain
和 type
自动选择默认协议。例如,(AF_INET, SOCK_STREAM)
会自动选择 TCP。
返回值: 成功 返回非负整数文件描述符
失败 返回-1,并设置errno
指定协议族:
AF_INET
: IPv4 协议
AF_INET6
: IPv6 协议
AF_UNIX
或AF_LOCAL
: 本地进程间通信指定通信定义:
SOCK_STREAM
: 提供面向连接的、可靠的字节流(TCP)
SOCK_DGRAM
: 提供无连接的、不可靠的数据报服务(UDP)
2、bind() 给套接字,绑定地址和端口号( ip+port )
原型: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能: 将一个本地协议地址(IP地址 + 端口号)分配给由 socket()
创建的套接字。服务器程序通常需要调用此函数来将其套接字与一个众所周知的端口号绑定,以便客户端能够连接到服务器
参数: int sockfd
: socket()
函数返回的套接字描述符
const struct sockaddr *addr
: 指向一个地址结构体的指针,该结构体包含了要绑定的IP和端口号
socklen_t addrlen
: 第二个参数 addr
所指向结构体的长度(以字节为单位),通常使用 sizeof(struct sockaddr_in)
返回值: 成功 返回0;
失败 返回-1,并设置errno
参数二:指向地址结构体的指针:
对于IPv4 (
AF_INET
),使用struct sockaddr_in
对于IPv6 (
AF_INET6
),使用struct sockaddr_in6
bind伪代码示例
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
//host to net short
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.1.200");
int ret = bind(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind fail");
return 1;
}
3、recvfrom() 从客户端接收数据(阻塞等待客户端请求)
原型: ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
功能: 从一个(特别是无连接的,如UDP)套接字接收数据,并捕获发送方的源地址。它是一个阻塞性调用,除非有数据到达,否则调用该函数的进程会被挂起(休眠)
参数:
int sockfd
: 套接字描述符。void *buf
: 指向接收缓冲区的指针,用于存放接收到的数据。size_t len
: 接收缓冲区buf
的最大长度。int flags
: 控制接收行为的标志位,通常设置为0
(表示无特殊行为)。struct sockaddr *src_addr
: (输出参数)指向一个sockaddr
结构体的指针,用于存放发送方的地址信息(IP和端口)。如果不需要知道发送方是谁,可以设置为NULL
。socklen_t *addrlen
: (输入输出参数)输入: 指向一个整数,表示
src_addr
指向的缓冲区的大小。输出: 函数返回时,该整数会被设置为实际存放的地址信息的长度。
返回值: 成功 返回接收到的字节数
失败 返回-1,并设置errno
recvfrom伪代码示例
char buf[512] = {0};
recvfrom(sockfd,buf,sizeof(buf),0, (SA)&cli,&len); //接收客户端数据
printf("recv client:%s\n",buf);
4、sendto() 向套接字发送数据
原型: ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
功能: 向一个指定的目标地址发送数据。通常用于无连接的套接字(如UDP),因为每次发送都需要指定目标地址。
参数:
int sockfd
: 套接字描述符。const void *buf
: 指向发送缓冲区的指针,包含要发送的数据。size_t len
: 要发送的数据的字节数。int flags
: 控制发送行为的标志位,通常设置为0
。const struct sockaddr *dest_addr
: 指向一个sockaddr
结构体的指针,包含了目标接收方的地址信息(IP和端口)。socklen_t addrlen
: 第五个参数dest_addr
所指向结构体的长度。
返回值: 成功 返回实际发送出去的字节数
失败 返回-1,并设置errno
sendto伪代码示例
sprintf(buf, "%s %s",buf,ctime(&tm)); //数据处理
sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len); //发送给客户端处理结束的数据
总代码示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * SA;
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket fail");
return 1;
}
//man 7 ip
struct sockaddr_in ser,cli;
ser.sin_family = AF_INET;
//host to net short
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.1.200");
int ret = bind(sockfd,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("bind fail");
return 1;
}
time_t tm;
socklen_t len = sizeof(cli);
while (1)
{
char buf[512] = {0};
time(&tm);
recvfrom(sockfd,buf,sizeof(buf),0, (SA)&cli,&len);
printf("recv client:%s\n",buf);
sprintf(buf, "%s %s",buf,ctime(&tm));
sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);
}
return 0;
}
客户端:
伪代码逻辑
socket() ==》sendto() ==》recvfrom()
//1.创建UDP套接字(Socket Creation)
// 创建一个用于UDP通信的套接字,得到文件描述符sockfd
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 2. 构建服务器地址 (Server Address Setup)
// 告诉系统数据要发往哪里(服务器的IP和Port)
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT); // 服务器端口,如8080
inet_addr("ser.ip"); // 服务器IP,如"192.168.1.100"
// 3. 发送数据 (Sending Data)
// 使用sendto将数据发送到上面指定的serv_addr
sendto(sockfd, request_data, request_len, 0,
(struct sockaddr*)&serv_addr, sizeof(serv_addr));
// 4. 接收回复 (Receiving Response)
// 使用同一个sockfd接收数据。可以捕获回复方的地址,这里设为NULL表示不关心
recvfrom(sockfd, response_buf, sizeof(response_buf), 0,
NULL, NULL); // 不关心回复来自谁,所以后两个参数为NULL
【函数使用方法见上述服务器端!!!】
1、socket() 创建通信节点(打开网络设备)
2、sendto() 向指定目标发送数据
3、recvfrom() 从套接字接收处理后的数据
总示例代码
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES */
#include <time.h>
#include <unistd.h>
typedef struct sockaddr *SA;
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket fail");
return 1;
}
//
struct sockaddr_in ser;
ser.sin_family = AF_INET;
// host to net short
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.1.200");
// ser.sin_addr.s_addr = inet_addr("192.168.1.23");
int i = 10;
while (i--)
{
char buf[512] = {"hello"};
sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
bzero(buf, sizeof(buf));
recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
printf("from ser:%s\n", buf);
sleep(1);
}
return 0;
}
服务器/客户端模型
c/s b/s
c/s(客户端/服务器模型) | b/s(浏览器/服务器模型) | |
客户端专用/通用 | 客户端专用 | 客户端通用 |
使用协议 | http协议 | 自定义协议、标准协议 |
资源角度 | 资源大部分都在client,server发送必要的交互资源给client | server发送给client |
功能 | 功能可以相对复杂 | 受到http协议的限制,功能不复杂 |
p2p模型(peer)
在P2P网络中,每个节点(Peer)既是客户端(向其他节点请求服务),也是服务器(为其他节点提供服务)。没有永恒的中心服务器,所有节点地位对等。
一个P2P节点的核心任务是:既要能主动发起请求,也要能被动接收并处理请求。因此,每个节点的流程都融合了客户端和服务器的行为。