1.端口号(port)
端口号是一个2字节16位整数。任何网络数据通信,必须要通过端口号来唯一标识自身。
IP号用来保证数据可以机器到机器传递,进程还要绑定进程号来标定自身。
一台机器上一个进程可以与端口号绑定,绑定后就在网络层面唯一标识一台主机上的一个进程
综上:
- 公网IP标识全网唯一 一台主机
- 端口号标识一台主机上的一个进程
- ip+port:标记全网唯一 一个进程
这种通过ip和端口的方式通信称为socket通信。
进程PID VS 端口号
进程PID与端口号都标识了一个进程。
但一台机器上存在大量进程,但不是所有进程都要网络通信。
进程PID标识系统所有进程,属于系统范畴。
端口号是网络概念,代表这个进程要进行网络数据请求
之所以将其区分开为了方便管理。
2.网络字节序
首先每一台机器储存方式可能是大端或者小端。如果客户端与服务器储存方式不同,如果直接通信,机器解析数据可能出现问题
所以在网络中需要考虑大小端。
在网络中规定网络序列,在网络序列上数据都是大端形式。小端机器在发送数据时必须经过转化,这样就避免了数据传输时因为机器存储类型不同导致传输失败。
为了使代码更具有可移植性,下面函数实现主机序列和网络序列之间转化前两个函数代表主机序列转网络,后两个函数相反。
3.sockaddr通用类型
套接字分为本地通信与跨网络通信不同的情况。
sockaddr_in用来实现跨网络通信。
针对本地通信也要存在接口。结构体不同
为了实现不同情况传递同一种参数,诞生了sockaddr结构体。不论是网络通信还是本地通信,都将对应的结构体强制转化位sockaddr结构体传参。
4.套接字系列函数
socket函数创建套接字(sys/types.h sys/socket.h)
参数解释:
- domain:什么类型套接字。一般为AF_INET
- type:套接字服务类型。SOCK_STREAM代表TCP,SOCK_DGRAM代表UDP
- protocol:协议类别。一般为0,因为函数可以根据前两个参数推导出第三个参数。
返回值:成功返回文件描述符,失败返回-1,设置错误码。
bind绑定端口与ip(sys/types.h sys/socket.h)
参数解释:
- socket:创建套接字成功返回的文件描述符
- address_len:address结构体大小
- address:
因为是跨网络通信,所以使用sockaddr_in结构体,在传参时强制转化为sockaddr
需要注意:
ipv4型ip又两种形式
1.点分10进制字符串风格(127.0.0.1)
2.无符号32位整数[0-255].[0-255].[0-255].[0-255]。这种整数形式的ip风格节省空间
字符串型ip转化位整数型ip函数
输入字符串型ip返回整数型ip
绑定操作一般如下
struct sockaddr_in local;
memset(&local,'\n',sizeof(local));//初始化
local.sin_family=AF_INET;//通信家族
local.sin_port=htons(port);//端口转为网络字节序
local.sin_addr.s_addr=inet_addr(ip.c_str());//字符串型IP转数字型IP
//绑定
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local))<0)//绑定失败返回-1
//......
返回值:绑定成功返回0,失败返回-1。错误码被设置
recvfrom函数读取网络数据(sys/socket.h sys/types.h)
参数解释:
- socket:文件描述符
- buffer:将数据读到那个缓冲区
- length:期望读取的长度
- flags:读取方式,0代表阻塞,1代表非阻塞。
- 其他两个参数表明谁发送的消息。这两个参数位输出型参数。函数调用结束后客户端属性被设置到src_addr中。addrlen代表表示结构体src_addr的长度的变量地址)(输出型参数)
返回值:实际读取的数据的长度
sendto函数发送数据到网络中(sys/socket.h sys/types.h)
参数解释:
- socket:文件描述符
- buffer:将那个缓冲区的数据发送
- length:期望发送的长度
- flags:发送方式,0代表阻塞,1代表非阻塞。
- 最后两个参数代表要将数据发送到那个服务器中
5.udp实现服务端文件回显客户端
注意:udp服务器的端口需要绑定。而客户端不需要绑定。客户端访问服务器只要端口唯一即可。
同时:云服务器如果需要band让外部访问,需要band 0(INADDR_ANY)
服务端
udp_sever.h
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<cstring>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/wait.h>
class UdpSever{
private:
int port;
int sockfd;
public:
UdpSever(int _port):port(_port),sockfd(-1){}
~UdpSever(){
if(sockfd>0)
{
close(sockfd);
}
}
bool InitUdpSever()
{
sockfd=socket(AF_INET,SOCK_DGRAM,0);//ipv4 udp协议
if(sockfd<=0){
std::cerr<<"socket error"<<std::endl;
return false;
}
std::cout<<"socket creat success sockfd:"<<sockfd<<std::endl;//socket=3文件描述符
//绑定端口与ip
struct sockaddr_in local;
memset(&local,'\n',sizeof(local));//初始化
local.sin_family=AF_INET;//通信家族
local.sin_port=htons(port);//端口转为网络字节序
local.sin_addr.s_addr=INADDR_ANY;//字符串型IP转数字型IP
//绑定
if(bind(sockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"bind error"<<std::endl;
return false;
}
std::cout<<"bind success"<<std::endl;
return true;
}
#define SIZE 255
void Start()
{
while(true)
{
//udp接受消息
char buff[SIZE]={0}; //预留一个空间给'\0'
sockaddr_in client;
socklen_t len=sizeof(client);
ssize_t size=recvfrom(sockfd,buff,sizeof(buff)-1,0,(struct sockaddr*)&client,&len);
if(size>0)
{
buff[size]='\0';
int _port=ntohs(client.sin_port);//对端的端口号
std::string _ip=inet_ntoa(client.sin_addr);
std::cout<<_ip<<":"<<_port<<"#"<<buff<<std::endl;
std::string cmd=buff;
std::string ret;//存放父进程读取到的管道信息
if(cmd=="ls")
{
int pipes[2];
pipe(pipes);//匿名管道
//进程替换执行操作
pid_t id=fork();
if(id==0)
{
//child进程进行写入
close(pipes[0]);
dup2(pipes[1],1);//将显示屏上的数据写到管道中
execl("/usr/bin/ls","ls","-a","-l","-i",nullptr);
exit(-1);
}
//父进程要读取数据放到ret字符串中
close(pipes[1]);
char ch=0;
while(true)
{
if(read(pipes[0],&ch,1)>0)
{
ret.push_back(ch);
}
else
{
break;
}
}
wait(nullptr);//等待任意一个进程
}
std::string echo_meg="sever get! ";
if(ret.empty())//表明不是ls命令
{
echo_meg+=buff;
}
else//是ls命令,将服务器文件发送到网络中
{
echo_meg=ret;
}
sendto(sockfd,echo_meg.c_str(),echo_meg.size(),0,(struct sockaddr*)&client,len);
}
else
{
std::cerr<<"recvfrom error"<<std::endl;
}
}
}
};
udp_sever.cpp
#include"udp_sever.h"
//udp + port启动服务
int main(int argc,char*argv[])
{
if(argc!=2)
{
std::cout<<"User:"<<argv[0]<<"+port"<<std::endl;
}
else
{
int port=atoi(argv[1]);
UdpSever*svr=new UdpSever(port);
svr->InitUdpSever();
svr->Start();
}
return 0;
}
客户端
udp_client.h
#pragma once
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<cstring>
#include<unistd.h>
class UdpClient{
private:
int sockfd;
std::string ip;
int sever_port;
public:
UdpClient(std::string _ip,int _sever_port):sockfd(-1),ip(_ip),sever_port(_sever_port){}
~UdpClient(){
if(sockfd>0)
{
close(sockfd);
}
}
bool InitUdpClient()
{
sockfd=socket(AF_INET,SOCK_DGRAM,0);
if(sockfd<0)
{
std::cerr<<"socket error"<<std::endl;
return false;
}
//客户端不需要绑定,sendto函数会采用系统自己分配
return true;
}
void Start()
{
std::string meg;
struct sockaddr_in sever;
memset(&sever,0,sizeof(sever));
sever.sin_family=AF_INET;
sever.sin_port=htons(sever_port);
sever.sin_addr.s_addr=inet_addr(ip.c_str());
while(true)
{
std::cout<<"Please Enter#";
std::cin>>meg;
sendto(sockfd,meg.c_str(),meg.size(),0,(struct sockaddr*)&sever,sizeof(sever));
char buff[128]={0};
struct sockaddr_in tmp;
socklen_t len=sizeof(tmp);
ssize_t size=recvfrom(sockfd,buff,sizeof(buff)-1,0,(struct sockaddr*)&tmp,&len);
if(size>0)
{
buff[size]='\0';
std::cout<<buff<<std::endl;
}
else
{
std::cout<<"recvfrom error"<<std::endl;
}
}
}
};
udp_client.cpp
#include"udp_sever.h"
//udp + port启动服务
int main(int argc,char*argv[])
{
if(argc!=2)
{
std::cout<<"User:"<<argv[0]<<"+port"<<std::endl;
}
else
{
int port=atoi(argv[1]);
UdpSever*svr=new UdpSever(port);
svr->InitUdpSever();
svr->Start();
}
return 0;
}