欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > linux socket编程之udp(实现客户端和服务端消息的发送和接收)

linux socket编程之udp(实现客户端和服务端消息的发送和接收)

2025/4/24 2:25:28 来源:https://blog.csdn.net/qq_73955920/article/details/147351228  浏览:    关键词:linux socket编程之udp(实现客户端和服务端消息的发送和接收)

目录

一.创建socket套接字(服务器端)

二.bind将prot与端口号进行绑定(服务器端)

2.1填充sockaddr_in结构

2.2bind绑定端口

三.直接通信(服务器端)

3.1接收客户端发送的消息

3.2给客户端发送消息

四.客户端通信

4.1创建socket套接字

4.2客户端bind问题

4.3直接通信即可

4.3.1构建目标主机的socket信息

4.3.2给服务端发送消息

4.3.3.接收服务端发送过来的消息

五.效果展示

5.1使用127.0.0.1本地环回测试

5.2使用公网ip测试

六.代码演示

6.1UdpServer.hpp

6.2UdpClient.cc

6.3InetAddr.hpp

6.4LockGuard.hpp

6.5Log.hpp

6.6main.cc

6.7makefile


一.创建socket套接字(服务器端)

int socket(int domain, int type, int protocol);

domain:选择你要使用的网络层协议 一般是ipv4,也就是AF_INET

type:选择你要使用的应用层协议,这里我们选择udp,也就是SOCK_DGRAM

protocol:这里我们先设置成0

成功返回文件描述符,失败返回-1

//1.创建socket套接字
_socketfd = socket(AF_INET,SOCK_DGRAM,0);
if(_socketfd < 0)
{LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);
}

二.bind将prot与端口号进行绑定(服务器端)

2.1填充sockaddr_in结构

uint16_t htons(uint16_t hostshort);//将端口号从主机序列转成网络序列
in_addr_t inet_addr(const char *cp);//将ip从主机序列转成网络序列 + 字符串风格ip转成点分十进制ip
uint16_t ntohs(uint16_t netshort);//将端口号从网络序列转成主机序列
char *inet_ntoa(struct in_addr in);//将ip从网络序列转成主机序列 + 点分十进制ip转成字符串风格ip

网络通信:struct sockaddr_in

本地通信:sockaddr_un

16位地址类型表明了他们是网络通信还是本地通信 

16位地址类型:sin_family

16位端口号:sin_port

32位ip地址:sin_addr.s_addr

//填充sockaddr_in结构
struct sockaddr_in local;
local.sin_family = AF_INET;//表明是网络通信
local.sin_port = htons(_prot);//将主机序列转成网络序列
local.sin_addr.s_addr = inet_addr("0.0.0.0");//将字符串类型的点分十进制ip转成四字节ip,并转成网络序列

2.2bind绑定端口

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockfd:要绑定的socket套接字的文件描述符

struct sockaddr *:包含ip地址+端口号的结构体(类型不一样需要进行强转)

socklen_t addrlen:sockaddr_in结构体的大小

//bind绑定端口号
int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要将sockaddr_in强转成sockaddr
if(n < 0)
{LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);
}

三.直接通信(服务器端)

3.1接收客户端发送的消息

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

sockfd:套接字的文件描述符

buf:缓存区

len:缓存区的大小,单位是字节

flags:暂时设置为0

src_addr:数据来自于哪                                

addrlen:struct sockaddr结构体的大小

//接收客户端发送来的消息
char buffer[1024];
sockaddr_in peer;
socklen_t len = sizeof(peer);
int n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(sockaddr*)&peer,&len);  

3.2给客户端发送消息

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

src_addr:要将数据发送给谁

addrlen:struct sockaddr结构体的大小

//处理客户端发送的消息,并且将结果返回给客户端
buffer[n] = 0;
sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);

四.客户端通信

4.1创建socket套接字

// 1. 创建socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0)
{std::cerr << "socket error" << std::endl;
}

4.2客户端bind问题

客户端不需要显示的bind,os会自动帮你绑定端口号!!!

试想一下,你的手机上有抖音和微信两个客户端小程序,如果抖音客户端bind了8080这个端口,微信也想要bind8080这个端口,那么这时候就会出现一个问题,一个端口号被两个进程竞争!!!结果就是,抖音和微信不可能同时启动。

所以解决方法就是:udp client首次发送数据的时候,OS会自己自动随机的给client进行bind

4.3直接通信即可

4.3.1构建目标主机的socket信息

struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport);
server.sin_addr.s_addr = inet_addr(serverip.c_str());

4.3.2给服务端发送消息

//给服务端发送消息
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

4.3.3.接收服务端发送过来的消息

//接收服务端发送过来的消息
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
char buffer[1024];
ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
if(n > 0)
{buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;
}

五.效果展示

5.1使用127.0.0.1本地环回测试

5.2使用公网ip测试

六.代码演示

