认识端口号
我们知道在网络数据传输的时候,在IP数据包头部有两个IP地址,分别叫做源IP地址和目的IP地址。IP地址是帮助我们在网络中确定最终发送的主机,但是实际上数据应该发送到主机上指定的进程上的,所以我们不仅要确定主机,还要确定主机上的指定进程。而标识该进程的就是通过端口号。所以IP+端口号port就能标识互联网中唯一的一个进程。
- 端口号是一个2字节16位的整数。
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理。
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程。
- 一个端口号只能被一个进程占用。
端口号和进程pid
进程pid同样也是可以表示进程的唯一性,但是为什么网络通信还需要新引入一个端口号来标识进程呢?
- 并不是每一个进程都会进行网络通信,所以有端口号的则表明需要进行网络通信。
- 进程模块采用pid,网络通信模块采用端口号port,进行解耦。提高可维护性与扩展性。
一个进程可以绑定多个端口号(创建多个socket套接字); 但是一个端口号不能被多个进程绑定(端口号具有唯一性)。
认识传输层协议
传输层有两个最常见的协议就是传输控制协议(TCP)和用户数据报协议(UDP)。
TCP协议是一种面向连接的协议,它提供了可靠的、有序的数据传输,是Internet上最常见的传输层协议。面向字节流传输。
UDP协议则是一种无连接的协议,它不提供可靠的数据传输,但具有低延迟和高效率的特点,适用于需要实时性要求较高的应用场景,如实时音视频传输等。面相数据报传输。
网络字节序
不同的主机,大小端存储方式是不同的。内存和磁盘文件中的数据有大小端的区分,网络数据流同样有大端小端之分,而我们进行网络通信的时候就需要将大小端确定,这样接收到的消息才是正确的顺序。
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址,TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据。如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可
一般 在网络通信时,会采用以上的库函数来进行网络字节序和主机字节序的转换。
套接字的认识
socket套接字API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockaddr结构
sockaddr是一个通用的套接字地址结构体,在网络编程中用于表示套接字的地址信息。
struct sockaddr {
unsigned short sa_family; // 地址族
char sa_data[14]; // 地址信息
};
该结构体的产生其实就是为了统一各种不同的网络协议的地址格式,是一个通用的地址类型。以便在不同函数接口中的参数能够统一 。在实际的网络通信中我们一般都是采用sockaddr_in结构体来存储套接字信息。
struct sockaddr_in {
short int sin_family; // 地址族(标识套接字所使用的网络协议类型)
unsigned short int sin_port; // 端口号
struct in_addr sin_addr; // IP地址
unsigned char sin_zero[8]; // 保留的空字节,用于让sockaddr与sockaddr_in两个数据结构保持大小相同
};
udp网络程序(多线程)
thread_pool.h
#pragma once
#include <iostream>
#include <queue>
#include <thread>
#include <functional>
#include <mutex>
#include <vector>
#include <unistd.h>
#include <condition_variable>
using namespace std;
using namespace placeholders;
#define numdefault 5
template <class T>
class thread_pool
{
thread_pool(const thread_pool&)=delete;
thread_pool operator=(const thread_pool&)=delete;
public:
static thread_pool* get_instance() // 单例
{
if(_instance==nullptr)
_instance = new thread_pool();
return _instance;
}
void task_execution(const string &args) // 多个线程开始任务执行
{
while (1)
{
T t;//调用默认构造
{
//共享代码段
unique_lock<mutex> ul(_mtx);
while (_qt.empty()) // 无任务就等待
{
cond.wait(ul); // 等待期间会解锁,多线程会再等待队列中阻塞,等待成功会上锁
}
t = _qt.front();
_qt.pop();
}
// 处理任务
cout<<args<<": ";
t();//执行bind好的函数
sleep(1);
}
}
void push(const T &t)//传任务
{
unique_lock<mutex> ul(_mtx);
_qt.push(t);
cond.notify_one();//有任务则条件满足
}
~thread_pool()//
{
for (int i = 0; i < _num; i++) // C++thread使用线程不join的话程序会崩溃
{
_vt[i].join();
}
}
private:
thread_pool(int num = numdefault)//构造函数私有
: _num(num), _vt(num)
{
for (int i = 0; i < _num; i++)
{
string name = "thread_";
name += to_string(i + 1);
// 移动赋值,线程不支持左值拷贝
_vt[i] = thread(bind(&thread_pool<T>::task_execution, this,_1), name);//bind其实与function功能一样,不过可以提前确定参数
}
}
int _num; // 线程数目
queue<T> _qt; // 任务管理
vector<thread> _vt; // 管理线程
mutex _mtx; // 锁
condition_variable cond; // 条件变量,任务为空等待
static thread_pool* _instance;
};
template<class T>
thread_pool<T>* thread_pool<T>::_instance = nullptr;//单例
udpserver.h
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <functional>
#include "thread_pool.h"
using namespace std;
#define default_size 1024
using Func = function<string(string)>; // 该类型下创建的对象就当做参数string返回值string的函数使用
using task_t = function<void()>; // 该类型下创建的对象就当做无参无返回值的函数使用,可以衔接bind修饰的函数,将参数确定化
class Inet_addr
{
public:
Inet_addr() {}
Inet_addr(const struct sockaddr_in &clients)
: _si(clients), _ip(inet_ntoa(clients.sin_addr)), _port(ntohs(clients.sin_port))
{
}
void print_client_info(const char *buffer)
{
cout << "[port:" << _port << " "
<< "ip:" << _ip << "]";
cout << "client says:" << buffer << endl;
}
bool operator==(const Inet_addr &com)
{
return _ip == com._ip && _port == com._port;
}
const struct sockaddr_in &addr()
{
return _si;
}
const string &ip()
{
return _ip;
}
const in_port_t &port()
{
return _port;
}
~Inet_addr()
{
}
private:
struct sockaddr_in _si;
string _ip;
in_port_t _port;
};
class udp_server
{
public:
udp_server(uint16_t port, Func f)
: _port(port), _func(f)