目录
前言
数据从一台主机到另一台主机不是目的,真正的目的是让对端主机提供数据处理的服务。
而数据是人通过特定的客户端产生的,发送给特定的服务器。
客户端本身是一个进程,而服务器也是一个进程,所以网络通信的本质就是进程间通信。
端口号
什么是端口号
IP仅仅是解决了两台物理机器之间互相通信,我们还需要考虑如何保证双方对应的进程能接收和发送相应的数据。
端口号:标识一台机器上唯一的一个进程。
这样,通过IP + PORT的方式,就能标识互联网中唯一的一个进程。
端口号 vs PID
1.端口号是一台主机上一个进程的唯一标识符,但由于网络服务通常是需要暴露给用户的,而用户访问该服务需要 IP+PORT ,所以对应服务进程的端口号通常需要被固定下来,目的是方便用户使用服务。
2.PID,是系统分配给一个进程的唯一标识符,PID就是各进程的身份标识符,程序一运行,系统就会自动分配给进程一个独一无二的PID。进程终止后,PID被系统回收,可能会被继续分配给新运行的进程。目的是让操作系统更方便的管理进程。
3.一个进程可以关联多个端口号,但一个端口号不能关联多个进程。
4.一个进程的PID永远不会超过一个,因为在OS中管理该进程的内部数据结构中只有一个PID字段。
端口号的划分
0 - 1023:知名端口号,HTTP、FTP、SSH、等这些广为使用的应用层协议,它们的端口号都是固定的。
1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的。
知名端口号
ssh服务器:使用22端口
ftp服务器:使用21端口
telnet服务器:使用23端口
http服务器:使用80端口
https服务器:使用443端口
网络字节序
为什么网络字节序(多数情况下)是大端?
由于历史原因,早年设备的缓存很小,先接受高字节能快速判断报文信息:包长度(需要准备多大缓存)、地址范围(IP地址是从前到后匹配的)。在性能不是很好的设备上,高字节序会更快一些。
为什么主机字节序(多数情况下)是小端?
小端的加法器比较好做,如果做一个8位*4的加法器,只需要一个8位加法器,然后依次从低到高循环加上所有字节就可以了,进位的电路非常简单,而如果是大端,则需要一次加载32位,不然的话进位的设计比较困难。
不过对于现在的设备来说,大小端的区别已经很小了。
struct sockaddr
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6、Unix Domain Socket等。然而,各种网络协议的地址格式并不相同。
不同的网络协议,其地址类型通常是不同的,这样,只要取得某种sockaddr结构体中的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
socket API可以都用struct sockaddr* 类型表示,在使用的时候定义成sockaddr_in,而在传参的时候需要强转成sockaddr,可以接受各种类型的sockaddr结构体指针作为参数。
这样的好处是程序的通用性,不同的协议,可以使用同一套标准接口。
udp socket
socket()
// 1.创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
bind()
// 2.给该服务器绑定端口和ip(特殊处理)
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port); //小端转大端
local.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr *)&local, sizeof(local))
recvfrom()
// 3.接收来自客户端的数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
sendto()
// 4.向客户端发送数据
sendto(sock, sendto_client.c_str(), sendtoto_client.size(), 0, (struct sockaddr *)&peer, len);
close()
// 5.关闭打开的套接字文件
close(sock);
实现一个远程执行指令的服务
服务端
#include <iostream>
#include <cerrno>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
const uint16_t port = 6666;
// udp_server
int main()
{
// 1.创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket create errno: " << errno << std::endl;
return 1;
}
// 2.给该服务器绑定端口和ip(特殊处理)
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port); //小端转大端
// a.需要将认识别的点分十进制,字符串风格IP地址,转化成4字节整数IP
// b.也要考虑大小端
// in_addr inet_addr(const char* cp);能完成上面ab两个工作
// 坑:云服务器,不允许用户直接bind公网IP,另外,正常编写的时候,我们也不会指明IP
// local.sin_addr.s_addr = inet_addr("124.223.93.138");
// INADDR_ANY:如果你bind的是确定的IP(主机),意味着只有发到该IP主机上面的数据
// 才会交给你的网络进程,但是,一般服务器可能有多张网卡,配置多个IP,我们需要的不是
// 某个IP上面的数据,我们需要的是,所有发到该主机,发送到该端口的数据!
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cerr << "bind errno: " << errno << std::endl;
return 2;
}
// 3.提供服务
bool quit = false;
#define NUM 1024
char buffer[NUM];
while (!quit)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
if (cnt > 0)
{
buffer[cnt] = 0;//当作一个字符串命令
FILE* fp = popen(buffer, "r");//popen是一个用来执行命令的函数
std::string echo_to_client;
char line[1024] = {0};
while(fgets(line, sizeof(line), fp) != NULL)
{
echo_to_client += line;
}
pclose(fp);
std::cout << "client# " << buffer << std::endl;
//根据用户输入构建一个新的返回字符串
sendto(sock, echo_to_client.c_str(), echo_to_client.size(), 0, (struct sockaddr *)&peer, len);
}
else
{
//TODO
}
}
return 0;
}
客户端
#include <iostream>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void Usage(std::string proc)
{
std::cout << "Usage: \n\t" << proc << " server_ip server_port" << std::endl;
}
// ./udp_client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 0;
}
// 1.创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
std::cerr << "socket error: " << errno << std::endl;
return 1;
}
//客户端需要显示的bind吗??
// a. 首先,客户端必须也要有ip和port
// b. 但是,客户端不需要显示的bind!一旦显示的bind,就必须明确client要和哪一个port关联
// client指明的端口号,在client端一定会有吗??有可能被占用,被占用就导致client无法使用
// server要的是port必须明确,而且不变,但client只要有就行!一般由OS自动bind()
// 就是client正常发送数据的时候,OS会自动bind,采用的是随机端口的方式
// b.数据给谁发?
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
// 2.使用服务
while (1)
{
// a.数据从哪来?
// std::string message;
// std::cout << "请输入# ";
// std::cin >> message;
std::cout << "MyShell $ ";
char line[1024] = {0};
fgets(line, sizeof(line), stdin);
sendto(sock, line, strlen(line), 0, (struct sockaddr *)&server, sizeof(server));
struct sockaddr_in tmp;
socklen_t len = sizeof(tmp);
char buffer[1024];
ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);
if(cnt > 0)
{
buffer[cnt] = 0;
std::cout << buffer << std::endl;
}
else
{
//TODO
}
}
return 0;
}
tcp socket
socket()
// 1.创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
bind()
// 2.bind
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
bind(listen_sock, (struct sockaddr *)&local, sizeof(local)
listen()
// 3.监听
//需要允许用户连接
const int back_log = 5;
listen(listen_sock, back_log)//listen的第二个参数与全连接队列有关
accept()
// 4. 从监听套接字中获取新来的套接字
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
connect()
// 5.发起连接
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);//server ip
server.sin_port = htons(atoi(argv[2]));//server port
connect(sock, (struct sockaddr*)&server, sizeof(server)
recv()/read()
// 6.读数据
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
send()/write()
// 7. 写数据
write(sock, buffer, sizeof(buffer)-1);
close()
// 8.关闭套接字文件
close(_sock);
实现一个多进程/多线程/线程池版(含任务队列)服务器
任务模块
#pragma once
#include <iostream>
#include <pthread.h>
namespace ljh_task
{
class Task
{
private:
int _sock;
public:
Task(int sock = -1)
: _sock(sock)
{
}
int Run()
{
// while (true)
// {
char buffer[1024];
memset(&buffer, 0, sizeof(buffer));
ssize_t s = read(_sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << "client# " << buffer << std::endl;
std::string echo_string = ">>>server<<<, ";
echo_string += buffer;
write(_sock, echo_string.c_str(), echo_string.size());
}
else if (s == 0)
{
std::cout << "client quit ..." << std::endl;
// break;
}
else
{
std::cerr << "read error: " << errno << std::endl;
// break;
}
// }
close(_sock);
}
~Task()
{}
};
}
线程池模块
#pragma once
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>
namespace ljh_thread_pool
{
const int g_num = 5;
template<class T>
class ThreadPool
{
private:
int _num;
std::queue<T> _task_queue;
pthread_mutex_t _mtx;
pthread_cond_t _cond;
static ThreadPool<T>* ins;
private:
ThreadPool(int num = g_num)
:_num(num)
{
pthread_mutex_init(&_mtx, nullptr);
pthread_cond_init(&_cond, nullptr);
}
//拷贝构造
ThreadPool(const ThreadPool<T>& tp) = delete;
//赋值
ThreadPool<T>& operator=(const ThreadPool<T>& tp) = delete;
public:
~ThreadPool()
{
pthread_mutex_destroy(&_mtx);
pthread_cond_destroy(&_cond);
}
public:
static ThreadPool<T>* GetInstance()
{
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
//双判定,减少锁的争用,提高获取单例的效率
if(ins == nullptr)
{
pthread_mutex_lock(&mtx);
if(ins == nullptr)
{
ins = new ThreadPool<T>();
ins->InitThreadPool();
std::cout << "首次加载对象" << std::endl;
}
pthread_mutex_unlock(&mtx);
}
return ins;
}
private:
void Lock()
{
pthread_mutex_lock(&_mtx);
}
void Unlock()
{
pthread_mutex_unlock(&_mtx);
}
bool IsEmpty()
{
return _task_queue.empty();
}
void Wait()
{
pthread_cond_wait(&_cond, &_mtx);
}
void Wakeup()
{
pthread_cond_signal(&_cond);
}
private:
//在类中让线程执行的方法必须设为静态方法
static void* Rountine(void* args)
{
pthread_detach(pthread_self());
ThreadPool<T>* tp = (ThreadPool<T>*)args;
while(true)
{
tp->Lock();
while(tp->IsEmpty())
{
tp->Wait();
}
T t;
tp->PopTask(&t);
tp->Unlock();
t.Run();
}
}
void InitThreadPool()
{
pthread_t tid;
for(int i = 0; i < _num; ++i)
{
pthread_create(&tid, nullptr, Rountine, (void*)this);
}
}
public:
void PushTask(const T& in)
{
Lock();
_task_queue.push(in);
Unlock();
Wakeup();
}
private:
void PopTask(T* out)
{
*out = _task_queue.front();
_task_queue.pop();
}
};
template<class T>
ThreadPool<T>* ThreadPool<T>::ins = nullptr;
}
服务端
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include "thread_pool.hpp"
#include "Task.hpp"
using namespace ljh_thread_pool;
using namespace ljh_task;
void Usage(std::string proc)
{
std::cout << "Usage: " << proc << "port" << std::endl;
}
// void ServiceIO(int new_sock)
// {
// //提供服务, 是一个死循环
// while (true)
// {
// char buffer[1024];
// memset(&buffer, 0, sizeof(buffer));
// ssize_t s = read(new_sock, buffer, sizeof(buffer) - 1);
// if (s > 0)
// {
// buffer[s] = 0;
// std::cout << "client# " << buffer << std::endl;
// std::string echo_string = ">>>server<<<, ";
// echo_string += buffer;
// write(new_sock, echo_string.c_str(), echo_string.size());
// }
// else if (s == 0)
// {
// std::cout << "client quit ..." << std::endl;
// break;
// }
// else
// {
// std::cerr << "read error: " << errno << std::endl;
// break;
// }
// }
// }
// void *HandlerRequest(void *args)
// {
// pthread_detach(pthread_self());
// int new_sock = *(int *)args;
// delete (int *)args;
// ServiceIO(new_sock);
// close(new_sock);
// }
//./tcp_server 8081
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
// tcp_server
// 1.创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
std::cerr << "socket error: " << errno << std::endl;
return 2;
}
// 2.bind
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
std::cerr << "bind error: " << errno << std::endl;
return 3;
}
// 3.监听
//需要允许用户连接
const int back_log = 5;
if (listen(listen_sock, back_log) < 0)
{
std::cerr << "listen error: " << errno << std::endl;
return 4;
}
// signal(SIGCHLD, SIG_IGN);
for (;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
if (new_sock < 0)
{
continue;
}
uint16_t client_port = ntohs(peer.sin_port);
std::string client_ip = inet_ntoa(peer.sin_addr);
std::cout << "get a new link -> [" << client_ip << ": " << client_port << "] : " << new_sock << std::endl;
// version 4 :加入线程池
// version 2, 3存在的问题:a.创建的进程/线程无上限;b.客户链接来了,我们才给客户创建进程/线程
// 1.构建一个任务
Task t(new_sock);
ThreadPool<Task>::GetInstance()->PushTask(t);
// version 3版本(多线程)
// pthread_t id;
// int* pram = new int(new_sock);
// pthread_create(&id, nullptr, HandlerRequest, pram);
// version 2版本(多进程)
// pid_t id = fork();
// if(id < 0)
// {
// continue;
// }
// else if(id == 0)
// {
// //child
// close(listen_sock);
// if(fork() > 0) exit(0);//不使用屏蔽信号的方式,但是要wait
// ServiceIO(new_sock);
// close(new_sock);
// exit(0);
// }
// else
// {
// //father
// close(new_sock);
// waitpid(id, nullptr, 0);
// }
// version 1:单进程版,没人使用!
// ServiceIO(new_sock);
}
return 0;
}
客户端
#include <iostream>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
void Usage(std::string proc)
{
std:: cout << "Usage: " << proc << " server_ip server_port" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 1;
}
std::string server_ip = argv[1];
uint16_t server_port = htons(atoi(argv[2]));
//1.创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
std::cerr << "socket error: " << errno << std::endl;
return 2;
}
//2.发起连接
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);//server ip
server.sin_port = htons(atoi(argv[2]));//server port
if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
std::cerr << "connect error: " << errno << std::endl;
return 3;
}
std::cout << "connect success!" << std::endl;
//3.使用服务
while(true)
{
std::cout << "Please Enter# ";
char buffer[1024];
fgets(buffer, sizeof(buffer)-1, stdin);
write(sock, buffer, sizeof(buffer)-1);
ssize_t s = read(sock, buffer, sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
return 0;
}