Linux网络编程套接字-UDP传输

17 篇文章 0 订阅
6 篇文章 0 订阅

网络编程套接字

udp传输
客户端

1、创建套接字 socket()

2、为套接字绑定地址 bind()

3、发送数据(如果socket还没有绑定地址,这时候操作系统会选择一个合适的地址端口进行绑定)

4、接收数据

5、关闭套接字

服务端

1、创建套接字,通过创建套接字使进程与网卡建立联系,创建struct socket{…}

2、为套接字绑定地址信息

3、接收数据

4、发送数据

5、关闭套接字

创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domin: 地址域
	AF_INET             IPv4 Internet protocols          ip(7)
	AF_INET6            IPv6 Internet protocols          ipv6(7)
type:套接字类型
	SOCK_STREAM	流式套接字,默认协议TCP,不支持UDP
	SOCK_DGRAM	数据报套接字,默认协议UDP,不支持TCP
protocol:协议类型
	0:使用默认套接字协议
	6/IPPOTO_TCP	TCP协议
	17/IPPOTO_UDP	UDP协议
返回值:套接字操作句柄-文件描述符	失败返回-1
为套接字绑定地址
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
bind()函数应将本地套接字地址地址分配给由描述符套接字标识的套接字,该套接字没有分配本地套接字地址.使用socket()函数创建的套接字最初是未命名的;它们仅由其地址族标识。

socket:套接字文件描述符
sockaddr:地址信息
addrlen:地址信息长度

返回值:0	失败返回-1	
详细了解地址家族

在bind函数中我们看到了sockaddr这个关键的地址家族。通过man手册只能找到少量的描述。通过查找资料找到了一些,再此做出总结

一般我们使用struct sockaddrstruct sockaddr_in这两个结构体用来处理网络通信的地址。

struct sockaddr
{  
    unsigned short sa_family;    //2 
    char sa_data[14];     //14
};  
该结构体的缺陷为sa_data[]把目标地址和端口信息混在一起了
上面是通用的socket地址,具体到Internet socket,用下面的结构,二者可以进行类型转换
struct sockaddr_in
{
    sa_family_t		sin_family;//地址族
    uint16_t		sin_port;//16位TCP/UCP端口号
    struct in_addr	sin_addr;//32位IP地址
    char			sin_zero[8];//不使用
}
在这个结构体中存在另一个结构体,该结构体一般用来存放32位IP地址
struct in_addr
{
    In_addr_t		s_addr;//32位IPV4地址
}

sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

一般用法
int sockfd;  
struct sockaddr_in   my_addr;  //赋值时用这个结构
sockfd = socket(AF_INET,   SOCK_STREAM,   0);      
my_addr.sin_family   =   AF_INET;     
my_addr.sin_port   =   htons(MYPORT);     
my_addr.sin_addr.s_addr   =   inet_addr("192.168.0.1");     
bzero(&(my_addr.sin_zero),   8);         
bind(sockfd,   (struct   sockaddr   *)&my_addr,   sizeof(struct   sockaddr));
//用(struct   sockaddr   *)转换即满足要求

还有网络上另一个其他解释

struct sockaddr 是一个通用地址结构,这是为了统一地址结构的表示方法,统一接口函数,使不同的地址结构可以被bind() , connect() 等函数调用;struct sockaddr_in中的in 表示internet,就是网络地址,这只是我们比较常用的地址结构,属于AF_INET地址族,他非常的常用,以至于我们都开始讨论它与 struct sockaddr通用地址结构的区别。
接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:套接字文件描述符
buf:用buf存储接收的数据
len:想要接收的数据长度
flags:0---默认阻塞接收
src_addr:发送端地址信息
addrlen:地址信息长度,不但要指定想接收多长,还要保存实际接受了多长
返回值:实际接收的长度	失败返回-1
发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:套接字文件描述符
buf:要发送的数据
len:要发送的数据长度
flags:0---默认阻塞发送
dest_addr:目的段地址信息--标识数据要发送到哪里去
addrlen:地址信息长度
返回值:实际发送的长度	失败返回-1
关闭套接字
int close(int fd);
这个在Linux文件操作符中已经了解到了,文件描述符用完之后需要被关闭
UDP传输Demo
//UdpSocket.hpp
//实现以UdpSocket类封装udp常用操作 
#include <iostream>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
class UdpSocket{
    public:
        UdpSocket():_sock(-1){}
        ~UdpSocket(){}
        bool Socket() {
            _sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if (_sock < 0) {
                perror("socket error");
                return false;
            }
            return true;
        }
        bool Bind(string &ip, uint16_t port) {
            struct sockaddr_in addr;//定义一个结构体名称为addr,是socket地址
            //in 表示internet,就是网络地址
            //sin_family指代协议族,在socket编程中只能是AF_INET
            //sin_port存储端口号(使用网络字节顺序)
            //sin_addr存储IP地址,使用in_addr这个数据结构
            
            addr.sin_family = AF_INET;//ipv4
            //uint16_t htons(uint16_t hostshort);
            
            addr.sin_port = htons(port);//htons是将整型变量从主机字节顺序转变成网络字节顺序, 
                                //就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
            //in_addr_t inet_addr(const char *cp);
            
            addr.sin_addr.s_addr = inet_addr(ip.c_str());//inet_addr是一个计算机函数,
            //addr.sin_addr.s_addr = INADDR_ANY;
            //      功能是将一个点分十进制的IP转换成一个长整数型数(u_long类型)等同于inet_addr()。
            
            socklen_t len = sizeof(struct sockaddr_in);
            //int bind(int sockfd, struct sockaddr *addr, 
            //  socklen_t addrlen);
            
            int ret = bind(_sock, (struct sockaddr*)&addr, len);
            if (ret < 0) {
                perror("bind error");
                return false;
            }
            return true;
        }
        bool Recv(string &buf, struct sockaddr_in *saddr) {
            //ssize_t recvfrom(int sockfd, void *buf, size_t len, 
            //int flags, struct sockaddr *src_addr, socklen_t *addrlen);
            char tmp[1500] = {0};
            socklen_t len = sizeof(struct sockaddr_in);
            int ret = recvfrom(_sock, tmp, 1500, 0, 
                    (struct sockaddr*)saddr, &len);
            if (ret < 0) {
                perror("recvfrom error");
                return false;
            }
            buf.assign(tmp, ret);
            return true;
        }
        bool Send(string &buf, struct sockaddr_in *daddr){
            socklen_t len = sizeof(struct  sockaddr_in);
            int ret = sendto(_sock, buf.c_str(), buf.size(), 0, 
                    (struct sockaddr*)daddr, len);
            if (ret < 0) {
                perror("sendto error");
                return false;
            }
            return true;
        }
        bool Close() {
            close(_sock);
            _sock = -1;
        }
    private:
        int _sock;
};

