【Linux网络与网络编程】03.UDP Socket编程

一、UDP Socket编程接口

// 创建套接字
int socket(int domain, int type, int protocol);
// 参数:
//		domain:域(协议家族),这里使用 AF_INET 表示进行网络编程
//		type:网络通信传输的类型,这里选择 SOCK_DGRAM 表示是使用UDP协议的面向数据报传递信息
//		protocol:这个参数目前置0即可
// 返回值:成功则返回一个文件描述符(所以创建套接字的本质就是创建了一个文件);失败则返回-1,并设置对应的错误码// 填充网络信息并绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 参数:
//		sockfd:套接字文件描述符
//		addr:这是为了统一socket接口而定义的数据结构,其中的 sockaddr_in 是我们今天网络通信所要用到的,我们需要在绑定之前设置好它的协议家族,端口号和ip地址
//		addrlen:这是将addr的大小传递给内核,方便判断是哪种addr
// 返回值:成功返回0;失败则返回-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:接收消息的主机的addr(用于指定ip+端口号)
//		addrlen::接收消息的主机的addr的长度
// 返回值:成功返回发送的字节数;失败则返回-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:用以接收 发送消息的主机的addr(知道是哪个ip+端口号发送到信息)
//		addrlen:发送消息的主机的addr长度
//返回值:成功则返回成功接收的消息的字节数;失败则返回-1,并设置对应的错误码

二、UDP Socket编程

2.1 Echo Sever

这里我们编写一个简单的程序,当客户端向我发送数据后,服务端会将信息重新发回给客户端。

这里我们将ip和port的转换封装成一个类:

//InetAddr.hpp
#pragma once
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class InetAddr
{// 网络IP转主机IPvoid IPNet2Host(){// 这里的转化我们不使用inet_ntoa,因为他还是不太安全的char buffer[1024];::inet_ntop(AF_INET, &_in_addr.sin_addr.s_addr, buffer, sizeof(buffer) - 1);_ip = buffer;}// 网络端口号转主机端口号void PortNet2Host(){_port = ::ntohs(_in_addr.sin_port);}public:// 构造函数InetAddr() = default;InetAddr(const sockaddr_in &addr) : _in_addr(addr){IPNet2Host();PortNet2Host();}InetAddr(std::string ip, uint16_t port) : _ip(ip), _port(port){_in_addr.sin_family = AF_INET;_in_addr.sin_port = ::htons(_port);_in_addr.sin_addr.s_addr = ::inet_addr(ip.c_str());}InetAddr(uint16_t port) : _port(port){// 云服务器的公网IP不允许被绑定,但是虚拟机可以// 所以在绑定云服务器时只需要给定端口号即可,IP使用INADDR_ANY//在网络编程中,当一个进程需要绑定一个网络端口以进行通信时,可以使用INADDR_ANY 作为 IP 地址参数。这样做意味着该端口可以接受来自任何 IP 地址的连接请求,无论是本地主机还是远程主机。例如,如果服务器有多个网卡(每个网卡上有不同的 IP 地址),使用 INADDR_ANY 可以省去确定数据是从服务器上具体哪个网卡/IP 地址上面获取的。_in_addr.sin_family = AF_INET;_in_addr.sin_port = ::htons(_port);_in_addr.sin_addr.s_addr = INADDR_ANY; }std::string Ip() { return _ip; }uint16_t Port() { return _port; }struct sockaddr *NetAddr() { return (struct sockaddr *)&_in_addr; }socklen_t Len() { return sizeof(_in_addr); }~InetAddr() {}private:struct sockaddr_in _in_addr;std::string _ip;uint16_t _port;
};

接下来编写服务端代码:

