🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈 本科在读菜鸡一枚,指出问题及时改正
文章目录
简单的回显服务器和客户端代码
Udp Server
socket套接字创建
cpp
#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已经被占用了。
创建套接字代码:
cpp
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地址、端口号绑定。
cpp
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
cpp
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
cpp
#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
cpp
#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 指向的结构的大小。这个参数在调用时需要正确设置,调用后该变量会被更新为实际的地址长度。
cpp
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地址绑定:
cpp
local.sin_addr.s_addr=INADDR_ANY;
UDP Client
和服务器有所不同, 客户端的进程很多,但是端口号只能和一个进程绑定,可能出现两个进程绑定同一个端口号,会出现冲突无法运行。为了解决这一问题,客户端的端口号一般不让用户设定,而是让客户端操作所在的操作系统随机选择一个端口号。客户端的端口号具体是多少不重要,只要能标记和别的进程不一样即可。
客户端需要绑定自己的IP地址和端口,但是不需要显示绑定自己的IP地址和端口。客户端在首次向服务器发送数据的时候,系统会自动给客户端绑定它自己的IP和端口。
cpp
#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
cpp
#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;
};
cpp
#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;
}
cpp
#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;
}