网络中通信的两端主机:客户端,服务端
客户端:是通信中主动发起请求的一端。
服务端:通信中针对请求提供服务的一端,也是被动接受请求的一端。
C/S –>客户端服务器架构 ; B/S -->浏览器服务器架构
1.UDP通信流程和接口介绍
UDP协议(用户数据报协议)特点:无连接(通信两端不需要先建立连接),不可靠(有可能数据丢包或者乱序),面向数据报。 应用于传输实时性高于安全性的场景,如 视屏音频传输。
1.1UDP通信流程:
服务端需要先启动等待客户端的请求,客户端需要先知道服务端的地址信息。
Server端:
-
创建套接字 在内核中创建socket结构体---->网络通信的一个句柄 -
为套接字绑定地址信息。给创建的socket结构指定源端IP和端口,协议。(服务端的地址信息一般都写死在程序中了) 作用: 发:1.发送数据时指定源端地址信息 收:2.告诉操作系统收到的哪条数据应该交给这个socket处理 -
接收数据 从指定的socket缓冲区中取出数据, -
发送数据 将数据放到指定的socket的发送缓冲区中。 -
关闭套接字
Client端:
- 创建套接字
- 为套接字绑定地址信息(客户端通常不主动绑定地址)
反例eg:多个客户端的情况,如果第一个客户端绑定了固定的端口,其他的客户端则无法绑定这个端口(一个端口只能被一个进程占用)。 - 发送数据 (发送数据时,检测到若自己没绑定地址信息,则系统会自动的选择合适的地址信息(IP+PORT)进行绑定。)(服务端的地址一般会提前告诉客户端,比如QQ客户端里面就写有其服务端的地址信息,不用我们自己写。)
- 接收数据
- 关闭套接字.
1.2接口介绍:
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
1.2.1创建套接字
int socket(int domain, int type, int protocol);
domain:地址域类型。
AF_INET : ipv4地址域类型 struct sockaddr_in
AF_INET6: ipv6地址域类型 struct sockaddr_in6
AF_UNIX: 本地 地址域类型 struct sockaddr_un
type:套接字类型
SOCK_STREAM:流式套接字—--提供字节流传输 默认协议为TCP
SOCK_DGRAM:数据报套接字---提供数据包传输 默认协议为UDP
protocol:通信所使用的协议类型(常用TCP/UDP)
0:表示套接字类型默认协议
IPPROTO_TCP(0):TCP协议
IPPROTO_UDP(17):UDP协议
返回值:成功返回一个文件描述符—>操作句柄,失败返回-1
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址 . IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容. socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
1.2.2为套接字绑定地址信息
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockkfd: 创建套接字返回的描述符
addr: struct sockaddr 地址信息通用结构体
addrlen:指定地址结构长度
返回值:成功返回0,失败-1;
如下图:sockaddr 和sockaddr_in、sockaddr_in6地址信息的长度和内部数据组织形式不同,但是函数接口里使用了统一的通用结构体来接受传入的地址,根据传addrlen(指定地址结构长度)就能解析出来你所传的是哪一种地址结构体。
sockaddr_in结构:----- /usr/include/linux/socket.h 常见操作: addr.sin_port=htons(port); addr.sin_addr.s_addr=inet_addr(ip) 我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址 sockaddr_in中: short sin_family;/Address family一般来说AF_INET(地址族)PF_INET(协议族)/
unsigned short sin_port;/Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)/
struct in_addr sin_addr;/IP address in network byte order(Internet address)/
unsigned char sin_zero[8];/Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐/
其中sockaddr结构为: sa_family_t:地址域类型 sa_data:通用结构体。 常见操作://addr.sin_family=AF_INET; in_addr结构为:
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数.
1.2.3发送数据
Size_t sendto( int sockfd, void *data,int len ,
int flag,struct sockaddr * peer_Addr,socklen_t addrlen);
sockfd:套接字描述符
data:要发送的数据的空间首地址
len:要发送的数据长度
flag:标志位 0表示阻塞发送(缓冲区数据满了则等待)
peer_Addr:获取指定对端的地址信息(发给谁)
addrlen:地址信息长度;
返回值:成功返回实际发送的长度,失败返回-1;
1.2.4接收数据
ssize_t recvfrom(int sockfd,void*buf , int len, int flag,
struct sockaddr* peer_addr,socklen_t* addrlen);
sockfd:套接字描述符
buf :缓冲区空间地址,用于存放接收的数据
len:要获取数据的长度
flag: 0 默认为阻塞接收(无数据则阻塞等待)
peer_addr:用于获取发送端的地址信息(谁发的)
addrlen:输入输出型参数---指定想要获取多长的地址;返回实际长度。
返回值:返回实际接收到的数据长度,失败返回-1;
使用UDP协议接收数时每次接收一条完整的数据,不一定将缓冲区中数据获取完。
1.2.5 关闭套接字 释放资源
Int close(int fd);
2.UDP通信代码实现
头文件和UdpSocket类接口:udpsocket.hpp 封装l了一个UdpSocket类,通过实例化对象,调用自己的成员接口可以在外部更加简单的完成客户端与服务端的搭建。 封装使代码的耦合度降低,方便外部调用,且内部数据访问更加安全。
#include <cstdio>
#include <iostream>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
class UdpSocket{
public:
UdpSocket():_sockfd(-1)
{}
bool Socket(){
_sockfd =socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP) ;
if(_sockfd<0){
perror("socket error\n");
return false;
}
return true;
}
bool Bind(const std::string &ip, uint16_t port) {
struct sockaddr_in addr;
addr.sin_family= AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port= htons(port);
int ret = bind(_sockfd,(struct sockaddr*)&addr,
sizeof(struct sockaddr_in));
if(ret<0){
perror("bind error \n");
return false;
}
return true;
}
bool Send(const std::string &data,
const std::string &ip, uint16_t port)
{
struct sockaddr_in peer_addr;
peer_addr.sin_family = AF_INET;
peer_addr.sin_port=htons(port);
peer_addr.sin_addr.s_addr=inet_addr(ip.c_str());
size_t ret = sendto(_sockfd,data.c_str(),data.size(),0,
(struct sockaddr*)&peer_addr,sizeof(struct sockaddr_in));
if(ret<0){
perror("send error \n");
return false;
}
return true;
}
bool Recv(std::string *buf,std::string *ip=NULL, uint16_t *port=NULL)
{
struct sockaddr_in addr;
socklen_t len =sizeof(struct sockaddr_in);
char tmp[4096]={0};
int ret = recvfrom(_sockfd,tmp,4096,0,
(struct sockaddr*)&addr,&len);
if(ret<0){
perror("recvfrom error\n");
return false;
}
buf->assign(tmp, ret);
if(ip!=NULL){
*ip=inet_ntoa(addr.sin_addr);
}
if(port!=NULL){
*ip = ntohs(addr.sin_port);
}
return true;
}
bool Close() {
if(_sockfd != -1) {
close(_sockfd);
}
return true;
}
private:
int _sockfd;
};
服务端:udp_server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(){
int sockfd= socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(sockfd<0){
perror("socket error\n");
return -1;
}
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(9999);
addr.sin_addr.s_addr =inet_addr("192.168.86.3");
socklen_t addrlen = sizeof(struct sockaddr_in);
int ret= bind(sockfd,(struct sockaddr*)&addr,addrlen);
if(ret <0){
perror("bind error\n");
return -1;
}
while(1){
char buf[1024]={0};
struct sockaddr_in peer_addr;
ret = recvfrom(sockfd, buf, 1023,0,(struct sockaddr*)&peer_addr,&addrlen);
if(ret <0){
perror("recvfrom error\n");
close(sockfd);
return -1;
}
printf("clent say:%s\n",buf);
memset(buf,0,1024);
printf("server say: ");
fflush(stdout);
fgets(buf,1024,stdin);
ret=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&peer_addr,addrlen);
if(ret <0){
perror("sendto error\n");
close(sockfd);
return -1;
}
}
close(sockfd);
return 0;
}
客户端:udp_client.cpp
#include "udpsocket.hpp"
#define CHECK_RET(q) if((q)==false){return -1;}
int main(){
UdpSocket sock;
CHECK_RET( sock.Socket());
while(1){
std:: string buf;
std::cout<<"clent say:";
std::cin>>buf;
CHECK_RET( sock.Send(buf,"192.168.86.3",9999) );
buf.clear();
CHECK_RET(sock.Recv(&buf,NULL,NULL) );
std:: cout<<"server say: "<<buf<<std::endl;
}
CHECK_RET(sock.Close());
return 0;
}
结果演示: 这里之启动了一个server端,也可同时启动多个server端。
|