//UDPSever.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogMudule;// 默认端口号
const static uint16_t defaultport = 8080;
// const static std::string defaultip = "127.0.0.1";//本地通信测试阶段用到的,网络通信不需要class UDPSever
{
public:UDPSever(uint16_t port = defaultport) : _in_addr(port), _isrunning(false){// 1. 创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";exit(1);}LOG(LogLevel::DEBUG) << "socket succeed";// 2. 绑定int n = ::bind(_sockfd, _in_addr.NetAddr(), _in_addr.Len());if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::DEBUG) << "bind succeed";}void Run(){_isrunning = true;char buff[1024];// 定义接收发送消息的主机的addr信息的结构struct sockaddr_in src_addr;socklen_t len;while (true){// 接收来自客户端的信息int n = ::recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&src_addr, &len);InetAddr cli(src_addr);if (n > 0){buff[n] = '\0';LOG(LogLevel::INFO) << cli.Ip() << ":" << cli.Port() << "Clint say#" << buff;std::string message = "Echo#";message += buff;// 将包装的信息发送给源主机::sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&src_addr, len);}}_isrunning = false;}~UDPSever(){close(_sockfd);}private:int _sockfd;InetAddr _in_addr; // 用InetAddr 来管理转化操作bool _isrunning;
};
// UDPSever.cc
#include <memory>
#include "UDPSever.hpp"
int main()
{std::unique_ptr<UDPSever> us_ptr = std::make_unique<UDPSever>();us_ptr->Run();return 0;
}

最后写客户端代码:

//UDPClient.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "InetAddr.hpp"class UDPClient
{
public:UDPClient(uint16_t port, std::string ip) : dst_addr({ip, port}){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){std::cerr << "socket error" << std::endl;exit(1);}// 2.绑定?// 不需要}void Run(){std::string message;while (true){std::cout << "Please Enter # ";std::getline(std::cin, message);// 向目标主机发送message::sendto(_sockfd, message.c_str(), message.size(), 0, dst_addr.NetAddr(), dst_addr.Len());// 用以接收由Sever发送回来的数据char buff[1024];sockaddr_in src_addr;socklen_t len;int n = ::recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&src_addr, &len);if (n > 0){buff[n] = '\0';std::cout << buff << std::endl;}}}~UDPClient(){close(_sockfd);}private:int _sockfd;InetAddr dst_addr; // 目标主机结构
};
//UDPClient.cc
#include <memory>
#include "UDPClient.hpp"//./UDPClient 127.0.0.1 8888
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage Error" << std::endl;exit(-1);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);std::unique_ptr<UDPClient> uc_ptr = std::make_unique<UDPClient>(port, ip);uc_ptr->Run();return 0;
}

这里怎么能够验证我们的程序编写是成功的呢?这里我们就来介绍几条命令:
ping : 用于检查本地主机到目标主机的网络是否可达,测量网络延迟,评估网络质量。

caryon@VM-24-10-ubuntu:~/linux/TCP_Socker/EchoServer$ ping -c5 62.234.18.77	
PING 62.234.18.77 (62.234.18.77) 56(84) bytes of data.
64 bytes from 62.234.18.77: icmp_seq=1 ttl=63 time=0.913 ms
64 bytes from 62.234.18.77: icmp_seq=2 ttl=63 time=0.993 ms
64 bytes from 62.234.18.77: icmp_seq=3 ttl=63 time=0.957 ms
64 bytes from 62.234.18.77: icmp_seq=4 ttl=63 time=0.951 ms
64 bytes from 62.234.18.77: icmp_seq=5 ttl=63 time=0.972 ms--- 62.234.18.77 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4009ms
rtt min/avg/max/mdev = 0.913/0.957/0.993/0.026 ms

netstat: 用于查看网络状态

常用选项:
• n 拒绝显示别名,能显示数字的全部转化成数字
• l 仅列出有在 Listen (监听) 的服务状态
• p 显示建立相关链接的程序名
• t (tcp)仅显示 tcp 相关选项
• u (udp)仅显示 udp 相关选项
• a (all)显示所有选项,默认不显示 LISTEN 相关

caryon@VM-24-10-ubuntu:~$ netstat -uap
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
udp        0      0 _localdnsproxy:domain   0.0.0.0:*                           -                   
udp        0      0 _localdnsstub:domain    0.0.0.0:*                           -                   
udp        0      0 VM-24-10-ubuntu:bootpc  0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:51398           0.0.0.0:*                           486728/./client_udp 
udp        0      0 localhost:323           0.0.0.0:*                           -                   
udp        0      0 0.0.0.0:8080            0.0.0.0:*                           486145/./sever_udp  
udp6       0      0 ip6-localhost:323       [::]:*                              -              

