1.IP地址+端口号port
要想进行网络间的通信,首先要找到进行通信的机器和机器上要通信的软件也就是机器上要进行通信的进程。
a.每台主机都有自己唯一的ip地址,如ipv4由4字节,32比特位构成。能在大多时候保证在全网的唯一性来确保我们找到需要进行通信的主机。
b.而主机上有许多不同的进程,可以通过端口号来找到需要进行网络通信的进程。
对于一台主机上的进程,端口号具有唯一性。
c.但是通过pid来标识进程同样具有唯一性,端口号被创建的意义是什么?
第一,不是所有进程都需要进行网络间的通信,但是所有进程都要有pid
第二,pid是系统功能中用于对进程进行管理的,若将pid用于网络通信功能,会增加网络通信和进程管理的耦合度,而我们追求低耦合,便于更新和维护。
因此创建端口号,使进程管理功能和网络通信功能解耦。
相关事项
1.用户使用ip时,一般为字符串形式,如:"192.168.20.1"
我们需要将其转为uint36_t类型。
可同系统提供的库函数来进行快速转换:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char* cp) ----将字符类型转换为32位长整型,in_addr_t就是uint36_t
2.客户端向服务端发送信息时需要知道服务端的端口号,这个端口号一般是公开且确定的,在发生信息的同时要将自己的端口号发送给服务端。来保证服务端也能将处理好后的消息发送给客户端。
2.socket套接字
要想将两个主机进行通信,就需要两个主机能看到同一份资源,这份资源就是socket套接字。
套接字编程具有几种不同的种类,用于不同情景下的网络通信。
对于这几种不同的套接字,通过将网络接口的抽象化来实现同一接口处理不同类型。
通过将所创建的不同种类的套接字传给接口时,接口内部自动对套接字进行识别来判断,从而提供不同的处理方案。
a.套接字的创建:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type, int protocol);
domain参数是将来创建套接字的域,如:AF_INET6表示ipv6也可以写成PF_INET6
type参数是套接字的类型,如:面向字节流,数据报(SOCK_DGRAM)
protocol参数是协议类型,暂时不考虑,传0
创建成功返回套接字的描述符,失败返回-1
b.绑定套接字
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd参数是创建套接字函数的返回值。
addr参数是套接字的通用接口,我们用的是sockaddr_in类型变量,使用时需要强转为通用类型
addrlen参数是套接字的长度
要想进行绑定,需要先创建一个socket,也就是struct sockaddr类型的结构体(我们用其中的sockaddr_in),并为其成员变量赋值。
如下所示:
struct sockaddr_in
{
sin_family;-----套接字的域
sin_port; -----套接字的端口号(uint16_t)
sin_addr; -----套接字用来存储ip的结构体
}
struct sin_addr
{
uint36_t s_addr;---用于存储ip地址
}
注意事项:
1.在初始化端口号时需要注意使用大于1024的端口号进行初始化,因为一般【0-1023】之间的端口号都是系统内定的端口号,一般都有固定的应用层协议使用。
2.绑定ip时使用默认的0.0.0.0,表示任意地址绑定,意思是凡是发给此机器的报文都根据端口号向上传递。因为,每台机器可能不止一个ip,若绑定一个则只能收到发送给固定ip的报文。
3.开始逐步进行网络通信的构建
a.要进行通信就要先创建通信对象,客户端和服务端
1.构建客户端
创建udpserver.hpp文件:
#pragma once
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unistd.h>
#include <errno.h>
typedef std::function <std::string(const std::string& )> func_t; //回调函数,用于处理服务端接收到的数据
uint16_t defaultport=8080; //设置的默认端口号
std::string defaultip="0.0.0.0"; //设置的默认ip
const int size=1024; //用去从网络中读取数据的缓冲区的默认大小
class UdpServer{
public:
UdpServer(const uint16_t &port=defaultport,const std::string &ip=defaultip):sockfd_(0),port_(port),ip_(ip),isrunning_(false)
{}
void Init()//服务端初始化,创建套接字,并对套接字进行绑定
{
//1.先进行udp套接字的创建
sockfd_=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd_<0)
{
std::cout<<"socket error"<<std::endl;
exit(0);
}
else
{
std::cout<<"socket creat success"<<std::endl;
}
//2.绑定套接字,先创建一个栈上的套接字,并对其相关成员变量赋值
struct sockaddr_in local;
bzero(&local,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(port_);//端口号和ip都需要转换成网络序列的存储方式
local.sin_addr.s_addr=htonl(INADDR_ANY);//inet_addr是一个库函数,作用是将一个char类型ipv4的地址转换成32位的网络序列的整数
//c_str()的作用是将string转化成char类型
socklen_t len=sizeof(local);
int res=bind(sockfd_,(const struct sockaddr*)&local,len); //为了适应通用接口需要将我们的sockaddr_in变量进行强转
if(res<0)
{
std::cout<<"bind erro"<<std::endl;
std::cout<<strerror(errno)<<std::endl;
exit(1);
}
else
{
std::cout<<"socket bind success"<<std::endl;
}
}
void Run()//服务端运行,就是从套接字中接收消息,并对消息进行处理,
{ //再将处理后的消息发送回给客户端(func是回调函数,将处理数据交给外部函数,对代码进行分层)
isrunning_=true;
char inbuffer[size]; //用于从套接字文件中读取消息的缓冲区
std::cout<<"进入whilerun"<<std::endl;
while(isrunning_) //一般服务器启动后就一直进行运行,等待客户的消息
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
ssize_t n=recvfrom(sockfd_,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);//读取客户端的消息并写入缓冲区
std::cout<<"revufrom:"<<n<<std::endl;
if(n<0)
{
std::cout<<"recvfrom error"<<std::endl;
continue;
}
inbuffer[n]=0; //将缓冲区里读到的消息的后面加一个/0
std::string info=inbuffer;
std::string echo_string="222"+info; //对读到的消息进行处理
std::cout<<echo_string<<std::endl;
sendto(sockfd_,&echo_string,sizeof(echo_string),0,(const struct sockaddr*)&client,len);//将处理后的消息发送回给客户端
}
}
~UdpServer()
{
if(sockfd_>0)
{
close(sockfd_);
}
}
private:
int sockfd_; //网络文件的描述符
std::string ip_; //任意ip地址
uint16_t port_; //表明服务器进程的端口号
bool isrunning_;
};
2.构建服务端
创建udpclient.cc文件:
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include<string.h>
using namespace std;
void Usage(std::string proc)//客户端启动后用于提示用于传输消息,proc为所运行程序
{
std::cout<<"\n\rUsage: "<<proc<<"serveip serveport\n"<<std::endl;
}
int main(int argc,char* argv[])//用命令行参数来接收ip和端口号
{
if(argc != 3) //./udpclient ip 端口号
{
Usage(argv[0]);
exit(0);
}
std::string serverip=argv[1];
uint16_t serverport=std::stoi(argv[2]); //将端口号转为uint16_t
struct sockaddr_in server; //创建服务端套接字将相关变量赋值,使用户知道服务端的ip和端口号,便于向服务端发送消息
bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(serverport);
server.sin_addr.s_addr=inet_addr(serverip.c_str());
socklen_t len=sizeof(server);
int sockfd=socket(AF_INET,SOCK_DGRAM,0); //创建客服端套接字向服务端发送消息
if(sockfd<0)
{
cout<<"socket error"<<endl;
return 1;
}
string message; //向服务端发送的消息
char buffer[1024]; //接收服务端处理后的消息的缓冲区
while(true)
{
cout<<"Please Enter@:";
getline(cin,message);
cout<<message<<endl;
if(-1==sendto(sockfd,message.c_str(),message.size(),0,(const struct sockaddr *)&server,len)){
cout<<"sendto error"<<strerror(errno);
}
//创建一个套接字用于接收服务器处理后的消息
struct sockaddr_in temp;
socklen_t len=sizeof(temp);
memset(buffer,0,sizeof(buffer));
ssize_t s=recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);
cout<<"客户端收到:"<<s<<"个字节"<<endl;
if(s>0)
{
buffer[s]='\0';
for(int i=0;i<s;i++)
{
cout<<buffer[i];
}
cout<<endl;
}
}
close(sockfd);
return 0;
}
3.创建调用函数文件Main.cc
#include "Udpserve.hpp"
#include <memory>
#include <cstdio>
void Usage(std::string proc)
{
std::cout<<"\n\rUsage:"<<proc<<"port[1024+]\n"<<std::endl;
}
/*std::string Handler(const std::string &str)//服务端用于处理接收到的数据的回调函数
{
std::string res="Server get a message: ";
res+=str;
std::cout<<res<<std::endl;
return res;
}
*/
int main(int argc,char* argv[])//用命令行参数来接收服务端的端口号,如:./udpserver port
{
if(argc != 2)
{
Usage(argv[0]);
exit(0);
}
uint16_t port=std::stoi(argv[1]);
std::unique_ptr<UdpServer> svr(new UdpServer(port)); //用智能指针来控制创建新的服务端
svr->Init();
svr->Run(); //传入处理函数
return 0;
}
4.创建makefile文件
.PHONY:all
all:udpserver udpclient
udpserver:Main.cc
g++ -o $@ $^ -std=c++11
udpclient:Udpclient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f udpserver udpclient
运行效果:
客服端发送与接收:
服务端接收与发送: