C++套接字编程

最近需要通过套接字传输JPEG图像及相关信息,初次接触套接字,这是一个总结性的文章。

套接字理论就不讲了,虽然没有接触过套接字编程,但是计算机网络课程中关于套接字和udp等理论还是知道的,而且也有很多专业的文章讲理论。

1、环境说明

首先是在ubuntu20上面进行编程,为了方面调试需要一个网络调试助手能够接受或者发送信息,调试自己的接受或者发送代码是否正常,网络调试助手的安装详见:ubuntu20安装网络调试助手遇到缺少qt4相关库的问题

本程序是基于UDP的套接字编程。

2、发送字符串功能的代码解析

本小节代码放在:CppSocket

我将接收和发送程序写成两个类,都存放在my_udp_socket.cpp中,首先看头文件内容:

#ifndef _MY_UDP_SOCKET_
#define _MY_UDP_SOCKET_

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

// 一个接受消息的UDP套接字类
class MyUdpReceiveSocket
{
public:
    int sockfd;      // 套接字对象

    struct sockaddr_in addr;      // 存放地址的结构体
    MyUdpReceiveSocket(string _ip, uint16_t _port);   // 构造函数,用于创建套接字和绑定到网络接口
    void receive();                  // 接受消息的函数
    ~MyUdpReceiveSocket();           // 析构函数,删除套接字对象
};

// 一个发送消息的UDP套接字类
class MyUdpSendSocket
{
public:
    int sockfd;

    struct sockaddr_in self_addr, dst_addr;
    MyUdpSendSocket(string self_ip, uint16_t self_port);
    void send(string dst_ip, uint16_t dst_port, char *information);
    ~MyUdpSendSocket();
};
#endif

可以看到无论是接受还是发送的套接字,流程就是三步:1)创建套接字绑定到网络接口;2)循环接收或者发送消息;3)最后删除套接字对象

具体实现代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "my_udp_socket.h"

MyUdpReceiveSocket::MyUdpReceiveSocket(string _ip, uint16_t _port)
{	
	//这里创建一个套接字,IF_INET为ipv4,SOCK_DGRAM是不连接,和UDP是好搭档
	// 后面的0应该填udp,但是使用了SOCK_DGRAM意味着你会使用UDP,所以填0就是自动适配了
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
    if (-1 == sockfd) // 创建失败则返回-1
    {
        puts("Failed to create socket");
        return;
    }

    std::cout << "socket创建成功: " << _ip << ":" << _port << std::endl;

	//addr在头文件声明了,是一个sockaddr_in的结构体,存放ip等地址
    memset(&addr, 0, sizeof(addr));                // 初始化清空,将addr前sizeof(addr)个字节设置为0
    addr.sin_family = AF_INET;                     // 使用 IPV4
    addr.sin_port = htons(_port);                  // 设置端口
    addr.sin_addr.s_addr = inet_addr(_ip.c_str()); // 参数为char *,c_str()是将cpp的string转为c的字符串即char *,指向字符串首地址

    // 将套接字绑定到接口,套接字只是应用层,必须依靠低层传输信息
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        printf("Failed to bind socket on port %d\n", _port);
        close(sockfd);
        return;
    }
}

void MyUdpReceiveSocket::receive()
{
    char buffer[2048];                 // 定义用于接受消息的变量,大小设置为2048
    memset(buffer, 0, sizeof(buffer)); // 将buffer设置为0,即完成清理的操作

    int counter = 0;  // 用于计数收到多少次消息
    while (1)
    {
        struct sockaddr_in client_addr;
        socklen_t src_len = sizeof(client_addr);
        memset(&client_addr, 0, sizeof(client_addr));

        // 接收消息,上面如果超时设置,在指定时间内阻塞等待,否则timeout
        // client_addr是储存消息来源设备的地址信息
        int sz = recvfrom(sockfd, buffer, 2048, 0, (sockaddr *)&client_addr, &src_len);
        if (sz > 0) // 如果接收成功
        {
            buffer[sz] = 0; // 将成功或失败标志位重置
            printf("Get Message %d: %s\n", counter++, buffer);
        }
    }
}

MyUdpReceiveSocket::~MyUdpReceiveSocket()
{
    close(sockfd); // 关闭套接字
}

MyUdpSendSocket::MyUdpSendSocket(string self_ip, uint16_t self_port)
{
    // 创建socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        puts("Failed to create socket");
        // return;
    }

    // 设置地址与端口
    socklen_t addr_len = sizeof(self_addr);

    memset(&self_addr, 0, sizeof(self_addr));
    self_addr.sin_family = AF_INET;        // Use IPV4
    self_addr.sin_port = htons(self_port); //
    self_addr.sin_addr.s_addr = inet_addr(self_ip.c_str());

    // 超时设置
    // struct timeval tv;
    // tv.tv_sec  = 0;
    // tv.tv_usec = 200000;  // 200 ms
    // setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));

    // 绑定获取数据的端口,作为发送方,不绑定也行
    if (bind(sockfd, (struct sockaddr *)&self_addr, addr_len) == -1)
    {
        printf("Failed to bind socket on port %d\n", self_port);
        close(sockfd);
        // return;
    }
}

void MyUdpSendSocket::send(string dst_ip, uint16_t dst_port, char *information)
{
	// 发送端除了需要将自身的ip和port绑定bind到本机接口
	// 也需要定义对方的地址即dst_addr
    dst_addr.sin_family = AF_INET;
    dst_addr.sin_port = htons(dst_port);
    dst_addr.sin_addr.s_addr = inet_addr(dst_ip.c_str());

    socklen_t addr_len = sizeof(dst_addr);
    // 发送函数
    int ret = sendto(sockfd, information, sizeof(information), 0, (sockaddr *)&dst_addr, addr_len);
    if (ret < 0)
    {   // 失败返回-1
        cout << "send failed!" << endl;
    }
    cout << "send success from local to "<<dst_ip<<":"<<std::to_string(dst_port) << endl;
}

MyUdpSendSocket::~MyUdpSendSocket()
{
    close(sockfd); // 关闭套接字
}

你可能感兴趣的:(c++)