目录
前言
本文主要介绍编写UDP服务端和客户端的一些关键函数,相比于TCP,UDP无需实现流控制,所以两端传输数据前无需建立连接。端点仅仅需要一个套接字就能与其他各个端点通信,而TCP每一个连接都需要一对套接字。UDP的客户端和服务端并没有太大的差别,所以本文不做细分。
一、UDP通信关键函数
1. 发送数据 sendto
ssize_t sendto(int sock, void* buff, size_t nbytes, int flags,
struct sockaddr* to, socklen_t addrlen);
成功则返回传输的字节数,失败则返回-1。
(1)sock:套接字文件描述符
(2)buf:要传输的数据
(3)nbytes:传输的数据长度
(4)flags:可选项参数,一般为0
(5)to:保存目标地址的sockaddr结构体变量地址
(6)addrlen:to参数的结构体变量长度
关于各参数的含义可以参考我的上一篇文章,这里不再赘述。
调用sendto:
sendto(server_socket,reply,sizeof(reply),0,
(struct sockaddr*)&client_addr,client_addr_len);
2. 接收数据 recvfrom
ssize_t recvfrom(int sock, void* buff, size_t nbytes, int flags,
struct sockaddr* from, socklen_t* addrlen);
成功则返回接收的字节数,失败则返回-1。
(1)sock:套接字文件描述符
(2)buf:接收数据的缓存地址
(3)nbytes:可接收的最大字节数
(4)flags:可选项参数,一般为0
(5)to:sockaddr结构体变量地址,用于保存发送方地址信息
(6)addrlen:用于保存from参数的结构体变量长度的地址
调用recvfrom:
recvfrom(server_socket,buf,sizeof(buf),0,
(struct sockaddr*)&client_addr,&client_addr_len);
二、UDP编程中的注意点
1. bind函数的使用
套接字在使用之前必须先给其分配地址,bind函数的作用就是手动给套接字分配地址,那么什么时候需要调用bind函数呢?
sendto函数和TCP编程中的connect函数一样,在调用过程中会自动给套接字分配地址,在这种情况下就不需要bind手动分配了。但是如果在调用这两个函数之前使用套接字,就要用bind先分配地址,比如先recvfrom,再sendto的情况。
2. UDP套接字存在数据边界
在TCP中,即使发送方调用多次send函数,接收方也可以通过一次recv函数接收所有数据。但是在UDP中,sendto和recvfrom函数的调用次数必须相同,不然接收不到完整的数据。在UDP中,recvfrom端依然有接收缓冲区。
3. UDP客户端调用connect函数
sendto函数会自动为套接字分配地址,函数调用完毕后会自动删除注册的地址,如果向同一目标多次发数据的情况下,重复的分配与删除地址会浪费时间,所以在客户端可以使用connect函数分配好地址,这样sendto就只需传输数据。客户端还可以像TCP一样使用send、recv函数进行通信。
三、 完整代码
1. 无连接版服务端
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;
int main(int argc, char* argv[])
{
int server_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char* ip = "127.0.0.1";
char* port = "9955";
char buf[100];
char *reply = "ok";
server_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(ip);
server_addr.sin_port = htons(atoi(port));
bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)); //bind分配地址
while(1)
{
recvfrom(server_socket,buf,sizeof(buf),0,
(struct sockaddr*)&client_addr,&client_addr_len);
if(!strcmp(buf,"q")) //收到q关闭
break;
else
cout << buf << endl;
sendto(server_socket,reply,sizeof(reply),0,
(struct sockaddr*)&client_addr,client_addr_len);
}
close(server_socket);
return 0;
}
2. 无连接版客户端
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;
int main(int argc, char* argv[])
{
int client_socket;
struct sockaddr_in server_addr;
socklen_t server_addr_len = sizeof(server_addr), from_len;
char* ip = "127.0.0.1";
char* port = "9955";
char buf[100];
client_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(ip);
server_addr.sin_port = htons(atoi(port));
while(1)
{
cout << "input: ";
cin >> buf;
sendto(client_socket,buf,sizeof(buf),0,
(struct sockaddr*)&server_addr,sizeof(server_addr));
if(!strcmp(buf,"q")) //输入q关闭
break;
else
recvfrom(client_socket,buf,sizeof(buf),0,
NULL,NULL);
cout << buf << endl;
}
close(client_socket);
return 0;
}
3. 有连接版服务端
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;
int main(int argc, char* argv[])
{
int server_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
char* ip = "127.0.0.1";
char* port = "9955";
char buf[100];
char *reply = "ok";
server_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(ip);
server_addr.sin_port = htons(atoi(port));
bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr));
for(int i=1;i<=3;i++)
{
recvfrom(server_socket,buf,sizeof(buf),0,
(struct sockaddr*)&client_addr,&client_addr_len);
cout << "i: " << i << " msg: " << buf << endl;
sendto(server_socket,reply,sizeof(reply),0,
(struct sockaddr*)&client_addr,client_addr_len);
}
close(server_socket);
return 0;
}
4. 有连接版客户端
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
using namespace std;
int main(int argc, char* argv[])
{
int client_socket;
struct sockaddr_in server_addr, from; //用from存储发送方的地址
socklen_t server_addr_len = sizeof(server_addr), from_len;
char* ip = "127.0.0.1";
char* port = "9955";
char* buf[] = {"hello","world","!"};
char reply[30];
client_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&server_addr,0,sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(ip);
server_addr.sin_port = htons(atoi(port));
connect(client_socket,(struct sockaddr*)&server_addr,server_addr_len);
for(int i=1;i<=3;i++)
{
send(client_socket,buf[i-1],sizeof(buf[i-1]),0);
recv(client_socket,reply,sizeof(reply),0);
cout << reply << endl;
}
close(client_socket);
return 0;
}