网络编程

来源:互联网 发布:淘宝页头怎么装修 编辑:程序博客网 时间:2024/04/28 06:40

以前做的服务器项目偏重业务层,网络通信调用的网络组编写的库,因此不太熟悉,现在补充一下。
网络通信本质属于一种远程进程间通信。

目的:

目前服务端开源的io库非常多,不需要都掌握。
重在掌握基本概念,能达到触类旁通。
重点是TCP/UDP区别、Socket流程及常用API、系统了解几种网络IO方法

基础知识复习

进程间通信(IPC,InterProcess Communication)

本地进程间通信:(掌握消息队列和共享内存吧,太多也记不住)
1)管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程之间使用。进程的亲缘关系通常是指父子进程关系。
2)有名管道(FIFO):有名管道也是半双工的通信方式,但是允许在没有亲缘关系的进程之间使用,管道是先进先出的通信方式。
3)信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
4)消息队列:消息队列是有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5)信号 ( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
6)共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
远程(不同机器):Socket

https://www.cnblogs.com/CheeseZH/p/5264465.html

都分为 创建-读/写-关闭 几个步骤。比如共享内存的例子

https://www.cnblogs.com/youngerchina/p/5624525.html
通过共享内存的指针来读写操作

//创建共享内存shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT); //sizeof(struct shared_use_st)指定共享内存大小,struct shared_use_st为一个预先定义好的结构体//将共享内存连接到当前进程的地址空间shm = shmat(shmid, 0, 0);//设置共享内存指针struct shared_use_st *shared = (struct shared_use_st*)shm;//读取共享内存中的数据printf("You wrote: %s", shared->text);//把共享内存从当前进程中分离shmdt(shm);//删除共享内存shmctl(shmid, IPC_RMID, 0);

TCP UDP定义及区别:

1)TCP提供面向连接的传输,通信前要先建立连接(三次握手机制),因此提供可靠的传输
UDP提供无连接的传输,通信前不需要建立连接,因此提供不可靠的传输。。
2)TCP**面向字节流的传输,因此它能将信息分割成组,并在接收端将其重组;UDP是面向数据报**的传输,没有分组开销。

TCP UDP的优缺点

tcp可靠,但占用资源多。
udp不可靠,但占用资源少。在大量短消息、对信息安全要求不高或网络比较阻塞时适用。

TCP三次握手、四次挥手

三次握手:
首先Client端发送连接请求报文。
Server段接受连接后回复ACK报文,并为这次连接分配资源。
Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。
四次挥手:
假设Client端发起中断连接请求,也就是发送FIN报文。
Server端接到FIN报文后,意思是说”我Client端没有数据要发给你了”,但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK。
当Server端确定数据已发送完成,则向Client端发送FIN报文,”告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。
Client端收到FIN报文后,发送ACK,Server端收到ACK后,”就知道可以断开连接了”。

基于Socket的TCP UDP区别

从下图中可以看出udp相比tcp没有listen()、connect()、Accept()函数(这几个函数用来建立连接)
这里写图片描述
这里写图片描述
常用的Socket类型有两种:流式Socket(SOCK_STREAM)、服务于TCP和数据报式Socket(SOCK_DGRAM)、UDP。

基于TCP/Socket自定义应用层协议

通过Socket传输的数据可以分为:包头+数据正文。
包头里面可以包含:
服务器IP、端口;
数据正文长度(告诉服务器接收多长的数据正文);
同步码(区分每一个有效的数据包,告诉服务器从哪边开始接收)
校验位(证明接收的数据是无误的)
数据包类型,可以指定数据包用来的目的

网络io,同步异步,阻塞非阻塞

https://www.cnblogs.com/Anker/p/3254269.html
http://blog.csdn.net/zh13544539220/article/details/44856363

这两个帖子写的很具体了,对照着看。
个人理解分为四种:
最后,再举几个不是很恰当的例子来说明这四个IO Model:
有A,B,C,D四个人在钓鱼(服务器等待接收数据):
A用的是最老式的鱼竿,所以呢,得一直守着,等到鱼上钩了再拉杆;(阻塞io)
B的鱼竿有个功能,能够显示是否有鱼上钩,所以呢,B就和旁边的MM聊天,不断地主动看看有没有鱼上钩,有的话就迅速拉杆(非阻塞io)
C用的鱼竿和B差不多,但他想了一个好办法,就是同时放好几根鱼竿,然后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来(不断的轮询所负责的所有socket,IO多路复用);
D是个有钱人,干脆雇了一个人帮他钓鱼,一旦那个人把鱼钓上来了,就给D发个短信(异步io)。
其中,由于异步io信号个数有限,多个描述符时不适用。较常用的是IO多路复用。

epool与select的区别

问题的引出,当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种:

1.使用多进程或者多线程,但是这种方法会造成程序的复杂,而且对与进程与线程的创建维护也需要很多的开销。(Apache服务器是用的子进程的方式,优点可以隔离用户)

2.用一个进程,但是使用非阻塞的I/O读取数据,当一个I/O不可读的时候立刻返回,检查下一个是否可读,这种形式的循环为轮询(polling),这种方法比较浪费CPU时间,因为大多数时间是不可读,但是仍花费时间不断反复执行read系统调用。

3.异步I/O(asynchronous I/O),当一个描述符准备好的时候用一个信号告诉进程,但是由于信号个数有限,多个描述符时不适用

4.一种较好的方式为I/O多路转接(I/O multiplexing)(貌似也翻译多路复用),先构造一张有关描述符的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的

select的例子

http://blog.csdn.net/timmiy/article/details/52123755
http://blog.csdn.net/leo115/article/details/8097143

简单的说,
我们在TCP的服务端里边,接收一个客户端的时候,我们调用accept函数,这个函数会返回一个客户端的socket,我们在主线程里边不停的接收客户的连接,每当有客户连接时,我们就会在开一个线程(因为recv操作是阻塞的,很耗时),用于对客户的服务。因此,如果有N个的客户进行连接的话,那么线程数量就会有N+1个(N个服务线程+主线程),若N比较大,则线程就会非常多,以至于将整个电脑都给拖垮掉。而我们的select模型呢,就是为了解决这个问题而设计的。接下来看下它是如何实现的。
服务端accept()后获得一个客户端的socket,把所有客户端的socket放入数组中,然后select函数去轮询获得数组中哪一个socket有数据来,然后对那一个socket调用recv函数

经典开源io:thrift

http://blog.csdn.net/sunmenggmail/article/details/46818147

从中也可以看出thrift体现了网络io的几种常见方法,比如阻塞式io+多线程、io多路复用等。

经典的开源io:libevent

经典的开源io:asio

原创粉丝点击