Linux(socket网络编程)UDP---初学
提要
UDP的缺点:
(1)丢得少不找回,可以设置阈值
(2)数据段顺序不能保障
UDP的优点:
没有繁琐的连接过程
TCP和UDP:
(1)TCP通常比UDP慢点:
1.TCP收发数据前后要进行连接和关闭清理。
2.收发数据量越小,TCP效率就越低。
(2)TCP能保障数据的完整性。
(3)TCP中套接字是一对一关系,服务端为了和多个客户端通信,要建立多个用于通信的套接字
(4)UDP中,客户端和服务端都只需要一个套接字
实验
UDP通信的程序中
先 完成对套接字的地址分配工作
然后 调用sendto传输数据,调用sendto函数时自动分配IP和端口号
代码步骤
服务端:
1.创建套接字描述符、udp套接字;
2.创建并设置地址结构体,用bind函数把结构体绑定到套接字;
3.recvfrom函数用于接收;sendto函数用于发送
客户端:
1.创建套接字描述符、创建UDP套接字;
2.创建地址结构体,结构体要设置服务器的IP和端口号。
3.sendto用于发送;recvfrom用于接收
sendto函数和recvfrom函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom()
sockfd:套接字文件描述符
buf:指向存储接收数据的缓冲区的指针
len:缓冲区的大小
flags:通常设置为0
src_addr:指向存储发送方地址的sockaddr结构的指针
addrlen:指向存储src_addr长度的变量的指针
返回值:成功时返回接收到的字节数,失败返回-1。
flags
MSG_PEEK:查看数据但不从队列中移除。
MSG_WAITALL:等待接收完整的消息(仅用于TCP)。
MSG_DONTWAIT:非阻塞模式。
sendto
sockfd:套接字文件描述符
buf:指向包含要发送数据的缓冲区的指针
len:要发送的数据的长度
flags:通常设置为0
dest_addr:指向包含目标地址的sockaddr结构的指针
addrlen:dest_addr的长度
返回值:成功时返回发送的字节数,失败返回-1
flags
MSG_CONFIRM:请求对方确认数据已接收
MSG_DONTROUTE:绕过路由表,直接发送数据
MSG_DONTWAIT:非阻塞模式
代码示例
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h> //struct sockaddr_in,htons(),htonl(),INADDR_ANY
#include <stdlib.h>//atoi()
#include <sys/wait.h>//close()
#include <arpa/inet.h>//inet_addr()
int udp_server(int argc, char* argv[]) {
int ser_sock = -1;//服务器套接字描述符
char message[512] = "";
struct sockaddr_in servaddr {};//服务器地址结构体
//检查命令行参数数量
if (argc != 2) {
printf("usage:%d <port>\n", argv[0]);
printf("error:argement is error\n");
return -1;
}
//创建udp套接字
ser_sock = socket(PF_INET, SOCK_DGRAM, 0);
if (ser_sock == -1) {
printf("error:create socket failed\n");
return -1;
}
//设置服务器结构体
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//监听所有可用端口
servaddr.sin_port = htons((short)atoi(argv[1]));//转换端口号为网络字节序
//绑定套接字到指定地址和端口
if (bind(ser_sock, (struct sockaddr*) & servaddr, sizeof(servaddr)) == -1) {
printf("error:bind failed");
close(ser_sock);
return -1;
}
struct sockaddr_in clientaddr;//
socklen_t clientlen{};
//循环接收并回显消息10次
for (int i = 0; i < 10; i++) {
clientlen = sizeof(clientaddr);
ssize_t len = recvfrom(ser_sock, message, sizeof(message), 0, (struct sockaddr*)&clientaddr, &clientlen);
sendto(ser_sock, message, len, 0, (struct sockaddr*)&clientaddr, clientlen);
}
close(ser_sock);
return 0;
}
#include<string.h>//strcmp()
int udp_client(int argc, char* argv[])
{
int client_sock;//客户端套接字描述符
struct sockaddr_in serv_addr;//地址结构体,存的是服务端的地址
socklen_t serv_len = sizeof(serv_addr);
char message[512] = "";
if (argc != 3) {
printf("usage:%s ip port\n", argv[0]);
printf("error:argement error!");
return -1;
}
//创建UDP套接字
client_sock = socket(PF_INET, SOCK_DGRAM, 0);
if (client_sock == -1) {
printf("error:socket create failed!");
return -1;
}
//设置地址结构体
serv_addr.sin_family = AF_INET;//地址族IPv4
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);//服务器IP地址
serv_addr.sin_port = htons((short)atoi(argv[2]));//服务器端口号
//循环发送和接收消息,直到用户输入‘q’或‘Q’
while (1) {
printf("input message(q to Quit):");
scanf("%s", message);
if ((strcmp(message, "q") == 0) || (strcmp(message, "Q") == 0)) {
break;
}
ssize_t len = sendto(client_sock, message, strlen(message), 0, (sockaddr*)&serv_addr, serv_len);
memset(message, 0, sizeof(message));
recvfrom(client_sock, message, sizeof(message), 0, (sockaddr*)&serv_addr, &serv_len);
printf("recv:%s\n", message);
}
close(client_sock);
return 0;
}
void test(char* argv0)
{
if (fork() > 0) {
int argc = 3;
char* argv[] = {
argv0,
(char*)"127.0.0.1",//const char[]类型转换为char*类型,避免被警告
(char*)"9999"
};
udp_client(argc, argv);
int status = 0;
wait(&status);//等待子进程结束
}
else {
int argc = 2;
char* argv[] = {
argv0,
(char*)"9999"
};
udp_server(argc,argv);
}
}
int main(int argc,char* argv[])
{
test(argv[0]);
return 0;
}