pidof: 查看服务器的进程 id

caryon@VM-24-10-ubuntu:~$ pidof sever_udp
486145

我们的客户端代码编写时留下了一个问题,为什么客户端不需要绑定端口号呢?
首先我们要明确,客户端也必须要有自己的ip和端口号,但是客户端是不需要自己显示调用bind的,因为一个端口号只能被绑定一次,客户端的端口号实在sendto时由操作系统自行绑定的。

2.2 Dictionary Sever

上面的Echo Sever没有让我们感受到网络服务的用处,这里我们使用一个Dictionary Sever来进行让网络通信完成一个小任务。

首先让我们看一下我们的小字典相关的文件:

# dictionary.txt
apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天
// Dictionary.hpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>const static std::string defaultfilepath="./";
const static std::string defaultfilename="dictionary.txt";
const static std::string defaultsep=": ";
class Dictionary
{//切割字符串bool SplitString(const std::string& line,std::string* key,std::string* value,const std::string& sep=defaultsep){auto pos=line.find(sep);if(pos==line.npos)  return false;*key=line.substr(0,pos);*value=line.substr(pos+sep.size());if(key->empty()||value->empty())    return false;return true;}//加载文件void Load(){//C++17文件操作std::string file=_filepath+_filename;std::ifstream in(file.c_str());if(!in.is_open()){std::cerr<<"file open failed"<<std::endl;exit(-1);}std::string line;while(std::getline(in,line)){//apple: 苹果std::string key;std::string value;if (SplitString(line, &key, &value)){ _dictionary.insert(std::make_pair(key, value));}}in.close();}
public:Dictionary(const std::string& filepath=defaultfilepath,const std::string& filename=defaultfilename):_filepath(filepath),_filename(filename){Load();}//翻译std::string translate(const std::string& str){auto posit=_dictionary.find(str);if(posit==_dictionary.end())    return "None";else return posit->second;}~Dictionary(){}
private:std::unordered_map<std::string,std::string> _dictionary;const std::string _filepath;const std::string _filename;
};

接下来我们改造一下我们的服务端:

