🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
文章目录
- Udp Server
- socket套接字创建
- 套接字和IP地址、端口号绑定
- 读取服务器套接字数据--recvfrom
- 发送数据--stndto
- 注意事项
- UDP Client
- 完整代码
- 实例测试
简单的回显服务器和客户端代码
Udp Server
socket套接字创建
#include<sys/types.h>
#include<sys/socket.h>int socket(int domain, int type, int protocol);
参数说明:
-
int domain
:指定协议族
AF_INET: IPv4 协议
AF_INET6: IPv6 协议
AF_UNIX: 本地通信(也称为 UNIX 域套接字)
-
int type
:指定套接字的类型
SOCK_STREAM: 提供可靠的、面向连接的字节流(TCP)
SOCK_DGRAM: 提供不可靠的、无连接的数据报(UDP)
SOCK_RAW: 提供原始套接字,允许直接访问网络层(通常用于网络监测或自定义协议)
-
int protocol
:指定所需的协议 -
返回值:成功时,socket 函数返回一个非负整数,代表新创建的套接字的文件描述符。这个文件描述符可以用于后续的套接字操作(如 bind、listen、accept 等)。
失败时,返回 -1,并设置 errno 来指示错误原因。
在UDP通信中,将前两个参数设置好之后,最后一个参数设置成0即可。
任何一个UDP服务通信中,都需要有一个int sockfd
的文件描述符,按照系统编程中所说,这里打印出来的文件描述符应该是3,因为0,1,2已经被占用了。
创建套接字代码:
void InitServer()
{//1.创建套接字 _sockfd=::socket(AF_INET,SOCK_DGRAM,0); //调用系统级的方法if(_sockfd<0){//通信不可能实现,直接退出LOG(FATAL,"socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG,"socket creat success, _sockfd:%d\n",_sockfd); //_socked=3
}
套接字和IP地址、端口号绑定
网络通信中,客户端和服务器需要有自己的IP地址和端口号,因此需要将套接字和IP地址、端口号绑定。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
void InitServer(){//1.创建套接字(文件) _sockfd=::socket(AF_INET,SOCK_DGRAM,0); //调用系统级的方法if(_sockfd<0){//通信不可能实现,直接退出LOG(FATAL,"socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG,"socket creat success, _sockfd:%d\n",_sockfd); //_socked=3//2.bind//(1)先填充本地信息struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_localport); //端口号需要从主机转换成网络序列local.sin_addr.s_addr=inet_addr(_localip.c_str()); //用户习惯的是字符串,比如"192.xxx.xxx.xxx"//但是网络中需要4字节ip,需要的是网络序列ip//也就是说这里需要将字符串转换成4字节和网络序列//(2)绑定int n=::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){//绑定失败,不会网络通信LOG(FATAL,"bind error\n");exit(BAND_ERROR);}//绑定成功LOG(DEBUG,"socket bind success\n");}
- 定义一个
struct sockaddr_in local;
结构体用于存储本地地址信息,该对象中有四个字段,如下:
需要对前三个字段进行设置,sin_family
的值和socket
函数中的domain
参数保持一致;sin_por
是端口信息,由于是在网络中通信,需要将主机转换成网络序列;local.sin_addr.s_addr=inet_addr(_localip.c_str())
是将ip地址从主机序列转换成网络序列,但是ip地址用户习惯于字符串形式,即“192.xxx.xxx.xxx”,需要转换成4字节,这里直接使用inet_addr()
函数即可。
读取服务器套接字数据–recvfrom
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数解释:
sockfd
套接字描述符buf
: 指向存储接收到数据的缓冲区的指针len
: 要接收的字节数,表示缓冲区的大小flags
: 接收选项的标志src_addr
: 可选参数,指向sockaddr
结构体的指针,用于存储发送方的地址信息。如果不需要该信息,可以传入NULL
addrlen
: 可选参数,指向一个socklen_t
类型的变量,表示src_addr
指向的结构的大小。调用后,该变量将被更新为实际的地址长度。
发送数据–stndto
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
dest_addr
:指向目标地址的指针,通常是 sockaddr 结构体的指针,表示数据将要发送到的地址。如果目标是 UDP 套接字,必须指定目标地址。addrlen
:指向一个 socklen_t 类型的变量,表示 dest_addr 指向的结构的大小。这个参数在调用时需要正确设置,调用后该变量会被更新为实际的地址长度。
void Start()
{_isrunning=true;char inbuffer[1024];while (_isrunning){struct sockaddr_in peer;socklen_t len=sizeof(peer);ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){inbuffer[n]=0;std::string echo_string="[udp_server echo] #";echo_string+=inbuffer;sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);}}}
注意事项
云服务器上禁止绑定自己的公网:
可以绑定内网,但是都不到信息,因为不会在公网公布:
在云服务上,绑定IP地址一般绑定为0,这样云服务器绑定了任意IP:
服务器端进程任意IP地址绑定:
local.sin_addr.s_addr=INADDR_ANY;
UDP Client
和服务器有所不同, 客户端的进程很多,但是端口号只能和一个进程绑定,可能出现两个进程绑定同一个端口号,会出现冲突无法运行。为了解决这一问题,客户端的端口号一般不让用户设定,而是让客户端操作所在的操作系统随机选择一个端口号。客户端的端口号具体是多少不重要,只要能标记和别的进程不一样即可。
客户端需要绑定自己的IP地址和端口,但是不需要显示绑定自己的IP地址和端口。客户端在首次向服务器发送数据的时候,系统会自动给客户端绑定它自己的IP和端口。
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 客户端需要先知道服务器ip地址和端口号int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// client 不需要显示绑定自己的IP和端口,但是需要绑定自己的IP和端口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());while (1){std::string line;std::cout << "Please Enter# ";std::getline(std::cin, line);int n = sendto(sockfd, line.c_str(), line.size(), 0, (sockaddr *)&server, sizeof(server));if (n > 0){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&temp, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{break;}}else{break;}}::close(sockfd);return 0;
}
完整代码
UdpServer.hpp
#pragma once#include<iostream>
#include<cstring>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"nocopy.hpp"
#include"Log.hpp"using namespace log_ns;static const int gsockfd=-1;
static const uint16_t glocalport=8888;enum
{SOCKET_ERROR=1,BAND_ERROR
};class UdpServer:public nocopy
{
public:UdpServer(uint16_t localport=glocalport):_sockfd(gsockfd),_localport(localport),_isrunning(false){}void InitServer(){//1.创建套接字(文件) _sockfd=::socket(AF_INET,SOCK_DGRAM,0); //调用系统级的方法if(_sockfd<0){//通信不可能实现,直接退出LOG(FATAL,"socket error\n");exit(SOCKET_ERROR);}LOG(DEBUG,"socket creat success, _sockfd:%d\n",_sockfd); //_socked=3//2.bind//(1)先填充本地信息struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_localport); //端口号需要从主机转换成网络序列/*local.sin_addr.s_addr=inet_addr(_localip.c_str()); //用户习惯的是字符串,比如"192.xxx.xxx.xxx"//但是网络中需要4字节ip,需要的是网络序列ip//也就是说这里需要将字符串转换成4字节和网络序列*/local.sin_addr.s_addr=INADDR_ANY;//(2)绑定int n=::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n<0){//绑定失败,不会网络通信LOG(FATAL,"bind error\n");exit(BAND_ERROR);}//绑定成功LOG(DEBUG,"socket bind success\n");}void Start(){_isrunning=true;char inbuffer[1024];while (_isrunning){struct sockaddr_in peer;socklen_t len=sizeof(peer);ssize_t n=recvfrom(_sockfd,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){inbuffer[n]=0;std::cout<<"client say# "<<inbuffer<<std::endl;std::string echo_string="[udp_server echo] #";echo_string+=inbuffer;sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&peer,len);}else{std::cout<<"recvfrom : error "<<std::endl;}}}~UdpServer(){if(_sockfd>gsockfd)::close(_sockfd);}private:int _sockfd; uint16_t _localport; //服务器的端口号bool _isrunning;
};
UdpServerMain.cc
#include"UdpServer.hpp"
#include<memory>int main(int argc, char *argv[])
{if (argc != 2){std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;exit(0);}uint16_t port = std::stoi(argv[1]);EnableScreen();std::unique_ptr<UdpServer> usvr=std::make_unique<UdpServer>(port); //C++14标准usvr->InitServer();usvr->Start();return 0;
}
UdpClientMain.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// 客户端需要先知道服务器ip地址和端口号int main(int argc, char *argv[])
{if (argc != 3){std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// client 不需要显示绑定自己的IP和端口,但是需要绑定自己的IP和端口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());while (1){std::string line;std::cout << "Please Enter# ";std::getline(std::cin, line);int n = sendto(sockfd, line.c_str(), line.size(), 0, (sockaddr *)&server, sizeof(server));if (n > 0){struct sockaddr_in temp;socklen_t len = sizeof(temp);char buffer[1024];int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&temp, &len);if (m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{break;}}else{break;}}::close(sockfd);return 0;
}