一、网络编程介绍
c++编程的应用场景在前面分析过,一个重要的方向就是网络编程。一般来说,开发者说的服务端编程在c++方向上简单的可以认为是网络编程。首先需要说明的,本系列不对网络编程的相关基础知识展开详细的说明,因为这种知识在书本上太多了。网络上各种资料更是满开飞,没有必要拷贝来拷贝去的。特别是一些协议等的解析说明,如果不遇到特定的问题不会深入分析说明。
那么最应该明白的网络编程是什么?那么就得明白网络是如何而来。网络,从名字上很好理解,一张把"经络"连接起来的大网。不过这个经络不是人体中的"经络"而是一个个节点,这个节点可以是虚拟的,也可以是物理的,也可以是混合的。它可以是一台电脑,一个手机,一个终端,也可以是一个局域网、城域网等等。
计算机的网络技术在计算机技术中算是比较早期的一种技术了,在60年代的中期就已经开始在实际应用了。但真正的普及是美国的国防用网络。早期学习电脑的或者看过早期电影的,都听说过某某黑客特别厉害,进入了五角大楼的网络,盗取了不少军事资料。可以这样讲,计算机的网络技术也是从美国开始兴起的,然后在全世界开始普及。这也解释了为什么现在最牛的互联网公司基本都在美国的一个重要原因。比如耳熟能详的谷歌、微软、脸书以及推特等等。
随着PC的出现和发展,局域网(LAN)出现。美国Xerox公司首先推出了Ethernet网,慢慢其成为了一种标准,大家都称现在的局域网络为以太网。有了局域就会有广域网WAN。不过需要说明的是,所谓局域与广域是一个相对概念,请大家一定要根据实际场景来确定。
网络技术其实就是处理PC间连接通信的技术。从物理上讲,如何识别网络中的PC,如何与其它PC交换数据等等。首先需要用物理导线将各个PC连接起来,一开始是电缆,后来光缆,再后来又有无线技术。然后还要有路由器和交换机把数据将有的传送到指定的PC。而为了实现上述的功能,就需要一系列的通信标准和通信协议。这就引出了网络协议的五层模型(七层就是个学术的东西,没啥实际应用的意义)。而这个模型中,则包含是最常见的网络编程中的TCP/IP、UDP、HTTP等最常见的网络编程技术。或者说的不准确一些,对大多数的网络编程人员来说,就是TCP/IP和UDP编程。在移动互联网中,HTTP则更为普及的被使用。至于其它的技术,基本都是相当专业的人员或者特定领域的开发者才会使用。
二、基本知识
这里不谈较老的技术和很新的技术,比如QUIC和HTTP3等。在网络编程中,可以分成两大类应用,即B/S开发和C/S开发(P2P以后专门讲),这里只谈C/S开发。即本系列主要针对C/S开发中的TCP/IP编程以及UDP的编程。只要掌握了它们的编程,其它的编程基本都差不多。在TCP/IP和UDP编程中,需要掌握一些基本的知识:
1、服务器
这个概念是一个非常容易混淆的概念,一定要区别在不同的语境和环境下的定义。在网络编程的语境下,一般是指承载网络服务软件的服务器电脑(硬件)。它可以分成网络内部自用,比如路由器、交换机等也可以只提供某种网络服务的电脑如打印服务器、邮箱服务器等。
2、服务端
服务端或服务端软件,也可以叫网络服务,在特定到C/S编程中,就是指提供连接服务的程序。一般来说,服务端是被接收连接的。
3、客户端
客户端在C/S编程中指发起连接的一端。
4、协议栈
协议栈(Protocol stack),又称协议堆叠,是计算机网络协议套件的一个具体的软件实现。
5、伯克利套接字
伯克利套接字(Berkeley sockets),也称为BSD Socket。其是一种使用C语言实现的网络编程抽象接口。现在几乎成为了互联网通信的标准接口。
6、五元组和三元组:
五元组包括:源IP地址,源端口,目的IP地址,目的端口和传输层协议。这等同于现实世界中的人和人之间的通信地址。
7、协议族
socket函数中的第一个参数中意义,也叫协议域。通常有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等。协议族确定socket的地址类型,即双方必须使用相同的通信类型才可以进行以通信。如常见的AF_INET需要用32位(类似192.168.0.1)ipv4地址与16位端口号(最大65535)的组合、AF_UNIX需要用一个绝对路径名作为地址。
当然,还有很多的基础性的知识和名词术语。网络技术是一个发展了很多年的技术,它既成熟又年轻。举一个简单的例子,当有一个人说他是搞服务端编程的,如何确定他的技术栈?其实这个定义非常难确定,网上一些大牛的说明其实也不能够完全覆盖相关的内容,即他们的定义也是不严谨的。但仅从经验和学识来推断,特定到C/c++中,它一般是指TCP/IP编程的相关技术栈(当然,它也不严谨)。
再举一个实际的例子,大家去品一下上面这段话,至于能理解多少看自身了。在某电力部门,要求把服务端程序部署在终端上,把客户端程序部署在服务器上。客户端要24*7运行,服务端可以允许断线。
注意:再次说明,本系列不是对网络编程技术基础知识的详细分析说明,是对c++在网络编程上的应用分析说明,所以只对相关的一些知识点进行指出和简要的说明。更多的相关知识,请自行查阅下面提供的书籍和资料!
三、简单示例
虽然网络编程的例子多之又多,但这里还是要给一个简单的例子:
服务端:
c
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[1024] = {0};
const char* msg = "hello moto!";
//创建socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
//设置选项
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
//地址设置
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( 8888 );
//绑定端口
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("bind err");
exit(EXIT_FAILURE);
}
//监听
if (listen(server_fd, 5) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
//接受连接
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("accept err!");
exit(EXIT_FAILURE);
}
send(new_socket , msg , strlen(msg) , 0 );
printf("send msg ...\n");
memset(buffer, '\0' , 1024);
int ret = recv( new_socket , buffer, 1024,0);
printf("%s\n",buffer );
return 0;
}
客户端:
c
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
int main() {
int sock = 0, ret;
struct sockaddr_in serv_addr;
const char* msg = "hello !";
char buffer[1024] = {0};
//创建socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8888);
//转换地址
if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr)<=0) {
printf("\n Invalid address \n");
return -1;
}
//连接server
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\n Connection err \n");
return -1;
}
send(sock , msg , strlen(msg) , 0 );
printf("send msg !\n");
ret = recv( sock , buffer, 1024,0);
printf("%s\n",buffer );
return 0;
}
写这种测试的小例程有一个需要注意的地方,客户端发送完成后,不要立即退出,否则可能服务端收不到相关的消息。
四、推荐的资料和书籍
一如在VC编程上侯捷教师是一个令开发者仰望的山峰,在网络编程也有很多更高的高峰。比如常见的推荐的《TCP/IP详解》三卷和《Unix网络编程》两卷的作者W. Richard Stevens(当然他写的APUE也相当的出名)。不过这些书的缺点也有,就是太老了。导致一些技术已经落后而一些新技术没有体现出来。其它国外的还有不少就不一一列举。国内也有几个比较有名气的网络开发者,限定到本文这个场景非常推荐MUDUO库的作者陈硕。当然就像江湖中一样,肯定还有很多高手隐身不出。
学习网络编程的书籍非常多,比如CSDN的孟岩大佬的"四书五经"之说就是为多数大牛推荐的。这里简单罗列一下:
1、TCP/IP详解(三卷)(TCP/IP Illustrated)
2、Unix网络编程(两卷)(UNIX Network Programming)
3、TCP/IP高级编程(Effective TCP/IP Programming)
4、C++网络编程(两卷)(C++ Network Programming)
虽然这些书籍非常不错,但对于初学者未必就合适,也推荐一些比较容易借鉴学习的书籍:
1、《Linux多线程服务端编程》 陈硕
2、《Linux高性能服务端编程》 游双
3、《Windows网络与通信程序设计》 王艳平
并不是说其它的书籍不值得推荐,是觉得这几本书更容易被学习和接受。至于网络上的资料就更多了,如陈硕、原网易的云风等人的BLOG都非常值得一看。个人的建议是,要根据自己的实际情况来决定学习成长的路线,不要人云亦云。大牛们给的建议可能对大多数人都是非常好的,但具体到某些个体,可能会有所不妥。大家要知道如何不断的根据大牛们的建议因地制宜的学习。
另外在网上还存在着大量的网络框架如C++网络编程中的ACE,还有libevent,libuv,libev,libeio,libhv,asio,poco等等。毕竟网络应用是一个非常高频的应用,也是很多开发者想登顶的希望。
其实还有很多应用程序中也有非常好的例子,比如REDIS,有时间推荐看看内部如何跨平台实现了网络服务端的编程。
五、总结
网络编程是一个复杂的应用,一般来说,很难在一两年内达到熟练掌握的程度,更不要谈精通了。通常,把基础的网络知识学习完成,头脑中有一个相对完整的网络编程概念,然后在实际应用中不断的加以印证,才能更快更好的掌握网络编程。
网络编程其实是一个简单应用易,复杂应用极难的技术。它不仅是涉及到网络相关的技术,还包括内存管理、多线(进)程以及异步编程等很多技术,甚至是否需要跨平台跨系统等。对大多数开发者言,网络编程的应用一般都是比较简单的应用,并发通常也就是十个量级左右,而且经常类似于交互式通信那种情况。对多线程和异步的要求不高甚至没有,对内存管理和效率的要求也不严格。
但当真正到了C10K以上的编程时,复杂程度立刻便上来,导致很多开发者没有一个过渡便直面这些复杂的应用。也就是说,在网络编程大多数的编程场景是要么简单,要么复杂,中等的开发场景非常少。而且从设计上考虑,一旦到了中等的场景,优秀的架构师通常会考虑扩展的情况下设计成更为复杂的框架结构。
复杂的网络编程,导致很多的框架的出现,而这些框架的出现更是切断了大多数开发者对背后复杂结构的理解,导致一个从初级网络编程到高级网络编程的连续而完整的流程,即要么只会简单的编程,要么只会在框架下完成各种场景的应用。
更何况,网络编程的实际要求仍然在不断的增长,这也是前面分析DPDK和XDP等的一些重要原因。换句话说,网络编程的技术仍然在不断的进步。所以,不断的学习才能保证在网络编程的方向上有更大的发展。