// TCPSever.hpp
#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Log.hpp"
#include "InetAddr.hpp"using namespace LogMudule;const static uint16_t defaultport = 8888;
const static std::string defaultip = "62.234.18.77";// 功能模块
using func_t = std::function<std::string(const std::string &)>;class UDPSever
{
public:UDPSever(func_t func, uint16_t port = defaultport) : _func(func), _in_addr(port), _isrunning(false){// 1. 创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";exit(1);}LOG(LogLevel::DEBUG) << "socket succeed";// 2. 绑定int n = ::bind(_sockfd, _in_addr.NetAddr(), _in_addr.Len());if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::DEBUG) << "bind succeed";}void Run(){_isrunning = true;char buff[1024];// 定义接收发送消息的主机的addr信息的结构struct sockaddr_in src_addr;socklen_t len;while (true){// 接收来自客户端的信息int n = ::recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&src_addr, &len);InetAddr cli(src_addr);if (n > 0){buff[n]=0;// 将传来了词进行翻译string result=_func(buff);// 将翻译的结果发送给源主机InetAddr peer(src_addr);int n=::sendto(_sockfd, result.c_str(), result.size(), 0, peer.NetAddr(), peer.Len());}}_isrunning = false;}~UDPSever(){close(_sockfd);}private:int _sockfd;InetAddr _in_addr; // 用InetAddr 来管理转化操作bool _isrunning;func_t _func;
};

对应的也就需要我们的服务端需要将翻译模块传过去,我们这样的最大好处是减少了代码的耦合度,使得字典文件和服务端文件成功分离。

// UDPSever.cc
#include <memory>
#include "UDPSever.hpp"
#include "Dictionary.hpp"int main()
{Dictionary d;std::unique_ptr<UDPSever> us_ptr = std::make_unique<UDPSever>([&d](const std::string& string)->std::string{return d.translate(string);});us_ptr->Run();return 0;
}

至于客户端等文件并未修改。

2.3 Chat Sever

我们的目标是要设计出来一个mini的群聊,下图是我们的设计demo
在这里插入图片描述
接下来就是我们的设计。

首先我们需要对参与聊天的用户进行管理,所以我们基于观察者模式设计出了用户管理类。

//User.hpp
#pragma once
#include <algorithm>
#include <list>
#include <string>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "mutex.hpp"using namespace LogMudule;// 为了代码的可扩展性,让用户类来继承用户接口
class UserInterface
{
public:virtual ~UserInterface() = default;virtual void SendTo(int sockfd, std::string message) = 0;virtual bool operator==(const InetAddr &user) = 0;
};//用户类
class User : public UserInterface
{
public:User(const InetAddr &user) : _user(user){}void SendTo(int sockfd, std::string message) override{LOG(LogLevel::INFO) << "send message to " << _user.Addr() << "Info is :" << message;::sendto(sockfd, message.c_str(), message.size(), 0, _user.NetAddr(), _user.Len());}bool operator==(const InetAddr &user) override{return _user == user;}~User() override{}private:InetAddr _user;
};// 观察者模式编写用户管理
class UserManage
{
public:UserManage() {}// 添加在线用户void AddUser(InetAddr &user){// 多线程访问需要加锁_mutex.Lock();for (auto &u : _online_user){if (*u == user){LOG(LogLevel::INFO) << user.Addr() << " 用户已存在";//不释放会导致死锁_mutex.Unlock();return;}}_online_user.push_back(std::make_shared<User>(user));LOG(LogLevel::INFO) << "添加新用户"<<user.Addr();_mutex.Unlock();}// 轮询发送信息void Router(int sockfd, std::string message){_mutex.Lock();for (auto &u : _online_user){u->SendTo(sockfd, message);}_mutex.Unlock();}// 删除用户void DeleteUser(InetAddr &user){        // 不会删除只会将目标元素移动到末尾auto pos = std::remove_if(_online_user.begin(), _online_user.end(), [&user](std::shared_ptr<UserInterface>& u) -> bool{ return *u == user; });_online_user.erase(pos,_online_user.end());}~UserManage() {}private:std::list<std::shared_ptr<UserInterface>> _online_user;MutexModel::Mutex _mutex;
};

再接下来我们要将用户类和服务端耦合,我们要让线程池来替我们解决客户端发送的任务。

//sever_udp.hpp
#pragma once
#include <iostream>
#include <functional>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "InetAddr.hpp"
#include "ThreadPool.hpp"using namespace ThreadPoolModual;const static uint16_t defaultport = 8888;// 线程池的任务
using task_t = std::function<void()>;
// 添加用户
using adduser_t = std::function<void(InetAddr &user)>;
// 轮询任务
using route_t = std::function<void(int sockfd, std::string message)>;
// 删除用户
using deletuser_t = std::function<void(InetAddr &user)>;class UDPSever
{
public:UDPSever(uint16_t port = defaultport) : _in_addr(port), _isrunning(false){// 1. 创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";exit(1);}LOG(LogLevel::DEBUG) << "socket succeed";// 2. 绑定int n = ::bind(_sockfd, _in_addr.NetAddr(), _in_addr.Len());if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(2);}LOG(LogLevel::DEBUG) << "bind succeed";}// 需要注入的任务序列void Regester(adduser_t add_user, route_t route, deletuser_t deletuser){_add_user = add_user;_route = route;_delet_user = deletuser;}void Run(){_isrunning = true;char buff[1024];// 定义接收发送消息的主机的addr信息的结构struct sockaddr_in src_addr;socklen_t len = sizeof(src_addr);while (true){// 接收来自客户端的信息int n = ::recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&src_addr, &len);if (n > 0){InetAddr cli(src_addr);buff[n] = '\0';std::string message;if (strcmp(buff, "quit") == 0){_delet_user(cli);message=cli.Addr()+" 我走了,你们聊";}else{// 添加用户_add_user(cli);// 合成新的信息message = cli.Addr() + "say #" + buff;}// 需要执行的任务就是User.hpp中的Routertask_t task = std::bind(_route, _sockfd, message);// 让线程池进程调度轮转ThreadPool<task_t>::GetInstance()->Enqueue(task);}}_isrunning = false;}~UDPSever(){close(_sockfd);}private:int _sockfd;InetAddr _in_addr; // 用InetAddr 来管理转化操作bool _isrunning;adduser_t _add_user;route_t _route;deletuser_t _delet_user;
};
//sever_udp.cc
#include <memory>
#include "UDPSever.hpp"
#include "User.hpp"
int main()
{//用户管理模块std::shared_ptr<UserManage> um =std::make_shared<UserManage>();//网络服务模块std::unique_ptr<UDPSever> us_ptr = std::make_unique<UDPSever>();us_ptr->Regester([&um](InetAddr &user){ um->AddUser(user); },[&um](int sockfd, const std::string &message){ um->Router(sockfd, message);},[&um](InetAddr &user){ um->DeleteUser(user);});us_ptr->Run();return 0;
}

接下来在我们运行起来后会发现客户端只有输入一条消息才能看到其他人发送的消息,这是因为单线程的缘故,所以我们要将客户端的收发信息独立开来,这里使用多线程进程操作

//client.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include "signal.h"
#include "InetAddr.hpp"void *Receive(void *args)
{int sockfd=*(int*)args;while(true){// 用以接收由Sever发送回来的数据char buff[1024];sockaddr_in src_addr;socklen_t len;int n = ::recvfrom(sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&src_addr, &len);if (n > 0){buff[n] = '\0';std::cerr << buff << std::endl;}}return nullptr;
}class UDPClient
{public:UDPClient(uint16_t port, std::string ip) : dst_addr({ip, port}){// 1.创建套接字_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){std::cerr << "socket error" << std::endl;exit(1);}std::cout<<"socket succeed!"<<std::endl;// 2.绑定?// 不需要}void Run(){pthread_t pid;// 多线程执行任务,主线程发消息,新线程收消息pthread_create(&pid, nullptr, Receive, (void*)&_sockfd);std::string message;//const std::string online ="我来了呀!";//::sendto(_sockfd, online.c_str(), online.size(), 0, dst_addr.NetAddr(), dst_addr.Len());while (true){std::cout << "Please Enter # ";std::getline(std::cin, message);// 向目标主机发送message::sendto(_sockfd, message.c_str(), message.size(), 0, dst_addr.NetAddr(), dst_addr.Len());}}void Quit(int signo){::sendto(_sockfd, "quit", sizeof("quit"), 0, dst_addr.NetAddr(), dst_addr.Len());exit(0);}~UDPClient(){close(_sockfd);}private:int _sockfd;InetAddr dst_addr; // 目标主机结构
};
//client_udp.cc
#include <memory>
#include <functional>
#include "UDPClient.hpp"std::unique_ptr<UDPClient> uc_ptr ;
//./UDPClient 127.0.0.1 8888
int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage Error" << std::endl;exit(-1);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);uc_ptr = std::make_unique<UDPClient>(port, ip);signal(2,[](int signo){uc_ptr->Quit(signo);});uc_ptr->Run();return 0;
}

三、Windows 下的UDP Socket

接下来我们来看一下Windows下的UDP Socket接口及其代码,来进行一下跨平台通信的测试

#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
std::string serverip = "62.234.18.77"; // 填写你的云服务器 ip
uint16_t serverport = 8888; // 填写你的云服务开放的端口号
int main()
{WSADATA wsd;WSAStartup(MAKEWORD(2, 2), &wsd);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());SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == SOCKET_ERROR){std::cout << "socker error" << std::endl;return 1;}std::string message;char buffer[1024];while (true){std::cout << "Please Enter@ ";std::getline(std::cin, message);if (message.empty()) continue;sendto(sockfd, message.c_str(), (int)message.size(), 0,(struct sockaddr*)&server, sizeof(server));struct sockaddr_in temp;int len = sizeof(temp);int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if (s > 0){buffer[s] = 0;std::cout << buffer << std::endl;}}closesocket(sockfd);WSACleanup();return 0;
}

最后附上windows linux 通信结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/900131.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux gcc

一、常用编译选项 ​基本编译 gcc [input].c -o [output] ​示例&#xff1a; gcc hello.c -o hello # 将 hello.c 编译为可执行文件 hello ./hello # 运行程序 ​分步编译 预处理&#xff1a;-E&#xff08;生成 .i 文件&#xff09; gcc -E hello.c -o hello…

若依框架二次开发——RuoYi-AI 集成本地大模型

文章目录 前提条件1. RuoYi-AI 已成功部署并运行2. Ollama 本地大模型已安装1. 配置本地大模型2. 切换至本地模型3. 开始对话总结本文将详细介绍如何在 RuoYi-AI 中集成本地 Ollama 大模型,使系统能够在 离线环境 下提供智能对话能力。 前提条件 在开始集成本地大模型之前,…

Flask学习笔记 - 模板渲染

Flask 模板渲染 模板是包含占位符的 HTML 文件 Flask 使用 Jinja2 模板引擎来处理模板渲染。模板渲染允许你将动态内容插入到 HTML 页面中&#xff0c;使得应用能够生成动态的网页内容。 创建模板&#xff1a;将 HTML 文件放在 templates 文件夹中&#xff0c;使用 Jinja2 占…

解码 from XXX import * - 导入的真相

文章目录 前言一、 什么是 from XXX import *?二、基本用法:导入的实际效果三、默认行为:无 __all__ 的情况四、与直接运行 XXX.py 的对比示例模块使用 from XXX import *直接运行 python example.py关键差异五、为什么需要注意 from XXX import *?最佳实践六、实际应用场景…

JavaScript 中常见的鼠标事件及应用

JavaScript 中常见的鼠标事件及应用 在 JavaScript 中&#xff0c;鼠标事件是用户与网页进行交互的重要方式&#xff0c;通过监听这些事件&#xff0c;开发者可以实现各种交互效果&#xff0c;如点击、悬停、拖动等。 在 JavaScript 中&#xff0c;鼠标事件类型多样&#xff0…

Nacos注册中心AP模式核心源码分析(单机模式)

文章目录 概述一、客户端启动主线流程源码分析1.1、客户端与Spring Boot整合1.2、注册实例&#xff08;服务注册&#xff09;1.3、发送心跳1.4、拉取服务端实例列表&#xff08;服务发现&#xff09; 二、服务端接收请求主线流程源码分析2.1、接收注册请求2.1.1、初始化注册表2…

prism WPF 模块

模块 DLL ModuleA 和 ModuleB 都要安装 Prism.Unity 引用方式1 项目引用 直接 在引用中添加项目引用 App.xaml.cs 添加模块 ConfigureModuleCatalog using ModuleA; using ModuleB; using Prism.Ioc; using Prism.Modularity; using Prism.Unity; using PrismWpfApp.ViewMo…

CSS:换行与不换行

一、CSS 不允许换行 在 CSS 中&#xff0c;有几种方法可以控制文本不换行&#xff1a; 1. 使用 white-space 属性 .no-wrap {white-space: nowrap; } white-space: nowrap; 会强制文本在一行显示&#xff0c;不换行。 2. 使用 overflow 和 text-overflow 通常与 white-sp…

JavaScript BOM、事件循环

目录 BOM&#xff08;浏览器对象模型&#xff09; 一、window 对象 1. 窗口控制 2. 定时器 二、location 对象 三、navigator 对象 四、history 对象 五、screen 对象 六、本地存储 1. localStorage 2. sessionStorage 七、BOM 应用场景 八、总结 JavaScript 执行…

k8s运维面试总结(持续更新)

一、你使用的promethues监控pod的哪些指标&#xff1f; CPU使用率 内存使用率 网络吞吐量 磁盘I/O 资源限制和配额&#xff1a;Prometheus可以监控Pod的资源请求和限制&#xff0c;确保它们符合预设的配额&#xff0c;防止资源过度使用。具体指标如container_spec_cpu_quota用于…

ubuntu20.04升级成ubuntu22.04

命令行 sudo do-release-upgrade 我是按提示输入y确认操作&#xff0c;也可以遇到配置文件冲突时建议选择N保留当前配置

Cortex-M​ 函数调用的入栈与出栈操作

在 ARM Cortex-M 系列单片机中,普通C函数调用的入栈(压栈)和出栈操作通常由编译器编译后生成的代码管理,而硬件仅负责部分关键操作。以下是详细分析: 1. 函数调用与返回的核心机制 (1) 硬件自动完成的部分 返回地址的保存: 当通过 BL(Branch with Link)或 BLX 指令调用…

DeepSeek能否用于对话系统(Chatbot)?技术解析与应用实例!

引言&#xff1a;Chatbot 的进化与挑战 你有没有发现&#xff0c;现在的AI聊天机器人越来越聪明了&#xff1f;无论是客服助手、智能语音设备&#xff0c;还是社交媒体上的自动回复&#xff0c;Chatbot&#xff08;对话系统&#xff09;已经渗透到我们生活的方方面面。但问题是…

多表查询的多与一

1.查寻表需要的条件 1.1.首先我们要了解查询表有哪些 1.1.1.多对一 多对一就是一个年表拥有例外一个表的多条数据 一个表对应立一个表的多条数据&#xff0c;另一个表对应这个表的多条数据 这个点被称为多对一 1.1.2.多对多 多对多简单来说就是需要一个中间商 中间商就…

配置文件、Spring日志

SpringBoot配置⽂件 SpringBoot⽀持并定义了配置⽂件的格式, 也在另⼀个层⾯达到了规范其他框架集成到SpringBoot的 ⽬的. 很多项⽬或者框架的配置信息也放在配置⽂件 中, ⽐如: 项⽬的启动端⼝ 数据库的连接信息(包含⽤⼾名和密码的设置) 第三⽅系统的调⽤密钥等信息 ⽤…

嵌入式——Linux系统的使用以及编程练习

目录 一、Linux的进程、线程概念 &#xff08;一&#xff09;命令控制进程 1、命令查看各进程的编号pid 2、命令终止一个进程pid 二、初识Linux系统的虚拟机内存管理 &#xff08;一&#xff09;虚拟机内存管理 &#xff08;二&#xff09;与STM32内存管理对比 三、Lin…

Springcache+xxljob实现定时刷新缓存

目录 SpringCache详解 SpringCache概述 核心原理 接口抽象与多态 AOP动态代理 核心注解以及使用 公共属性 cacheNames KeyGenerator&#xff1a;key生成器 key condition&#xff1a;缓存的条件&#xff0c;对入参进行判断 注解 xxl-job详解 SpringcacheRedis实现…

前端Uniapp接入UviewPlus详细教程!!!

相信大家在引入UviewPlusUI时遇到很头疼的问题&#xff0c;那就是明明自己是按照官网教程一步一步的走&#xff0c;为什么到处都是bug呢&#xff1f;今天我一定要把这个让人头疼的问题解决了&#xff01; 1.查看插件市场 重点&#xff1a; 我们打开Dcloud插件市场搜素uviewPl…

vector的介绍与代码演示

由于以后我们写OJ题时会经常使用到vector&#xff0c;所以我们必不可缺的是熟悉它的各个接口。来为我们未来作铺垫。 首先&#xff0c;我们了解一下&#xff1a; https://cplusplus.com/reference/vector/ vector的概念&#xff1a; 1. vector是表示可变大小数组的序列容器…

ZLMediaKit 源码分析——[5] ZLToolKit 中EventPoller之延时任务处理

系列文章目录 第一篇 基于SRS 的 WebRTC 环境搭建 第二篇 基于SRS 实现RTSP接入与WebRTC播放 第三篇 centos下基于ZLMediaKit 的WebRTC 环境搭建 第四篇 WebRTC学习一&#xff1a;获取音频和视频设备 第五篇 WebRTC学习二&#xff1a;WebRTC音视频数据采集 第六篇 WebRTC学习三…