目录
1 前言 1
2 原理 2
2.1 通信原理 2
3 IO模型 10
3.1 IO模型原理介绍及优势劣势分析 10
3.2 不同平台的差异 10
4 多线程模型 10
4.1 select+多线程 10
4.2 IOCP 10
4.3 epoll 10
4.4 reactor设计模式 10
4.5 preactor设计模式 10
5 协议 10
5.1 介绍 10
5.2 举例 10
6 服务器的关键技术高性能,高可用 10
7 学习方法分享 11
8 书籍推荐 11
linux socket内核源码实现:【Linux 内核网络协议栈源码剖析】socket.c--BSD Socket层(1)
前言
我虽然不是软件行业的编程大牛,但也是对编程事业十分热爱的群体成员之一,尤其是对网络编程情有独钟;经常被学弟们问到这个问题,有时候时间仓促没有说清楚怕反而耽误别人,于是我准备专门写一篇文章来回答这个问题,也算是对自己从业三年多的一个总结!知识有限,仅供参考!一次没写完,后期慢慢完善,纯个人经验,不按照专业体系来!愿对编程热爱的人永远保持热爱!
原理
通信原理
在没有网络通信的时候,各个电脑的软件都是各自为战,老死不相往来,那样的网络世界不能充分利用数据资源资源,极其枯燥乏味!
![b80891701759d79094144c9c338f2668.png](https://i-blog.csdnimg.cn/blog_migrate/77d0f9012d0c83ca76c4a705e32caafa.png)
上图是与世隔绝的计算机世界,没有网络,各个设备之间无法直接沟通;人们只能玩单机游戏,只能看自己电脑上存储的片,也不能及时沟通;于是蒂姆·伯纳斯·李苦思冥想就觉得应该建立网络这个东西,大家不同出门就可以相互沟通,可以共享各种信息,一起玩游戏;于是计算机世界就发生了天翻地覆的变化。
![10f8363fd0d83ae29ffeabecf04b59ea.png](https://i-blog.csdnimg.cn/blog_migrate/c2bb64fdf351d411f3dbd3f21d3d49b6.png)
网络把世界上所有的设备连接在一起,从那时候起,人们就可以共享数据资源,一起玩游戏和及时沟通了;但这里有一个问题,大家都只想买个便宜的电脑,也不像购买太多的存储空间来存储数据,于是大家就上来出来了一个方法,我们应该把最浪费cpu的计算,大堆的数据存储放到公共的电脑上,于是就出现了服务这种东西。
![e7bd5c76bea673a910c451d376d09dfc.png](https://i-blog.csdnimg.cn/blog_migrate/9ef51b35020713d88c295e0c2a7e9396.png)
服务器的出现使得整个互联网世界生活变得更加丰富多彩,这里的服务器包括服务器硬件和服务器程序;服务器硬件就是一个电脑主机,只是它的cpu、内存、磁盘、网卡等硬件配置都远高于一般的个人电脑的电脑,需要7x24小时的运行,肩负着为所有客户端程序提供服务的使命,而且大公无私地存储着所有用户需要的数据,硬件好一点情有可原。有了服务器,客户端就变爽了,只需要很垃圾的硬件配置,就可以在线观看互联网中的任何视频,和朋友们一起玩游戏,及时沟通。
网络中有那么多设备,我们如何精准地找到你心目中的她呢?我们人与人之间是用姓名或者身份证号来标识,而网络世界中我们用ip地址来唯一确定网络的设备;不过就算你找到你想连接的服务器设备,仍然无法正常沟通,因为服务器上运行着很多程序,服务器不知道你需要那个服务,于是你还需要指定服务端口;ip地址标记了互联网世界中的一台设备,而端口标记了一台设备中的唯一服务程序;反过来服务器需要给你答复,那么它也需要知道你(客户端)的ip地址和端口,如下图:
![b1071c1ec24da2e06dc505eb363e6097.png](https://i-blog.csdnimg.cn/blog_migrate/e90a1d779a62ba93c1cbe663cf07604a.png)
怎么来标识互联网中的唯一对话,那就一个网络全相关:本地ip,本地端口,远程ip,远程端口,通信协议。找到了互联网中的电脑,找到了电脑中运行的那个程序,还是不能正常通信,还需要大家都使用相同的协议;好比你打电话去咨询别人,然后前台帮你转接到了咨询目标,然后还需要大家都说中文(通信协议,比如tcp、udp、sctp)。百度百科对通信五元组的介绍:https://baike.baidu.com/item/%E4%BA%94%E5%85%83%E7%BB%84
说了那么多似乎跟网络编程无关的东西,接下来终于可以讲到怎么用socket编程来建立一个网络全相关了,然后进行正常的数据交互了。
![24ee1d6579859af9cb1491da089519e2.png](https://i-blog.csdnimg.cn/blog_migrate/b784b7ab5d500be53fc45693fa8c7076.jpeg)
服务器这边首先调用int socket(int domain,int type, int protocol)创建一个套接字句柄,比如是int sockfd = socket(int domain,int type, int protocol),这个接口会指定通信协议比如tcp;然后调用int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)绑定本地ip(本机网卡)和端口(服务器进程标识),此时相当于建立了一个网络半相关,socket只标识了通信协议、本机ip、本机端口,因此还无法正常通信;然后就是调用int listen(int sockfd,int backlog)开启监听连接情况;再就是int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)接受客户端的连接,这个函数会返回一个新的socket句柄,int nclient = accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen),这个nclient是一个全相关,包括通信协议、本机ip、本机端口、远程ip、远程端口,通信协议、本机ip、本机端口是从监听套接字sockfd复制出来的,远程ip和远程端口是从连接中取得的,现在就可以正常通信了;调用ssize_t recv(int sockfd,void *buf, size_t len,int flags)就可以接收数据;调用ssize_t send(int sockfd,constvoid *buf, size_t len,int flags)就可以向对端发送数据;通信完成了就调用close把这个连接关闭;服务器工作还没完,这才完美服务完一个客户端的通信过程,还得继续监听其它客户端的连接,直到服务器关机。套接字接口的参考文章:https://blog.csdn.net/G_BrightBoy/article/details/12854117。
客户端这边与服务器操作类似,只是不需要监听连接情况,也不需要一直运行。首先调用int socket(int domain,int type, int protocol)创建一个套接字句柄,比如是int sockfd = socket(int domain,int type, int protocol),这个接口会指定通信协议比如tcp;然后直接调用int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)连接到服务器,需要指定要连接服务器的ip和端口号,但是不需要指定本机的ip和端口号,ip操作系统会帮你绑定,端口号操作系统也会帮你随机分配一个;然后就是调用ssize_t recv(int sockfd,void *buf, size_t len,int flags)就可以接收数据;调用ssize_t send(int sockfd,constvoid *buf, size_t len,int flags)就可以向对端发送数据;通信完成了就调用close把这个连接关闭。
代码示例:https://blog.csdn.net/guo8113/article/details/26448011。
Server:
// server.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <winsock2.h>
#include <iostream>
#include <string.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib") //add ws2_32.lib
const int DEFAULT_PORT = 8000;
int main(int argc,char* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
int err,iLen;
wVersionRequested = MAKEWORD(2,2);//create 16bit data,网络动态库版本
//(1)Load WinSock,加载网络动态库
err = WSAStartup(wVersionRequested,&wsaData); //load win socket
if(err!=0)
{
cout<<"Load WinSock Failed!";
return -1;
}
//(2)create socket,准备连接全相关的内存资源
SOCKET sockSrv = socket(AF_INET,SOCK_STREAM,0);
if(sockSrv == INVALID_SOCKET){
cout<<"socket() fail:"<<WSAGetLastError()<<endl;
return -2;
}
//(3)server IP,制定本机ip
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_addr.s_addr = htonl(INADDR_ANY);//Auto IP, byte sequence change
addrSrv.sin_port = htons(DEFAULT_PORT); //指定本机端口
//(4)bind,将ip和端口复制到套接字指向的内存块
err = bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
if(err!=0)
{
cout<<"bind()fail"<<WSAGetLastError()<<endl;
return -3;
}
//
//(5)listen,开启监听
err = listen(sockSrv,5);
if(err!=0)
{
cout<<"listen()fail"<<WSAGetLastError()<<endl;
return -4;
}
cout<<"Server waitting...:"<<endl;
//(6)client ip
SOCKADDR_IN addrClt;
int len = sizeof(SOCKADDR);
while(1)
{
//(7)accept,客户端连接进来,建立全相关连接了;也就是得到了远端ip和端口
SOCKET sockConn = accept(sockSrv,(SOCKADDR*)&addrClt,&len);
char sendBuf[1024],hostname[100];
if(gethostname(hostname,100)!=0) //get host name
strcpy(hostname,"None");
sprintf(sendBuf,"Welecome %s connected to %s!",inet_ntoa(addrClt.sin_addr),hostname);
//(8)send recv
err = send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[1024]="0";
iLen = recv(sockConn,recvBuf,1024,0);
recvBuf[iLen]='0';
cout <<recvBuf<<endl;
//(9)close connected sock
closesocket(sockConn);
}
//(10)close server sock
closesocket(sockSrv);
//(11)clean up winsock
WSACleanup();
return 0;
}
客户端:
// client.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <winsock2.h>
#include <iostream>
#include <string.h>
using namespace std;
#pragma comment(lib, "ws2_32.lib") //add ws2_32.lib
const int DEFAULT_PORT = 8000;
int main(int argc,char* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
int err,iLen;
wVersionRequested = MAKEWORD(2,2);//create 16bit data
//(1)Load WinSock
err = WSAStartup(wVersionRequested,&wsaData); //load win socket
if(err!=0)
{
cout<<"Load WinSock Failed!";
return -1;
}
//(2)create socket
SOCKET sockClt = socket(AF_INET,SOCK_STREAM,0);
if(sockClt == INVALID_SOCKET){
cout<<"socket() fail:"<<WSAGetLastError()<<endl;
return -2;
}
//(3)IP
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");
addrSrv.sin_port = htons(DEFAULT_PORT);
//(5)connect
err = connect(sockClt,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
if(err ==INVALID_SOCKET)
{
cout<<"connect() fail"<<WSAGetLastError()<<endl;
return -1;
}
char sendBuf[1024],hostname[100];
if(gethostname(hostname,100)!=0) //get host name
strcpy(hostname,"None");
strcpy(sendBuf,hostname);
strcat(sendBuf,"have connected to you!");
err = send(sockClt,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[1024]="0";
iLen = recv(sockClt,recvBuf,1024,0);
if(iLen ==0)
return -3;
else if(iLen==SOCKET_ERROR){
cout<<"recv() fail:"<<WSAGetLastError()<<endl;
return -4;
}
else
{
recvBuf[iLen] = '0';
cout<<recvBuf<<endl;
}
closesocket(sockClt);
WSACleanup();
system("PAUSE");
return 0;
}
IO模型
IO模型原理介绍及优势劣势分析
不同平台的差异
多线程模型
select+多线程
IOCP
深入理解DMA技术原理:操作系统--I/O设备管理(1) - yaoxiaokui的专栏 - CSDN博客
异步IO实际上就是使用了DMA(direct memory access)技术;
DMA原理:DMA控制器直接将数据复制到内存中,复制完成之后才通知cpu干活;
中断技术原理:cpu提交任务给中断控制器,中断控制器将数据从磁盘复制到中断控制器缓存,然后中断控制器通知cpu来从缓存中把数据复制到用户内存;最坑的是循环IO模式;
中断和DAM使用了不同的控制器;DMA通知cpu干活的时机更加节约cpu,也更节约内存;
各个设备都有自己的寄存器,寄存器分为三类:数据寄存器、控制寄存器、状态寄存器;
epoll
reactor设计模式
preactor设计模式
协议
介绍
举例
TCP/IP协议
服务器的关键技术高性能,高可用
学习方法分享
先对网络编程有一定的概念,做一个简单的demo,不断网上面加功能;能到公司干活的水平了,再去看系统的书籍。
书籍推荐
《unix网络编程》