linux网 络
协议本质就是一种约定。就是通信双方都认识的结构化的数据类型。
为什么要有TCP/IP协议:因为通信主机距离变远了,会有很多新问题,有新问题就需要解决问题,所以就有了TCP/IP。本质是一种解决方案。
局域网(例如在一个家,家人连接的同一个路由器)两台主机在一个局域网里可以直接通信。
每台主机在局域网上,要有唯一的标识来保证主机的唯一性:(mac地址)
IP在网络中,用来标识主机的唯一性。
在网卡出厂时就确定了,不能修改.mac地址通常是唯一的(虚拟机中的mac地 址不是真实的mac地址,可能会冲突;也有些网卡支持用户配置mac地址)
•以太网中,任何时刻,只允许一台机器向网络中发送数据(临界资源)
• 如果有多台同时发送,会发生数据干扰,我们称之为数据碰撞
• 所有发送数据的主机要进行碰撞检测和碰撞避免
报头部分,就是对应协议层的结构体字段,我们一般叫做报头
除了报头,剩下的叫做有效载荷
报文 = 报头+有效载荷
网络协议的共性:1.报头和有效载荷分离的问题--解包;2.除了应用层,每一层协议都必须解决一个问题,即自己的有效载荷应该交给上一层的哪一种协议--分用
跨网络传输:
IP地址是在IP协议中,用来标识网络中不同主机的地址;
对于IPv4来说,IP地址是一个4字节,32位的整数;
ip通常使用"点分十进制"的字符串表示IP地址,例如192.168.0.1;用点 分割的每一个数字表示一个字节,范围是0-255;
IP层(网络层)向上看到的所有报文都是一样的。都至少是IP报文。
IP和MAC的区别:
IP地址在整个路由过程中,一直不变, Mac地址一直在变。
IP是一种长远目标,Mac是下一阶段目标,目的IP是路径选择的重要依 据,mac地址是局域网转发的重要依据。
数据传输到主机不是目的,而是手段。到达主机内部,在交给主机内的进程, 才是目的。但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标 进程?
端口号是一个2字节16位的整数,在传输层中,用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来 处理。
一个端口号只能被一个进程占用。
IP地址+端口号能够标识网络上的某一台主机的某一个进程。
IP+Port叫做套接字(socket)。
每一个进程都有Pid,但是只有进行网络通信的进程才有端口号。
网络通信的本质,也是进程间通信。
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。所以就有了库函数做网络字节序和主机字节序的转换。
UDP代码实现:
//udpserver.hpp 搭建服务器#include <iostream>
#include <unistd.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include "InetAddr.hpp"static const int gsockfd = -1;
static const uint16_t glocalport = 8888;enum
{SOCKET_ERROR = 1,BIND_ERROR
};class UdpServer
{
public:UdpServer(uint16_t localport = glocalport): _sockfd(gsockfd),_localport(localport),_isrunning(false){}void InitServer()//服务器初始化{// 1. 创建socket文件_sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){LOG(FATAL, "socket error\n");exit(SOCKET_ERROR);}// 2. bindstruct 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()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时local.sin_addr.s_addr = INADDR_ANY; // 在服务器端,可以进行任意IP地址绑定int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){LOG(FATAL, "bind error\n");exit(BIND_ERROR);}}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){InetAddr addr(peer);//client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口inbuffer[n] = 0;std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << 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>// ./udp_server 8888按这种格式进行输入
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]);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地址和端口号
// ./udp_client 127.0.0.1 8888按这种格式运行
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];//获取服务端ipuint16_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的端口号,一般不让用户自己设定,而是让client OS随机选择// client 需要 bind它自己的IP和端口, 但是client 不需要 “显示” bind它自己的IP和端口, // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口struct sockaddr_in server;//获取服务端的ip和portmemset(&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);// std::cout << "line message is@ " << line << std::endl;int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct 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, (struct sockaddr*)&temp, &len);if(m > 0){buffer[m] = 0;std::cout << buffer << std::endl;}else{std::cout << "recvfrom error" << std::endl;break;}}else{std::cout << "sendto error" << std::endl;break;}}::close(sockfd);return 0;
}
//网络转主机 inetaddr.hpp
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>class InetAddr
{
private:void ToHost(const struct sockaddr_in &addr){_port = ntohs(addr.sin_port);_ip = inet_ntoa(addr.sin_addr);}public:InetAddr(const struct sockaddr_in &addr):_addr(addr){ToHost(addr);}std::string Ip(){return _ip;}uint16_t Port(){return _port;}~InetAddr(){}private:std::string _ip;uint16_t _port;struct sockaddr_in _addr;
};
TCP代码实现:
udp:面向数据报(数据是一个整体,整体发送)
tcp:面向字节流(客户端发的数据不一定是服务器收的,不确定发送多少)
//TCPServer.hpp#include <iostream>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"using namespace log_ns;enum
{SOCKET_ERROR = 1,BIND_ERROR,LISTEN_ERR
};const static int gport = 8888;
const static int gsock = -1;
const static int gblcklog = 8;using task_t = std::function<void()>;class TcpServer
{
public:TcpServer(uint16_t port = gport): _port(port),_listensockfd(gsock),_isrunning(false){}void InitServer(){// 1. 创建socket,这里只是监听的套接字_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd < 0){exit(SOCKET_ERROR);}struct sockaddr_in local;//填充信息memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY;//接受任意的IP// 2. 绑定 sockfd 和 Socket addrif (::bind(_listensockfd, (struct sockaddr*)&local, sizeof(local)) < 0){exit(BIND_ERROR);}// 3. 因为tcp是面向连接的,tcp需要未来不断地能够做到获取连接,所以需要监听。if (::listen(_listensockfd, gblcklog) < 0){exit(LISTEN_ERR);}}class ThreadData{public:int _sockfd;TcpServer* _self;InetAddr _addr;public:ThreadData(int sockfd, TcpServer* self, const InetAddr& addr) :_sockfd(sockfd), _self(self), _addr(addr){}};void Loop(){// signal(SIGCHLD, SIG_IGN);_isrunning = true;while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);// 4. 获取新连接int sockfd = ::accept(_listensockfd, (struct sockaddr*)&client, &len);//获取来自客户端的连接if (sockfd < 0){continue;}InetAddr addr(client);//下面有4个版本实现服务// version 0 --- 不靠谱版本// Service(sockfd, addr);// version 1 --- 多进程版本// pid_t id = fork();// if (id == 0)// {// // child// ::close(_listensockfd); // 建议互相关闭没用的fd// if(fork() > 0) exit(0);//子进程再建一个子进程,孙子进程退出,此时父进程就可以做到不用回收子进程// Service(sockfd, addr);// exit(0);// }// // father// ::close(sockfd);// int n = waitpid(id, nullptr, 0);// if (n > 0)// {// LOG(INFO, "wait child success.\n");// }// version 2 ---- 多线程版本 --- 不能关闭fd了,也不需要了// pthread_t tid;// ThreadData *td = new ThreadData(sockfd, this, addr);// pthread_create(&tid, nullptr, Execute, td); // 新线程进行分离// version 3 ---- 线程池版本 int sockfd, InetAddr addrtask_t t = std::bind(&TcpServer::Service, this, sockfd, addr);ThreadPool<task_t>::GetInstance()->Equeue(t);}_isrunning = false;}static void* Execute(void* args){pthread_detach(pthread_self());ThreadData* td = static_cast<ThreadData*>(args);td->_self->Service(td->_sockfd, td->_addr);delete td;return nullptr;}void Service(int sockfd, InetAddr addr){// 长服务while (true){char inbuffer[1024]; // 当做字符串ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);//读取信息(相当于recv)if (n > 0){inbuffer[n] = 0;LOG(INFO, "get message from client %s, message: %s\n", addr.AddrStr().c_str(), inbuffer);std::string echo_string = "[server echo] #";echo_string += inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());//写信息,相当于是send}else if (n == 0)//没有读到信息了,就退出{LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());break;}else{LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());break;}}::close(sockfd);}~TcpServer() {}private:uint16_t _port;int _listensockfd;bool _isrunning;
};
//TcpServerMain.cc#include "TcpServer.hpp"#include <memory>// ./tcpserver 8888
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]);std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);tsvr->InitServer();tsvr->Loop();return 0;
}
//TcpClientMain.cc#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>// ./tcpclient server-ip server-port
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]);// 1. 创建socketint sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}// 客户端不需要显示的bind,但是一定要有自己的IP和port,所以需要隐式的bind,OS会自动bind sockfd,用自己的IP和随机端口号// 当连接成功或者绑定成功的时候进行自动bindstruct sockaddr_in server;//填充服务端的信息memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));//与服务器建立连接if (n < 0){std::cerr << "connect socket error" << std::endl;exit(2);}while(true){std::string message;std::cout << "Enter #";std::getline(std::cin, message);write(sockfd, message.c_str(), message.size());//写,相当于是sendchar echo_buffer[1024];n = read(sockfd, echo_buffer, sizeof(echo_buffer));//收到信息if(n > 0){echo_buffer[n] = 0;std::cout << echo_buffer << std::endl;}else{break;}}::close(sockfd);return 0;
}
常见的网络命令:
netstat -upa查看udp所有网络服务(p进程,a所有)
netstat -tpa查看tcp所有网络服务
ping www.qq.com (域名)/网址 检查网络是否连通。
pidof 进程名(查看进程的pid)