最近需要通过套接字传输JPEG图像及相关信息,初次接触套接字,这是一个总结性的文章。
套接字理论就不讲了,虽然没有接触过套接字编程,但是计算机网络课程中关于套接字和udp等理论还是知道的,而且也有很多专业的文章讲理论。
首先是在ubuntu20上面进行编程,为了方面调试需要一个网络调试助手能够接受或者发送信息,调试自己的接受或者发送代码是否正常,网络调试助手的安装详见:ubuntu20安装网络调试助手遇到缺少qt4相关库的问题
本程序是基于UDP的套接字编程。
本小节代码放在:CppSocket
我将接收和发送程序写成两个类,都存放在my_udp_socket.cpp中,首先看头文件内容:
#ifndef _MY_UDP_SOCKET_
#define _MY_UDP_SOCKET_
#include <sys/select.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
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 <sys/select.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <iostream>
#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); // 关闭套接字
}