一、基础知识
1.区分源地址、目的地址
(1)源IP地址和目的地址:最开始的IP地址与送达数据的地址
(2)源MAC地址和目的MAC地址:相当于上一站的地址与下一站的地址,在不断地变化
socket通信,本质是进程间通信,只是是跨网络的进程间通信(如:客户端进程与服务器端进程的通信)
2.端口号
任何的网络服务与网络客户端,如果要进行正常的数据通信,必须要用端口号来唯一标识自身
(1)端口号是传输层的内容
(2)端口号是一个2字节16bit的整数
(3)端口号用来标识一个进程,告诉OS当前数据交给哪一个进程来处理
(4)IP地址+端口号:标识全网内唯一的一个进程
公网IP:唯一的标识全网内唯一的一台主机
端口号(port):标识一台主机上唯一的一个进程
(5)在同一个OS内,一个进程可以与一个端口号进行绑定,该端口号就在网络层面唯一标识一台主机上的唯一一个进程
ip+port的通信方式就叫做socket通信
进程PID VS 端口号
一台主机可能存在大量的进程(OS层面),但不是所有的进程都要对外进行网络数据请求(网络层面)(身份证号与公司员工号的关系,各有各的机制)
3.源端口号、目的端口号
源端口号:数据是谁发的
目的端口号:数据要发给谁
4.TCP、UDP(后面详细总结TCP、UDP异同)
都是传输层协议
TCP | UDP |
---|---|
传输层协议 | 传输层协议 |
有连接 | 无连接 |
可靠传输 | 不可靠传输面向数据报 |
面向字节流 | 面向数据报 |
5.网络序列
网络上面的数据必须都是大端
(1)概念
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中;
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
规定:网络上的数据必须都是大端
(2)为什么规定是大端?
发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出;
接收主机把从网络接到的数据一次保存在接收缓冲区中,也是按照从低到高的地址顺序保存。
二、socket
1.socket编程接口(常见API)
(1)创建socket文件描述符 (TCP/UDP, 客户端 + 服务器)
函数:int socket(int domain, int type, int protocol);
domain:域,协议家族(16位地址类型)
type:服务类型,分为两种,SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
protocol:协议类别,一般设为0,默认,根据前两个字段自动推断需要的是哪一个协议
(2) 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr address,socklen_t address_len);
(3)开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
(4)接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr address,socklen_t* address_len);
(5)建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
2.sockaddr结构
(1)IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
(2)IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
(3)socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
3.简单UDP客户端/服务端通信
udp_client.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
class UdpClient
{
private:
int sockfd;
std::string server_ip;
int server_port;
public:
UdpClient(std::string _ip,int _port)
:server_ip(_ip),server_port(_port)
{}
bool InitUdpClient()
{
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
std::cerr << "socket error" << std::endl;
return false;
}
//客户端不需要绑定吗?(不需要)需要port吗?(需要)
}
void Start()
{
struct sockaddr_in peer;
memset(&peer,0,sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(server_port);
peer.sin_addr.s_addr = inet.addr(server_ip.c_str());
for(;;)
{
std::cout << "Please enter#:" ;
std::cin >> msg;
sendto(sockfd,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
//读取消息
char buffer[128];
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(strucy sockaddr*)&temp,&len);
if(size > 0) //读取成功
{
buffer[size] = 0;
std::cout << buffer << std::endl;
}
}
}
~UdpClient()
{
if(sockfd >= 0)
close(sockfd);
}
};
udp_client.cc
#include"udp_client.hpp"
//3个参数:./udp_client server_ip server_port
int main(int agec,char * argv[])
{
if(argc != 3)
{
std::cerr << "Usage:" << argv[0] << "server_ip server_port" << std::endl;
return 1;
}
std::string ip = argv[1];
int port = atoi(argv[2]); //字符串转为整型
UdpClient *ucli = new UdpClient(ip,port);
ucli->InitUdpClient();
ucli->Start();
return 0;
}
udp_server.hpp
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/wait.h>
#define DEFAULT 8081
class UdpServer
{
private:
//std::string ip;
int sockfd;
int port;
public:
UdpServer(int _port = DEFAULT):port(_port),socket(-1)//,ip(_ip)
{}
//初始化
bool InitUdpServer()
{
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET是利用ipv4,也可以使用其它机制
if(socket < 0) //创建套接字失败
{
std::cerr << "socket error" << std::endl;
return false;
}
std::cout << "socket create success,sockfd:" << sockfd << std::endl; //文件描述符是3,0 1 2 默认分配了,从3开始
//绑定,即:文件和网络关联起来
//sockaddr_in结构体,是bind()函数里的一个参数,需要填充信息
//此结构体主要包括:地址类型,端口号,IP地址
struct sockaddr local;
memset(&local,'\0'.sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port); //port需要发送到网络中,需要设置为网络序列
//local.sin_addr.s.addr = inet_addr(ip.c_str()); //整数ip转换成4字节ip,inet_addr为C语言接口,则调用使用ip.cstr()(C++风格的转换成C语言字符串以'\0'结尾的)
local.sin_addr.s_addr = INADDR_ANY; //绑定0
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;
}
//启动
void Start
{
#define SIZE 128 //定义的缓冲区大小
char buffer[SIZE] = 0;
//dup2(sockfd,1); //重定向
for(;;)
{
//读取数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t size = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
if(size > 0) //读取成功
{
buffer[size] = 0;
int _port = ntohs(peer.sin_port); //网络序列转成主机序列
std::string _ip = inet_ntoa(peer.sin_addr); //网络字节IP转换成字符串IP
std::cout << _ip << ":" << port << "#" << buffer << std::endl;
//其余想做的任务
std::string cmd = buffer;
std::string result;
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);
}
close(pipes[1]); //父进程关闭写
char c;
while(1)
{
if(read(pipes[0],&c,1) > 0)
{
result.push_back(c);
}
else
{
break;
}
}
wait(nullptr);
}
std::string echo_msg;
if(result.empty())
{
echo_msg += buffer;
echo_msg = "server get!";
}
else
{
echo_msg = result;
}
//服务器返回内容
//std::string echo_msg = "server get!";
//echo_msg += buffer;
sendto(sockfd,echo_msg.c_str(),echo_msg.size(),0,(struct sockaddr*)&peer,len);
}
else
{
std::cerr << "recvfrom error" << std::endl;
}
}
}
~UdpServer()
{
if(sockfd >= 0)
close(sockfd);
}
};
udp_server.cc
#include"udp_server,hpp"
//udp_server ip port
int main(int argc, char *argv[])
{
if*(argc != 2)
{
std::cerr << "Usage:" << argv[0] << "port" << std::endl;
return 1;
}
std::string ip = "127.0.0.1"; //127.0.0.1是本地环回ip,标识本主机
int port = argv[1];
UdpServer *svr = new UdpServer(ip,port);
svr->InitUdpServer();
svr->Start(); //服务器启动
return 0;
}
udp_server.cc外网访问
#include"udp_server,hpp"
//udp_server ip port
//INADDR_ANY->0
int main(int argc, char *argv[])
{
if*(argc != 2)
{
std::cerr << "Usage:" << argv[0] << "port" << std::endl;
return 1;
}
//std::string ip = "127.0.0.1"; //127.0.0.1是本地环回ip,标识本主机
int port = argv[1];
//UdpServer *svr = new UdpServer(ip,port);
UdpServer *svr = new UdpServer(port);
svr->InitUdpServer();
svr->Start(); //服务器启动
return 0;
}
注意:
(1)netstat:查看当前网络状态
参数:-nlup
参数 | 作用 |
---|---|
n | 能显示成数字就显示成数字 |
l | list |
tp | tcp |
up | udp |
(2)端口号通常是16位,IPV4通常是32比特位的数据 | |
(3)服务器需要绑定,客户端不需要绑定 | |
Ⅰ.服务器是为了给别人提供服务,别人需要知道ip和端口,端口要总所周知,绑定后不能轻易改变 | |
Ⅱ.客户端不绑定,因为客户端访问server,端口号只要是唯一的即可,需要和特定的client进程强相关,client端口可以动态设置 | |
Ⅲ.client需要port,sendto类似的接口,client直接在OS层面会自动给client获取一个唯一的端口 | |
Ⅳ.云服务器的ip,是由对应的云厂商提供的,这个ip不能直接被绑定(直接绑定需要虚拟机或自定义安装的Linux),如果需要bind,需要让外网访问,需要bind 0(INADDR_ANY为0,即服务器可以接受来自任何client的请求) |
Ⅳ.云服务器的ip,是由对应的云厂商提供的,这个ip不能直接被绑定,如果需要bind,需要让外网访问,需要bind 0,INADDR_ANY为0