文章目录
这篇博客一直写写停停拖了半个月才出来
套接字预备知识
我们所写的程序都是在用户层进行开发,或者说是在应用层进行协议的创建和规定,所以我们所用到的接口都是传输层所用到的接口,也就是系统调用接口,也就是说,我们后面所学的套接字接口,所用的都是传输层的接口
两个主机之间的通信,是不是可以认为是两个主机硬件之间的通信,
答案既可以是,也可以不是,实际上,硬件是承当两台主机进行通信的载体不错,但是,进行通信的是两台主机里面分别的进程,即两个进程之间进行通信,也即是软件层面上的
套接字,端口号,ip
IP地址(公网ip),唯一标识互联网中的一台主机
源IP,目的IP:对一个报文来说就是,从哪来到哪去,
这个最大的意义:就是指导一个报文进行路径选择,到哪里去:本质就是让我们根据目标进行路径进行路径选择的依据!
下一跳设备(mac地址的变化)
我们今天,这里更加专业和准确的来说,套接字本质就是进程之间的通信(是指不同主机之间的进程)
而ip地址的主机的唯一性,但是在这个主机上有很多进程,那么我应该怎么去确定我应该给哪一台主机呢?
所以,我们还需要通过某种方式来去表示特定主机上的某种进程,而标识进程的方式,我们就叫做端口号
端口号我们就是可以去标识一台主机里面唯一的进程
而用ip和端口号我们就能标识全网范围内的唯一主机上面的唯一进程
套接字=ip地址+端口号(端口号是一个16位的比特位)
我们学的网络通信,是站在人与人之间的通信
技术人员的视角:我们学到的网络通信,本质上是进程间的通信!!
(跨网络进行进程间通信)
比如:抖音的app客户端(进程)《-》抖音的服务器(也是一个进程)
IP仅仅知识解决了两台物理机器之间的通信
但是我们要考虑双方用户之间能够看到发送和接收数据,应用层上都有进程
端口号和PID
每个进程都会有PID,但是一个进程必须是要有网络进程才会有端口后
{注意:一个端口号只能由一个进程占用,但是一个进程可以有多个端口号}
(10086就相当于一个IP地址,(打过去之后叫了人工服务)而某一个人工就相当于是一个端口号,然后他的身份证就相当于是PID)
为了解耦(如果操作系统对于进程的标识方法变化的话,所有东西都要变化,但是如果用端口号的话,它只在网络中使用,不影响其他地方)
总结:
- 端口号就是一个2字节16位的整数
- 端口号用来标识一个进程,告诉操作系统,当前这个数据是要交给哪一个进程来处理
- 一个端口用只能被一个进程占用
- ip地址+端口号能够用来标识网络上某一台主机的某一个进程
互联网世界,是一个进程间通信的世界
万物互联就是每一个机器上都有一个进程,每一个进程就可以收集各种进程间的消息,还可以和其他进程之间通信,这样就可以获得了
TCP和UDP 协议的初识
因为我们要在应用层上写程序,而离应用层最近的就是传输层,所以我们使用的就是传输层接口
TCP协议特点(初始)
UDP 协议特点(初始)
tcp的可靠和不可靠是一个中性词,在直播的时候,就是使用UDP,
标准:非用tcp不可的就是用tco,处于成本考虑,也可以使用UDP,实在不会的话就使用TCP,
TCP协议需要解决数据传错了怎么办,丢包了怎么办,对方来不及接收怎么办,网络堵塞了怎么办
就是说,他会把大量的问题都要很好的解决,这样也就直接决定了TCP 的特点(相对于UDP来说,会比较复杂)。放过来UDP就比较的简单高效
为什么还需要不可靠的传输协议
可靠意味着需要举行的更多的工作来保证可靠性,成本也会更加多,效率也会更低一点,不可靠协议的特点就是简单,高效,实际上,我们需要根据需求来选择不同的协议
网络字节序
小端法(pc本机) 大端法(网络)
我们现在的机器都是小端,但是有没有一种可能,我们这边是小端机器,但是对面的服务器是大端机呢,这样的化,我们按照小端字节序发出去的数据,到了对方的服务器上面使用大端的方式来接收,就会造成错乱
那么如何解决这个问题呢?
我们网络中规定:所有的网络中所跑到 数据都是打断的数据,如果是小端(数据的低位保存在内存的低地址中,高位保存在内存的高地址)机器,都是需要先转化成为大端(数据的地位保存在内存的高地址,高位保存在内存的低地址)之后,才能进行发送或者接收,然后先发送或者接收低地址的数据,再是高地址的数据
总结来说,其
- 发送主机通常把缓冲区的数据按照内存地址的从低到搞的顺序进行发出
- 接收主机把从网络上接到的字节一次保存再接收缓冲区里面,也是按照内存地址从低到高的顺序保存
- 因此,网络数据流的地址应该这样进行规定,先发出的数据是低地址,后发出的数据就是高地址
- TCP/IP协议规定,,网络数据流应该采用大端字节序,即低地址高字节
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据,如果当前发送的主机是小端机,就需要先将数据转化成为大端,否者就忽略们直接发就可以了
为什么要规定成大端呢
因为这样,我们先就可以接收到低地址的数据,就是高位权重的数据
这样我们可以将高位放在高的位置,可以实现变实现边计算
同样,当我们接收字符串等待消息的时候,接收的信息就可以按照我们视觉中的从左万右出现,而不是先接收到最后一位,然后不断的头插操作
即:大端更加符合我们的阅读习惯
为了使得网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能够正常的运行,可以调用以下库函数左网络字节序和主机字节序的转换
h标识host(主机)
n标识network(网络)
例如
htonl表示将32位长整数从主机转化位网络字节序,例如将ip地址转化后准备发送(因为ip就是32位,4字节),ip协议
(192.168.0.102),这个是string我们想要使用的话,就要先把这个转化成为整数
如果主机是小端字节序。这些函数将参数左对应的大小端转换然后返回
如果是主机是大端字节序,就不用改变
htons将16位长整型数从主机转化成网络字节序,例如端口(port)
nthos,将16位长整数从网络转化为主机里面
string转到网络字节序如192.168.0.122
string->atoi->int->htol—>网络字节序
socket编程
socket创建套接字的流程
socket的API
#include<sys/type.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);//创建一个端点进行通信
返回的是socket的文件描述符,(TCP/UDP,客户端和服务器)
int bind(int sockfd ,const struct sockaddr* address,socklen_t address_len);
绑定端口号(TCP/UDP,服务器)
int listen(int socket ,int backlog)
;开始监听socket(tcp,服务器)
backlog:全连接队列的最大长度,如果有多个客户端发送过来请求,此时 没有被连接上的就会被放在一个链接队列李米娜,该传输表示的就是全连接队列的最大长度,一般不要设置太大,设置5./10即可
我们一开始创建的套接字并不是一个普通的套接字,而应该要叫做监听套接字,
int accept(int socket,struct sockaddr* address,socklen_t * address_len)’
接受请求
int connect (int sockfd,const struct sockaddr* addr,socklen_t addrlen)
建立连接
调用accept函数获取连接时,是从监听套接字里面获取的,如果accept连接成功,就会返回接收到的套接字对应的文件描述符
监听套接字和accept函数返回的套接字的作用:
- 监听套接字:用于获取客户端发来的请求,accpt函数会不断从监听套接字里面获取新的连接
- accept函数防护的套接字,用于为本次accpet获取到的连接提供服务。监听套接字的任务知识不断的获取连接,真正为这些连接提供服务的是accpet函数套接字
sockaddr 的结构
网络通信的标准又很多种,基于ip的网络通信,AF_INET,原始套接字,域间套接字
我们想让这些接口系统的统一化,sockaddr也就是一个通用结构
struct sockaddr_in把里面的14个字节进行细分了一下,然后sockaddr就被废弃了,所以我们在实际定义的时候就是定义struct sockaddr_in,在调用这个函数的时候要进行强制类型转换
#include<netinet/in.h>
- sockaddr_in(inet) 是用来进行网络通信的,
- sockaddr_un 是用来进行本地通信的 、
- sockaddr_in结构体存储了协议类型,端口号,ip地址等信息,网络通信时可以通过这个结构体把自己的信息发送给对方,也可以通过这个结构体获取远端的信息
- 可以看出,着3个结构体的前面16位都是一样的,代表的就是协议家族,可以根据这个参数判断需要进行哪一种特性(本地和跨网络)
- ipv4, ipv6的地址格式定义在netinet /in.h中,ipv4地址用socketaddr_in,结构体表示,包括16位地址类型,16位端口号,32位ip地址
- ipv4和ipv6地址类型分别定义为常数AF_INET,AF_INET6,只要取得某种socketaddr结构体的首地址,不需要知道具体是哪一种类型的结构体,就可以根据地址类型字段确定结构体中的内容
- socket API可以都用struct sockaddr* 类型表示,在使用的时候需要强制转化成sockaddr,这样好处是程序的通用性,可以接收ipv4和ipv6以及unix domain socket各种类型的sockaddr结构体指针为参数,
sockaddr_in的结构体:因为我们主要用到网络通信,所以这里主要介绍这个结构体,
sin_family 代表的是地址类型,我们主要用的是AF_INET,
sin_port代表的是端口号,这个是网络字节序的端口号,用之前要先htons初始化
sin_addr代表的是网络地址(也就是ip地址),用一个结构体struct in_addr来进行描述(这里填充的是ipv4的地址,一个32位整数),还要使用htonl将string转化成为网络字节序,要用inet_pton
但是这里的ip地址的类型是struct in_addr{uint32_t s_addr}
我们使用的时候就要用这个东西进行初始化
1 2 3 4 5 6 7 8 9 10 11 12
| struct sockaddr_in addr; addr.sin_family=AF_INET; add.sin_port=htos(port);
addr.sin_addr.s_addr=htonl(INADDR_ANY) bind((struct sockaddr*)&addr,size); }
|
地址转换函数
ip地址可以用点分十进制的字符串如127.0.0.1,也可以用一个32整数来表示,其中就涉及到二者之间 的转换一下是两者相互转化的库函数
字符串转in_addr(32位整数)
#include<arpa/inet.h>
int inet_aton(const char* cp,struct in_addr* inp);
in_addr_t inet_addr(const char* cp);
int inet_pton(int af,const char* src,void* dst);p即ip,n就是网络,将ip中的string转化成为网络字节序,所以返回值是int
这里的af就是ip地址家族,ip协议类型(只能是AF_INET,或者AF_INET6)
src就是我们要转的ip地址(点分是10进制的)
dst就是传出参数,就是转化后的网络字节序的ip地址,
返回值:成功返回1,如果返回0说明src里面的字符串并不代表一个有效的ip地址,-1的话就是我们输入的af并非AF_INET,或者说AF_INET6,错误码设置成EAFNOSUPPORT
const char* inet_ntop(int af,const void* src,char* dst,socklen_t size);//将网络字节序转化位ip地址,所以返回值是string
af就是ip协议,
src就是我们要转化的整数,网络字节序中的ip地址
dst就是转换后位字符串的ip地址
socklen_t size就是dst的大小
返回值,成功返回非空字符串指向dst,失败返回null,
errno里面可以查看错误的原因
字符串转整数的函数
inet_addr
函数原型
in_addr_t inet_addr(const char* cp)
参数:
返回值:整数ip
整数转字符串的函数
in_addr转字符串的函数
char * inet_ntoa(struct in_addr in)
in 是描述ip地址的结构体
返回值:字符串IP
const char* inet_ntop(int af,const void* src,char* dst,socklen_t size);//
UDP服务器
server.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <iostream> #include <string> #include <sys/types.h> #include <sys/socket.h> #include <cerrno> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include<cstdio> using namespace std;
int main(int argc,char* argv[]) { if(argc!=2) { cout<<"port"<<endl; return 1; } int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { cerr << "socket create error" << endl; return 1; } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(atoi(argv[1])); local.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *)&local, sizeof(local)) < 0) { cerr << "bind cerror " << errno << endl; return 2; } bool quit = false; while (!quit) { char buf[1024]; struct sockaddr_in peer; socklen_t len = sizeof(peer); ssize_t s = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&peer, &len); if (s > 0) { buf[s] = 0; cout << "client # " << buf << endl; FILE* fp=popen(buf,"r"); char t[1024]; string ech; while(fgets(t,sizeof(t),fp)) { ech+=t; } pclose(fp);
ech += "....."; sendto(sockfd, ech.c_str(), ech.size(), 0, (struct sockaddr *)&peer, len); } else { return 1; } } return 0; }
|
client.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <iostream> using namespace std; #include <sys/socket.h> #include <sys/types.h> #include <string> #include <arpa/inet.h> #include <cstdlib> #include <unistd.h> #include<cstring> #include<cstdio>
int main(int argc, char *argv[]) { if (argc != 3) { cout << "ip+port" << endl; return 0; } int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { cerr << "socket error" << endl; return 1; }
while (1) { string msg; cout<<"Myshell$ "; char line[1024]; fgets(line,sizeof(line),stdin);
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]); sendto(sockfd, line, strlen(line), 0, (struct sockaddr *)&server, sizeof(server));
struct sockaddr_in tmp; socklen_t len = sizeof(tmp); char buf[1024]; ssize_t s = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&tmp, &len); if (s > 0) { buf[s] = 0; cout << buf << endl; } else { cerr<<"recvfrom error"<<endl; } }
return 0; }
|
TCP服务器
多进程模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| #include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <cstring> #include <unistd.h> #include<cstdlib> #include<signal.h> #include<string> #include<sys/wait.h> using namespace std;
void serverio(int newsockfd) { while (true) { char buf[1024]; memset(buf, 0, sizeof(buf)); ssize_t s = read(newsockfd, buf, sizeof(buf) - 1); if (s > 0) { buf[s] = 0; cout << "client # " << buf << endl; string echo = "server send "; echo += buf; write(newsockfd, echo.c_str(), echo.size()); } else if (s == 0) { cout << "client quit" << endl; break; } else { cerr << "error" << endl; break; } } } int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { cerr << "socket error" << endl; return 1; } struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(8080); local.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *)&local, sizeof(local)) < 0) { cerr << "bind error" << endl; return 2; } if (listen(sockfd, 5) < 0) { cerr << "listen error" << endl; } while (1) { struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); socklen_t len = sizeof(peer); int newsockfd = accept(sockfd, (struct sockaddr *)&peer, &len); if (newsockfd < 0) { continue; } uint16_t cli_port=ntohs(peer.sin_port); string ip=inet_ntoa(peer.sin_addr); cout<<"get a link -> : "<<cli_port<<" : "<<newsockfd<<" "<<ip<<endl; pid_t id = fork(); if (id < 0) { continue; } else if (id == 0) { close(sockfd); if(fork()>0) exit(0);
serverio(newsockfd); close(newsockfd); exit(1); } else { waitpid(id,nullptr,0); close(newsockfd); } } return 0; }
|
线程池模式
serverpthread.cc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <cstring> #include <unistd.h> #include <cstdlib> #include <signal.h> #include <string> #include <sys/wait.h> using namespace std; #include <pthread.h> #include "threadpool.hpp" #include "Task.hpp" using namespace ns_task; using namespace ns_threadpool;
int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { cerr << "socket error" << endl; return 1; } struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(8080); local.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr *)&local, sizeof(local)) < 0) { cerr << "bind error" << endl; return 2; } if (listen(sockfd, 5) < 0) { cerr << "listen error" << endl; } while (1) { struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); socklen_t len = sizeof(peer); int newsockfd = accept(sockfd, (struct sockaddr *)&peer, &len); if (newsockfd < 0) { continue; } uint16_t cli_port = ntohs(peer.sin_port); string ip = inet_ntoa(peer.sin_addr); cout << "get a link -> : " << cli_port << " : " << newsockfd << " " << ip << endl; Task t(newsockfd); ThreadPool<Task>::GetInstance()->PushTask(t);
} return 0; }
|
threadpool.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
| #pragma once #include <iostream> #include <string> #include <queue> #include <unistd.h> #include <pthread.h>
namespace ns_threadpool { 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 &tp) = delete;
ThreadPool operator=(const ThreadPool &tp) = delete;
public: static ThreadPool<T> *GetInstance() {
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
if (ins == nullptr)
{
pthread_mutex_lock(&lock);
if (ins == nullptr) { ins = new ThreadPool<T>(); ins->InitThreadPool(); std::cout << "首次加载对象" << std::endl; } pthread_mutex_unlock(&lock); } return ins; }
~ThreadPool() { pthread_mutex_destroy(&mtx_); pthread_cond_destroy(&cond_); }
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();
sleep(1); } } void InitThreadPool() { pthread_t tid; for (int i = 0; i < num_; i++) { pthread_create(&tid, nullptr, Rountine, (void *)this); } } void PopTask(T *out) { *out = task_queue_.front(); task_queue_.pop(); } void Wait() { pthread_cond_wait(&cond_, &mtx_); } bool IsEmpty() { return task_queue_.empty(); } void Lock() { pthread_mutex_lock(&mtx_); } void UnLock() { pthread_mutex_unlock(&mtx_); } void Wakeup() { pthread_cond_signal(&cond_); } void PushTask(const T &in) { Lock(); task_queue_.push(in); UnLock(); Wakeup(); }
}; template <class T> ThreadPool<T> *ThreadPool<T>::ins = nullptr;
}
|
Task.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #pragma once #include <iostream> #include <pthread.h> #include"unistd.h" #include<cstring> using namespace std;
namespace ns_task { class Task { private: int _sockfd;
public: Task() : _sockfd(-1) { } Task(int sockfd) : _sockfd(sockfd) { }
~Task() { } int Run() { while (true) { char buf[1024]; memset(buf, 0, sizeof(buf)); ssize_t s = read(_sockfd, buf, sizeof(buf) - 1); if (s > 0) { buf[s] = 0; cout << "client # " << buf << endl; string echo = "server send "; echo += buf; write(_sockfd, echo.c_str(), echo.size()); } else if (s == 0) { cout << "client quit" << endl; close(_sockfd); break; } else { cerr << "error" << endl; close(_sockfd); break; } } }
int operator()() { return Run(); } }; }
|
popen
执行这个command,后以文件的方式来操作,读还是写操作
执行的结果在FILE* 这个文件指针里面,可以用一个字符串读取里面的内容接收,返回给客户端
执行完关闭
底层是管道,fork,文件方案拿到的
netstate
netstate -nltp
-t是tcp业务
-p是process进程