在这之外,上面的头文件中还有几个其他接口函数,需要了解

htons()作用是将端口号由主机字节序转换为网络字节序的整数值。(host to net)

inet_addr()作用是将一个IP字符串转化为一个网络字节序的整数值,用于sockaddr_in.sin_addr.s_addr。

数字的字节序转换:

​ 主机字节序转换为网络字节序

uint32_t	htonl(uint32_t	hostlong)
uint16_t	htons(uint16_t	hostshort)

​ 网络字节序转换为主机字节序

uint32_t	ntohl(uint32_t	hostlong)
uint16_t	ntohs(uint16_t	hostshort)

将点分十进制ip地址转换为网络字节序ip地址:

in_addr inet_addr(const char *cp);
int inet_pton(int af,const char *src,void *dst)

将网络字节序IP地址转换为点分十进制IP地址:

const char *inet_ntop(int af,const void* src,char *dst,socklen_t size);
char *inet_ntoa(struct in_addr in);
udp服务端
#include "udpsocket.hpp"    
    
#define CHECK_RET(q) if((q) == false){return -1;}    
    
int main(int argc,char *argv[])    
{    
  if(argc != 3)    
  {    
    cout<<"./udp_server ip port:"<<endl;                                                                                
    return -1;    
  }    
  string ip = argv[1];    
  uint16_t port = atoi(argv[2]);    
    
  UdpSocket sock;    
  CHECK_RET(sock.Socket());    
  CHECK_RET(sock.Bind(ip,port));    
    
  while(1)    
  {    
    string buf;    
    struct sockaddr_in cli_addr;    
    CHECK_RET(sock.Recv(buf,&cli_addr));    
    cout<<"client say:"<<buf<<endl;    
    
    cout<<"server say:";    
    fflush(stdout);    
    cin>>buf;                               
    CHECK_RET(sock.Send(buf,&cli_addr));
  } 
  sock.Close();             
} 
udp客户端
#include "udpsocket.hpp"    
    
#define CHECK_RET(q) if((q) == false){return -1;}    
    
int main(int argc,char *argv[])    
{    
  if(argc != 3)    
  {    
    cout<<"./udp_client ip port"<<endl;                                                                                 
    return -1;    
  }    
  string ip = argv[1];    
  uint16_t port = atoi(argv[2]);    
    
  UdpSocket sock;    
  CHECK_RET(sock.Socket());    
    
  struct sockaddr_in srv_addr;    
  srv_addr.sin_family = AF_INET;    
  srv_addr.sin_port = htons(port);    
  srv_addr.sin_addr.s_addr = inet_addr(ip.c_str());    
    
  while(1)    
  {    
    string buf;    
    cout<<"client say:";    
    fflush(stdout);    
    cin>>buf;                         
    CHECK_RET(sock.Send(buf,&srv_addr));               
    CHECK_RET(sock.Recv(buf,&srv_addr));
    cout<<"server say"<<buf<<endl;    
  }                
  sock.Close();             
} 

网络1

两边聊天如上图

udp的传输速度快,无连接,不可靠,面向数据报这个必须牢牢记住了。

但是在代码中有个bug存在,发送的数据如果有空格,那么数据就会产生截断,分成两次发送

PS:2019.8.19更新,截断是因为cin或者scanf输入时遇到空格就停止,所以用getline(cin,buf)可以解决这个bug

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值