6.1UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"enum 
{SOCKET_ERROR = 1,//创建套接字失败BIND_ERROR,//bind绑定端口失败USAGE_ERROR//启动udp服务失败
};int default_socketfd = -1;
class UdpServer
{
public:UdpServer(uint16_t prot): _socketfd(default_socketfd),_prot(prot){}   ~UdpServer(){}void Init(){//1.创建socket套接字_socketfd = socket(AF_INET,SOCK_DGRAM,0);if(_socketfd < 0){LOG(FATAL, "SOCKET ERROR, %s, %d\n", strerror(errno), errno);exit(SOCKET_ERROR);}LOG(INFO, "socket create success, sockfd: %d\n", _socketfd);//2.bind 将socket套接字和端口号进行绑定//填充sockaddr_in结构struct sockaddr_in local;local.sin_family = AF_INET;//表明是网络通信local.sin_port = htons(_prot);//将主机序列转成网络序列local.sin_addr.s_addr = inet_addr("0.0.0.0");//将字符串类型的点分十进制ip转成四字节ip,并转成网络序列//local.sin_addr.s_addr = INADDR_ANY;//bind绑定端口号int n = bind(_socketfd,(sockaddr *)&local,sizeof(local));//需要将sockaddr_in强转成sockaddrif(n < 0){LOG(FATAL, "BIND ERROR, %s, %d\n", strerror(errno), errno);exit(BIND_ERROR);}LOG(INFO, "socket bind success\n");}void Stat(){//3.直接开始通信while(true){//接收客户端发送来的消息char buffer[1024];struct sockaddr_in peer;socklen_t len = sizeof(peer);           ssize_t n = recvfrom(_socketfd,buffer,sizeof(buffer) - 1,0,(struct sockaddr*)&peer,&len);  if(n > 0){InetAddr addr(peer);LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);//处理客户端发送的消息,并且将结果返回给客户端buffer[n] = 0;sendto(_socketfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);}}}private:uint16_t _prot;int _socketfd;
};

6.2UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}// ./udpclient serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);// 1. 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){std::cerr << "socket error" << std::endl;}// 2. client要不要bind?一定要,client也要有自己的IP和PORT。要不要显式[和server一样用bind函数]的bind?不能!不建议!!// a. 如何bind呢?udp client首次发送数据的时候,OS会自己自动随机的给client进行bind --- 为什么?防止client port冲突。要bind,必然要和port关联!// b. 什么时候bind呢?首次发送数据的时候// 构建目标主机的socket信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());std::string message;// 2. 直接通信即可while(true){//给服务端发送消息std::cout << "Please Enter# ";std::getline(std::cin, message);std::cout<<message<<std::endl;sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));//接收服务端发送过来的消息struct sockaddr_in peer;socklen_t len = sizeof(peer);char buffer[1024];ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);if(n > 0){buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}}return 0;
}

6.3InetAddr.hpp

#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void GetAddress(std::string *ip, uint16_t *port){*port = ntohs(_addr.sin_port);*ip = inet_ntoa(_addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr) : _addr(addr){GetAddress(&_ip, &_port);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};

6.4LockGuard.hpp

#include <iostream>
#include <pthread.h>class LockGuard
{
public:LockGuard(pthread_mutex_t *mutex):_mutex(mutex){pthread_mutex_lock(_mutex); // 构造加锁}~LockGuard(){pthread_mutex_unlock(_mutex);// 析构释放锁}
private:pthread_mutex_t *_mutex;
};

6.5Log.hpp

#pragma once
#include <cstdio>
#include <iostream>
#include <string>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <fstream>#include "LockGuard.hpp"bool IsSave = false;//是否向文件中写入
const std::string logname = "log.txt";//日志信息写入的文件路径// 日志是有等级的
enum Level
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};// 将日志的登记由整形转换为字符串
std::string LevelToString(int level)
{switch (level){case DEBUG:return "Debug";case INFO:return "Info";case WARNING:return "Warning";case ERROR:return "Error";case FATAL:return "Fatal";default:return "Unknown";}
}// 获取时间
std::string GetTimeString()
{time_t curr_time = time(nullptr);struct tm *format_time = localtime(&curr_time);if (format_time == nullptr)return "None"; // 没有获取成功char time_buffer[1024];snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",format_time->tm_year + 1900, // 这里的year是减去1900之后的值,需要加上1900format_time->tm_mon + 1,     // 这里的mon是介于0-11之间的,需要加上1format_time->tm_mday,format_time->tm_hour,format_time->tm_min,format_time->tm_sec);return time_buffer;
}//将日志信息写入到文件中
void SaveFile(const std::string &filename, const std::string &message)
{std::ofstream out(filename, std::ios::app);if (!out.is_open()){return;}out << message;out.close();
}pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义锁 支持多线程
// 日志是有格式的
// 日志等级 时间 代码所在的文件名/行数 日志的内容
void LogMessage(std::string filename, int line, bool issave, int level, const char *format, ...)
{std::string levelstr = LevelToString(level);std::string timestr = GetTimeString();pid_t selfid = getpid();char buffer[1024];va_list arg;//定义一个void* 指针va_start(arg, format);//初始化指针,将指针指向可变参数列表开始的位置vsnprintf(buffer, sizeof(buffer), format, arg);//将可变参数列表写入到buffer中va_end(arg);//将指针置空std::string message = "[" + timestr + "]" + "[" + levelstr + "]" +"[" + std::to_string(selfid) + "]" +"[" + filename + "]" + "[" + std::to_string(line) + "] " + buffer + "\n";LockGuard lockguard(&lock);if (!issave){std::cout << message;//将日志信息打印到显示器中}else{SaveFile(logname, message);//将日志信息写入到文件}
}// C99新特性__VA_ARGS__
#define LOG(level, format, ...)  do{ LogMessage(__FILE__, __LINE__,IsSave,level, format, ##__VA_ARGS__); }while(0)
#define EnableFile()    do{ IsSave = true; }while(0)
#define EnableScreen()  do{ IsSave = false; }while(0) 

6.6main.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"void Usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " local_port\n" << std::endl;
}// ./udpserver port
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(USAGE_ERROR);}uint16_t port = std::stoi(argv[1]);std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port);usvr->Init();usvr->Stat();return 0;
}

6.7makefile

.PHONY:all
all:udpserver udpclient
udpclient:UdpClient.ccg++ -o udpclient UdpClient.cc -std=c++14
udpserver:main.ccg++ -o udpserver main.cc -std=c++14
.PHONY:clean
clean:rm -f udpserver

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词