UDP通信
- 1 server
- 1.1 server建立的步骤
- 1.2 运行server
- 2 client
- 2.1 client的建立步骤
- 2.2 运行client
- 3 总结
- 3.1 server
- 3.2 client
1 server
server的启动方式是:./udpserver 8080 --> 格式就是./proc port端口
port端口自己指定
1.1 server建立的步骤
- 获取文件描述符
_sockfd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET:表示ipv4. SOCK_DGRAM:表示UDP
socket
函数就是创建套接字,返回值_sockfd
就是文件描述符。
2. 获取套接字,并填充
1.套接字的内容有哪些?
套接字是一个结构体,sockaddr
是套接字。
套接字分为三种:网络套接字,域间套接字和原始套接字。
可以将网络套接字/域间套接字/原始套接字看作是套接字的子类
sockaddr_in/sockaddr_un
继承自sockaddr
1. 填充套接字
主要就填充三个部分:
sin_family
:直接填充为AF_INET就行了。
local.sin_family = AF_INET;
sin_port
:直接填充我们服务器启动的端口号。
local.sin_port = _port;
sin_addr
:填充为我们服务器启动的ip地址。
但是这里有一个需要注意的地方:
2.sin_addr是struct in_addr类型,ip地址是string类型,该如何进行赋值呢?
所以,一路往下调用,最终就是uint32_t
类型。
这里的uint32_t
类型是网络中的ip地址类型,我们日常使用中为了方便,采用的是点分十进制(也就是string
类型)。因此,需要将string
转换为uint32_t
类型。
这样的转化,可以使用inet_addr()
函数。
local.sin_addr.s_addr = inet_addr(_ip.c_str());
- 将套接字和ip地址和port端口进行绑定
3.为什么要进行绑定,之前不是已经将ip地址和port填充给套接字了吗?
在绑定之前,
sockaddr_in
是在栈区上的,操作系统根本不知道设的值是什么,只有使用bind
之后,才真正将IP地址和端口号绑定到了操作系统中。
bind()函数接口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd : 之前socket()函数返回的文件描述符
- addr:套接字。我们的套接字是
sockaddr_in
类型(网络套接字),因此需要强转 - addrlen:套接字的长度,
sizeof(addr)
- 返回值:创建成功返回0,创建失败返回-1
1.2 运行server
本文想实现的功能是:客户端发送信息,服务端接收。
服务端要通过文件描述符得到客户端发来的内容,因此需要recvform()函数:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
sockfd
: 创建套接字时返回的文件描述符fd。buf
: 用来存储从网络中读取下来的数据的。len
: buf缓冲区的大小。flags
: 读取的方式,一般设置为0,即阻塞读取数据。sockaddr* src_addr
: 输出型参数,同样传参sockaddr_in结构体,系统会自动对这个结构体进行填充,可以获取数据的来源,包括发送方的地址类型,端口号port以及IP地址。addrlen
是第五个输出型结构体变量的大小所在的地址,注意类型是socklen_t*的,和bind的时候不一样。- 返回值ssize_t,返回读取到的数据个数,单位是字节,如果读取失败则返回-1。
里面传的参数是指针类型的参数,都可以获得他们的值。
struct sockaddr_in client; //记录客户端(发送方)的ip地址和端口号socklen_t len = sizeof(client);//通过recvfrom拿到client结构体的ip地址,port端口等,因为sockaddr* src_addr是输出型参数int n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (sockaddr*)&client, &len);
2 client
流程:
client需要向server发送信息,因此,需要知道server的ip地址和port。在启动的时候已经知道了。只需要将ip地址和port填充到server的套接字中就行了。最后通过sendto()函数将要写入的内容通过文件描述符写到套接字中。
2.1 client的建立步骤
client的启动方式是:./udpclient 127.0.0.0.1 8080 --> 格式就是./proc ip地址 port端口
- 填充server的套接字
string serverip = argv[1];
uint16_t serverport = stoi(argv[2]);struct sockaddr_in server;
bzero(&server, sizeof(server));//填充套接字结构体
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(serverip.c_str());
server.sin_port = htons(serverport);
- 通过socket()获取文件描述符
过程和建立server相同。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
4.client需要绑定吗?
客户端在发送数据时不一定需要显式绑定,因为系统会自动分配一个可用端口。客户端只需要指定目标服务器的地址和端口即可,操作系统会自动完成本地端口的分配。因此,对于客户端,
bind
是可选的。
2.2 运行client
使用sendto()
函数,将message
的内容通过文件描述符写到server
的套接字中。
string message;
char buffer[1024];while(true)
{cout << "please enter@ ";getline(cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr*)&server, sizeof(server));
}
3 总结
3.1 server
- 根据 ./proc port的输入,得到服务器的端口
- 使用socket()函数获得文件描述符
- 填充server的套接字,包括sin_family/sin_addr/sin_port
- 使用bind()函数将得到的套接字和文件描述符/ip地址/port端口进行绑定
- 使用recvfrom()函数,通过文件描述符获得client发来的消息。可以根据recvfrom()的输出型参数获得client的套接字,从而获得client的ip地址和port端口。
3.2 client
- 根据 ./proc ip port的输入,得到服务器的ip地址和端口
- 使用socket()函数获得文件描述符
- 根据获得的server服务器的ip地址和端口填充server的套接字,包括sin_family/sin_addr/sin_port
- 使用sendto()函数将要发送的内容通过文件描述符发送server端。
代码链接:UDP通信