深圳工程建设信息网站,一个新的网站怎么做SEO优化,网络项目发布网,临沂网站建设公司 杨超学习任务#xff1a; 我们先来认识端口号#xff0c;区分好主机IP和端口号的区别#xff0c;以及涉及到进程PID和端口号的区别。 然后简单认识一下TCP协议和UDP协议#xff0c;这两个协议都是传输层的。接着了解什么是网络字节序#xff0c;它有什么作用。然后是网络编程的… 学习任务 我们先来认识端口号区分好主机IP和端口号的区别以及涉及到进程PID和端口号的区别。 然后简单认识一下TCP协议和UDP协议这两个协议都是传输层的。接着了解什么是网络字节序它有什么作用。然后是网络编程的一些接口。最后写代码简单实践一下。 目录
1、认识端口号区分IP/portPID/port
2、认识TCP协议认识UDP协议
3、认识网络字节序
4、socket编程接口
5、代码示例能够实现一个简单的udp客户端/服务器 1、认识端口号区分IP/portPID/port
IP地址公网IP是用来唯一标识互联网中的一台主机一台主机一个IP。而IP分源IP和目的IP源IP和目的IP对一个报文来讲是起从哪里来到哪里去的作用其最大的意义是指导报文该如何进行路径的选择而路径中每一个“站点”就是MAC地址的变化。
认识端口号port
数据从计算机A到达计算机B并不是真正的目的而是到达计算机B的某一个进程提供数据处理的服务才是网络传输数据最终的目的。
数据本身并不是由计算机产生的而是由人即用户通过特定的客户端等等输入进去的因此本质上所有的网络通信站在人的角度上就是人与人之间的通信这是一个比较好的理解方向站在计算机角度上是进程间通信只不过通信的进程不在一台计算机上。就比如抖音的app客户端它是一个进程抖音的服务器也是一个进程。我们通过抖音客户端达到网络通信在抖音的服务器上获取信息便是进程间通信。
而IP地址仅仅是解决了两台物理机器之间的相互通信的识别问题我们还要解决是在这两台计算机之间的进程间的通信就是怎么知道计算机A发出的信息是要传给计算机B中的某个进程呢这就需要端口号了 因此端口号的作用是唯一标识一台机器上的唯一一个进程通过IP端口号port就能够标识互联网中的唯一一个进程
我们可以将整个网络看成是一个大的OS所有的网络行为几乎都是在这一个大的OS进行进程间通信
既然说端口号port是进程的一个身份那么进程的PID按理论上来说也能通过PID来进行网络上的进程间通信那么为什么还需要一个port呢
区分IP/PORT,PID/PORT
上面我们已经很清楚了IP的标识物理机器的port是标识进程的。而PID也是用来标识进程的也是唯一性的其实PID跟port都属于进程的身份就好像学生由身份证也有他的学生证一句话来说将进程的PID和port分开来使用是为了解耦
一个进程可以关联多个端口号而一个端口号不能关联多个进程。
网络是一份共享资源
要在网络上进行进程间通信我们首先需要找到目标主机然后找到该主机上的服务进程完成进程间通信。我们可以说网络世界是一个进程间通信的世界。而进程要通信的话由于进程具有独立性因此不同的进程必须看到同一份资源即共享资源所有网络便是一份共享资源
2、认识TCP协议认识UDP协议
这里先简单得对TCP和UDP来一个直观的认识
TCP协议和UDP协议都是传输层的控制协议以下是两种协议的特定我们需要根据它们的特定在不同场景下权衡使用哪种协议。
TCP协议 *传输层协议 *有连接 *可靠传输 *面向字节流 YDP协议 *传输层协议 *无连接 *不可靠传输 *面向数据报 3、认识网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
网络数据流觉得这样分来分去太麻烦了这样吧我就使用大端的形式吧如果你的数据流本来就是大端那你就直接传输如果你的数据流是小端那么麻烦你先转换成大端再来传输
因此网络字节序指的就是在网络上的采用的大端形式先发出的数据是低地址,后发出的数据是高地址。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
总结一下网络字节序 ⭐发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出。 ⭐接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。 ⭐因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。 ⭐TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。 ⭐不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据。 ⭐如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。 4.socket编程接口
socket是套接字的意思用于描述IP地址和端口号是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。
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);
我们逐一来理解一下这些接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 服务器)
int socket(int domain, int type, int protocol); 第一个参数domain协议域。就是需要用哪种协议我们最常用的就两种-AF_INET (IPV4协议)AF_INET6 (IPV6协议)。 第二个参数套接字的类型即SOCK_STREAMTCP、SOCK_DGRAMUDP。 第三个参数这个我们置为0即可它是用来制定某个协议的特定类型即type类型中的某个类型。通常一种协议只有一种类型那样该参数可以直接被设置为0如果协议有多种类型则需要指定协议类型。 返回值返回一个文件描述符。 // 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len); 第一个参数socket函数返回的文件描述符。 第二个参数指定想要绑定的IP和端口。下面将分析sockadder结构体。 第三个参数address的长度。 返回值成功为0失败-1 sockaddr结构
网络通信的方式有很多种比如基于网IP的网络通信AF_INET原始套接字域间套接字等等。有那么多方式那么在绑定IP和端口的时候就需要很多种方法了因此系统需要将其统一一下结构就有了sockadder。 IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址。 IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。 socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。 sockaddr 结构 sockaddr_in 结构 虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。
我们简化看看表示IPV4的结构体
struct sockaddr_in
{sa_family_t sin_family;//地址族uint16_t sin_port;//TCP/UDP端口号16位整型struct in_addr sin_addr;//IP地址32位整型char sin_sero[8];//别管它了
};其中sin_famile:
地址族含义AF_INETIPV4网络协议中的使用的地址族AF_INET6IPV6网络协议中使用的地址族AF_LOCAL本地通信中采用的UNIX协议的地址族
in_addr结构 in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。
我们使用这两个函数再补充两个函数recvfrom和sendto就可以写一个示例了UDP的。 recvfrom适用于UDP协议
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen); 本函数用于从(已连接)套接口上接收数据并捕获数据发送源的地址 第一个参数套接字文件描述符 第二个参数指明一个缓冲区该缓冲区用来存放recvfrom函数接收到的数据 第三个参数buf的长度 第四个参数一般置0即false。 第五个参数是一个struct sockaddr类型的变量该变量保存源机的IP地址及端口号。 第六个参数第五个参数的sizeof 返回值成功返回接收到的字节数。失败返回1。 sendto适用于UDP协议
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen); ·第一个参数套接字文件描述符。 第二个参数指明一个存放应用程序要发送数据的缓冲区。 第三个参数buf的长度 第四个参数置为0吧。 第五个参数dest_addr表示目地机的IP地址和端口号信息 第六个参数dest_addr的长度 返回值成功返回接收到的字节数。失败返回1。 示例代码
实现一个网络通信功能在client中输入信息会在server中显示出来并且返回信息给client达到网络通信聊天的效果。
客户端client代码
#include iostream
#include string
#include cerrno
#include sys/socket.h
#include sys/types.h
#include netinet/in.h
#include arpa/inet.hvoid Usage(std::string proc)
{std::coutUsage: \n\tproc server_ip server_portstd::endl;
}int main(int argc,char *argv[])
{if(argc!3){Usage(argv[0]);return 0;}//1.创建套接字打开网络文件int socksocket(AF_INET,SOCK_DGRAM,0);if(sock0){std::cerr socket error : errno std::endl;return 1;}//客户端不需要显示bind。//首先客户端必须也要有IP和port//但是客户端不需要显示的bind。因为一旦显示bind就必须明确客户端client//要和哪个端口port关联。//而如果客户端client指明了端口号那么在客户端client不一定会有用因为//这个端口号有可能被占用了比如我们在联网的时候一边打LOL一边斗地主//被占用就会导致client无法使用//server要的是port必须明确而且不变但client只要有就行一般是由OS自动给你bind()// 就是client正常发送数据的时候OS会自动给你bind采用的是随机端口的方式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]);//使用服务while(1){//数据从我们键盘输入std::string message;std::cout输入# ;std::cinmessage;sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)server, sizeof(server));struct sockaddr_in tmp;socklen_t len sizeof(tmp);char buffer[1024];recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)tmp, len);std::coutserver echo# bufferstd::endl;}return 0;
}
服务器server代码
#includeiostream
#includestring
#includecerrno
#includesys/types.h
#includesys/socket.h
#includenetinet/in.h
#includearpa/inet.hconst uint16_t port 8080;int main()
{//1.创建套接字打开网络文件int sock socket(AF_INET,SOCK_DGRAM,0);if(sock0){std::cerrsocket create error: errnostd::endl;return 1;}//2.给该服务器绑定端口和ipstruct sockaddr_in local;//填充字段local.sin_family AF_INET;local.sin_port htons(port);//此处的port是端口号是计算机上的变量//是属于主机序列说明需要主机转网络的操作htons()//需要将人识别的点分十进制字符串风格IP地址转化成为4字节整数IP //需要考虑大小端,因此使用in_addr_t inet_addr(const char *cp);//local.sin_addr.s_addr inet_addr(43.139.32.198);//点分十进制【0-255】//我们不能像上面这行代码一样直接绑定bind某个IP因为如果指定绑定一个IP那么//只有发送到该IP主机上的数据才会交给你的网络进程//但是服务器一般会配置很多个网卡有很多个IP。//因此作为服务器我们需要的不是某个IP上面的数据//而是需要所有发送到该服务器主机上的某个端口的数据//使用INADDR_ANY,不绑定指定IPlocal.sin_addr.s_addr INADDR_ANY;//绑定IP和端口if(bind(sock,(struct sockaddr*)local,sizeof(local))0){std::cerrbind error: errnostd::endl;return 2;}//3.提供服务bool quit false;#define NUM 1024char buffer[NUM];while(!quit){struct sockaddr_in peer;//保存接受到的数据的空间socklen_t len sizeof(peer);//空间的大小recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)peer,len);std::coutclient# bufferstd::endl;std::string echo_hello hello;//服务器发送回给用户表示收到消息了sendto(sock,echo_hello.c_str(),echo_hello.size(),0,(struct sockaddr*)peer,len);}return 0;
}
接下来我们改造一下代码实现一个功能在client中输入bash命令在server中执行命令并且将执行结果返回到客户端client中。实现了简单的xshell。
client的代码
#include iostream
#include string
#include cerrno
#include sys/socket.h
#include sys/types.h
#include netinet/in.h
#include arpa/inet.h
#includecstringvoid Usage(std::string proc)
{std::cout Usage: \n\t proc server_ip server_port std::endl;
}// ./udp_client server_ip server_portint 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::coutMyShell $ ;char line[1024];fgets(line,sizeof(line),stdin);sendto(sock, line, strlen(line), 0, (struct sockaddr*)server, sizeof(server));//此处tmp就是一个”占位符“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(cnt0){//在网络通信中只有报文大小或者是字节流中字节的个数//没有C/C字符串这样的概念buffer[cnt] 0;//添加\0std::cout buffer std::endl;}else{//TODO}}return 0;
}
server的代码
#include iostream
#include string
#include cerrno
#include cstdio
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h//const uint16_t port 8080;
std::string Usage(std::string proc)
{std::coutUsage: procportstd::endl;
}
// udp_server,细节最后在慢慢完善
// ./udp_server port
int main(int argc,char *argv[])
{if(argc!2){Usage(argv[0]);return -1;}uint16_t port atoi(argv[1]);//argv[1]是字符串类型需要转成整型//1. 创建套接字打开网络文件int sock socket(AF_INET, SOCK_DGRAM, 0);if(sock 0){std::cerr socket create error: 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_t inet_addr(const char *cp); 能完成上面ab两个工作.// 坑: // 云服务器不允许用户直接bind公网IP另外, 实际正常编写的时候我们也不会指明IP// local.sin_addr.s_addr inet_addr(42.192.83.143); //点分十进制字符串风格[0-255].[0-255].[0-255].[0-255]// 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 error : errno std::endl;return 2;}//3. 提供服务bool quit false;#define NUM 1024char 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(cnt0){buffer[cnt] 0;//0\0FILE *fp popen(buffer,r);std::string echo_hello;char line[1024] {0};while(fgets(line,sizeof(line),fp)!NULL){echo_helloline;}//这里可以选择判断是否读到了文件结尾// if(feof(fp))// {// //读取结果完成// }pclose(fp);std::cout client# buffer std::endl;//根据用户输入构建一个新的返回字符串//echo_hello...;sendto(sock, echo_hello.c_str(), echo_hello.size(), 0, (struct sockaddr*)peer, len);}else{//TODO}}return 0;
}