37
架架架架架 < 架架架架 > 架架 架架nekeyzhong 架架2009 架 8 架 21 架

架构平台部 < 网络通讯 >培训

  • Upload
    amos

  • View
    170

  • Download
    13

Embed Size (px)

DESCRIPTION

架构平台部 < 网络通讯 >培训. 讲师: nekeyzhong 日期:2009年 8 月 21 日. 课程简介. 网络通讯,socket实现tcp,udp。以及select,epoll等功能函数应用. 背景知识回顾. OSI 七层模型: tcp/ip 五层模型:. tcp/ip 四层模型:. Unix基本Socket API介绍1. Unix基本Socket API介绍2. // 客户端 struct sockaddr_in servaddr; int sockfd = socket(AF_INET, SOCK_STREAM, 0); - PowerPoint PPT Presentation

Citation preview

Page 1: 架构平台部 < 网络通讯 >培训

架构平台部< 网络通讯 > 培训

讲师: nekeyzhong日期: 2009 年 8 月 21 日

Page 2: 架构平台部 < 网络通讯 >培训

课程简介

网络通讯,socket实现tcp,udp。以及select,epoll等功能函数应用

Page 3: 架构平台部 < 网络通讯 >培训

背景知识回顾

tcp/ip 四层模型 :

OSI 七层模型 :tcp/ip 五层模型 :

Page 4: 架构平台部 < 网络通讯 >培训

Unix 基本 Socket API 介绍 1

API 作用socket 创建 socket

connect 连接服务器send/write 发送数据recv/read 接收数据bind 绑定监听端口,一般用于服务器listen 监听端口,用于服务器accept 服务器接收客户端连接select 检查可用文件描述符

Page 5: 架构平台部 < 网络通讯 >培训

Unix 基本 Socket API 介绍 2

API 作用sendto 发送 UDP 包recvfrom 接收 UDP 包close 关闭 socket

Page 6: 架构平台部 < 网络通讯 >培训

简单的 TCP 客户端代码

// 客户端struct sockaddr_in servaddr;

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

servaddr.sin_family = AF_INET;

servaddr.sin_port =htons(18000);

servaddr.sin_addr.s_addr = inet_addr("192.168.1.100");

connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

write(sockfd,"hello",strlen("hello"));

memset(szRecv,0,sizeof(szRecv));

int n = read(sockfd,szRecv,sizeof(szRecv));

printf("%s\n",szRecv);

close(sockfd);

Page 7: 架构平台部 < 网络通讯 >培训

简单的 TCP 服务器代码

int sockfd = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server;

server.sin_addr.s_addr = INADDR_ANY;

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(18000);

bind(listenfd, &servaddr, sizeof(servaddr));

listen(listenfd, 10);while(1){

clilen = sizeof(cliaddr);

int newclientfd = accept(listenfd, &cliaddr, &clilen);

n = read(newclientfd ,sRecv,sizeof(sRecv));

if(n == 0) close(newclientfd );

n = write(newclientfd , sRecv, n);

close(newclientfd );

}

Page 8: 架构平台部 < 网络通讯 >培训

简单的 UDP 客户端代码char buf[1024];servaddr.sin_family = AF_INET;struct sockaddr_in server;server.sin_port = htons(8888);server.sin_addr.s_addr = inet_addr("192.168.1.100");int sockfd = socket(AF_INET,SOCK_DGRAM,0);while(1){

strcpy(buf,"hello");int ret = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr *)&server,sizeof(struct sockaddr_in));socklen_t len = sizeof(struct sockaddr_in);ret = recvfrom(sockfd,buf,1024,0,(struct sockaddr *)&server,(socklen_t*)&len);

printf("redv:%s\n",buf);}

Page 9: 架构平台部 < 网络通讯 >培训

简单的 UDP 服务器代码struct sockaddr_in server ;

servaddr.sin_family = AF_INET;

server.sin_port = htons(8888);

server.sin_addr.s_addr = INADDR_ANY;

struct sockaddr_in client;

sockfd = socket(AF_INET,SOCK_DGRAM,0);

bind(sockfd,(struct sockaddr *)&server,sizeof(struct sockaddr_in));

int iclientlen = sizeof(struct sockaddr_in);

while(1){

int recvlen= recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&client, (socklen_t*)&iclientlen);

sendto(sockfd,buf,recvlen,0,(struct sockaddr *)&client,sizeof(struct sockaddr_in));

}

Page 10: 架构平台部 < 网络通讯 >培训

UDP 的显式 connect

udp 也可以使用 connect 来指明对方 ip , port

使用 connect 后, udp 可以使用 write , read 等函数发送数据,无需指明对方地址。只要知道对方的 ip , port 就可以发送数据给对方。recvfrom 可能收到任何对端的数据,不一定是你期待的对端。例如客户端调用 recvfrom 得到的数据可能不是 sendto所指明的服务器发送的。使用 connect 可以指明对端, read 时只能收到此对端的数据。由于 udp 可以使用一个 socket 像任何对端发送数据,也可以使用一个 socket 从任何对端接收数据,所以它只需要一个 socket 。

Page 11: 架构平台部 < 网络通讯 >培训

TCP 与 UDP 的差异

可靠的传送报文无大小限制一个 socket 对应一对 client,server

流式数据发送, IP 报文可合并或拆分。需要建立连接,并维护。有流控,不会淹没

不可靠的传送64K ,大了要分包一个 socket 可以处理所有 client , server

面向报文的数据发送, IP 报文不会合并或拆分无连接概念,只要 ip,port 即可通讯。无流控,快的淹没慢的。

流式数据在接收时面临怎样的问题?

Page 12: 架构平台部 < 网络通讯 >培训

TCP 的连接过程( 3 次握手 )

3 次握手

主动方 消息 被动方connect() --SYN-->SYN_SEND SYN_RECV

<-- SYN+ACK--

ESTABLISHED -- ACK --> ESTABLISHED

失败流程:SYN ----------------> 关闭的端口

<---------RST----

Page 13: 架构平台部 < 网络通讯 >培训

TCP 状态机

状态机连接管理定时器拥塞控制算法安全

Page 14: 架构平台部 < 网络通讯 >培训

TCP 的断接过程close()

------ FIN ------->

FIN_WAIT1 CLOSE_WAIT

<----- ACK -------

FIN_WAIT2

close() 调用 <------ FIN ------

LAST_ACK

TIME_WAIT

------ ACK ------->

(wait...) CLOSED

CLOSED

Page 15: 架构平台部 < 网络通讯 >培训

一个工具 tcpdump

tcpdump -X -s 0 port 39111 and host 172.27.196.237

Page 16: 架构平台部 < 网络通讯 >培训

字节序

网络字节序是 big-endian

intel x86 是 little-endian

sender htonl(int)

recver ntohl(int)

什么是字节序?字节序由什么决定?

Page 17: 架构平台部 < 网络通讯 >培训

阻塞与非阻塞的问题

connect,accept,read,write 默认情况下,行为均是阻塞的。read 在未读到数据时不会返回。write 在数据未完全发完时不会返回。过多的等待,一个流程大部分时间都在等待,无事可做 ...

一次只能在一个连接上等待。

什么是阻塞,非阻塞?

怎么解决?

1.单流程并发2.多流程3.非阻塞流程

Page 18: 架构平台部 < 网络通讯 >培训

单流程并发 (Select 多路监听 )

fd_set allSet;FD_ZERO(&allSet); // 每次 select 之前必须重新设置 fd_set

FD_SET(listenfd,&allSet); FD_SET(clientfd[0-127],&allSet); // 设入所有有效的 clientfdif (select(FD_SETSIZE, &read_set,NULL, NULL, NULL) > 0){ for (int i = 0; i < 127; ++i){ if (FD_ISSET(clientfd[i], &read_set)){ ProcessRecv(clientfd[i]); } if (FD_ISSET(listenfd, &read_set)) { Accept_New(listenfd);// 将得到的 clientfd 放入数组 }}

clientfd[128]listenfd

select返回 0说明什么? read返回 0说明什么?本质上还是阻塞, write仍然存在很多不必要的等待。

Page 19: 架构平台部 < 网络通讯 >培训

多流程并发

多流程并发1. 多进程并发

a. 主进程 accept 后, fork 一个子进程处理 .

b. 所有进程竞争 accept

c. 主进程 accept 后,选择一个子进程,将 fd交给此进程处理。

2. 多线程并发 a. 一个线程调用 accept产生 fd 到队列中,其余线程竞争处理此队列中的 fd

Page 20: 架构平台部 < 网络通讯 >培训

多流程并发之 ---- 多进程并发 1

多进程并发 1

主进程 accept 后, fork 一个子进程处理。子进程只处理一个连接 .

while(1){

struct sockaddr_in cli_addr;

socklen_t cli_len = sizeof (cli_addr);

newsockfd = accept (sockfd, (struct sockaddr *) &cli_addr, &cli_len);

pid_t pid = fork();

if (pid ==0){

do_work(newsockfd);

close(newsockfd);

exit(0);

}

}

Page 21: 架构平台部 < 网络通讯 >培训

多流程并发之 ---- 多进程并发 2

多进程并发 2所有进程竞争 accept (惊群现象)

bind(listenfd, ...);listen(listenfd);// 创建进程for (int i = 0; i < forknum; ++i){

pid = fork();if (pid == 0)

break;}// 多个进程竞争 acceptfor (;;){

connfd = accept(listenfd, ...);if(connfd >=0)

child_process(listendfd);}

Page 22: 架构平台部 < 网络通讯 >培训

多流程并发之 ---- 多进程并发 3

多进程并发 2

选择一个子进程,将 fd交给此进程处理for (;;){

connfd = accept(listenfd, ...);

for (i = 0; i < childnum; ++i){

if (child[i].status == 0)

// 这个子进程不忙,通过管道传送 fd ,将任务发给它write(child[i].pipefd, &connfd,sizeof(connf

d));break;

}

}

没有竞争,没有惊群,但需要维护复杂的进程状态。

Page 23: 架构平台部 < 网络通讯 >培训

多流程并发之 ---- 多线程并发

多线程并发 线程 A accept 描述符 fd 到一个队列,线程 N1 , N2 。。。从队列竞争

得到 fd.

临界资源,需要保护

Page 24: 架构平台部 < 网络通讯 >培训

多流程的缺点

各流程之间竞争 cpu资源,切换代价大。多少个流程是合适的?单个流程阻塞严重时,需要更多的流程,更多的系统资源。性能有上限,总能力 = 单个流程能力 * 流程数

怎么解决 ?

非阻塞

Page 25: 架构平台部 < 网络通讯 >培训

非阻塞 socket

read write

read_buf write_buf

物理网络

应用程序调用

OS系统缓存

网络传输

read 将 OS缓存中的数据读出,无数据时不会等待。write 将数据写到 OS缓存后马上返回,能写多少写多少。

设置非阻塞 :int fdctl = fcntl(socket,F_GETFL);fcntl(socketfd,F_SETFL,fdctl | O_NONBLOCK);

设置 /获取系统 socket缓存大小 :setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const void *)&iOptVal, iOptLen);getsockopt(...)

Page 26: 架构平台部 < 网络通讯 >培训

非阻塞 socket

由于没有阻塞点,只需要一个流程即可。write 不保证数据全部能否发送完毕,所以需要程序需要自己保存未发完数据,等待下次发送。非阻塞特性可以通过 accpet继承下来。

int iWLen = write(clientfd,szBuff,iDataLen);

if(iWLen < iDataLen){

append_to_buffer(szBuff+iWLen,iDataLen-iWLen );

}

复杂性在于需要对每一个链接维护状态和残余数据。

Page 27: 架构平台部 < 网络通讯 >培训

非阻塞 socket 返回值

send > 0

等于申请发送的长度,一切 OK

小于申请发送的长度,缓冲满,部分数据未发送。

send < 0

errno 等于 EAGAIN ,此次调用失败, socket正常errno 等于其它值, socket 异常,需要关闭。

recv = 0

对端关闭。

recv < 0

errno 等于 EAGAIN ,此次调用失败, socket正常errno 等于其它值, socket 异常,需要关闭。

Page 28: 架构平台部 < 网络通讯 >培训

比 select更快的 epoll

Epoll 一种新的 I/O 多路复用技术。传统的 select 以及 poll 的效率会因为在线人数的线形递增而导致呈二次乃至三次方的下降,这些直接导致了网络服务器可以支持的人数有了个比较明显的限制。select 管理的描述符最多 1024 个。基于性能问题,使用 epoll替代 select 。

Page 29: 架构平台部 < 网络通讯 >培训

性能对比

Page 30: 架构平台部 < 网络通讯 >培训

Epoll 函数函数声明: int epoll_create(int size)     该函数生成一个 epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围

函数声明: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。

参数: epfd :由 epoll_create 生成的 epoll专用的文件描述符;op :要进行的操作例如注册事件,可能的取值 EPOLL_CTL_ADD 注册、 EPOLL_CTL_MOD 修改、 EPOLL_CTL_DEL 删除fd :关联的文件描述符;event :指向 epoll_event 的指针;

例如: ev.data.fd = newSocket;ev.data.fd = newSocket; ev.events = EPOLLIN | EPOLLERR | EPOLLOUTev.events = EPOLLIN | EPOLLERR | EPOLLOUT;;

如果调用成功返回 0, 不成功返回 -1

Page 31: 架构平台部 < 网络通讯 >培训

Epoll 函数函数声明 :int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)该函数用于轮询 I/O 事件的发生;

参数:epfd: 由 epoll_create 生成的 epoll专用的文件描述符;epoll_event: 用于回传代处理事件的数组;maxevents: 每次能处理的事件数;timeout: 等待 I/O 事件发生的超时值;

返回发生事件数。

Page 32: 架构平台部 < 网络通讯 >培训

events 字段

EPOLLIN :表示对应的文件描述符可以读EPOLLOUT :表示对应的文件描述符可以写EPOLLPRI :表示对应的文件描述符有紧急的数据可读EPOLLERR :表示对应的文件描述符发生错误EPOLLHUP :表示对应的文件描述符被挂断EPOLLET/EPOLLLT :表示事件触发模式 Edge Triggered (ET) Level Triggered (LT)

Page 33: 架构平台部 < 网络通讯 >培训

对比

FD_SET(socketfd, &rfds);

FD_SET(socketfd, &wfds);

epoll_ctl(epollfd, EPOLL_CTL_ADD, newSocket, &ev)

select(maxfd+1, &rfds, NULL, NULL, &timeout) epoll_wait(epollfd, events,

MaxfdNum,timeout)

FD_ISSET(socket, rfds)

FD_ISSET(socket, wfds) if(events[i]->events & EPOLLOUT)

Page 34: 架构平台部 < 网络通讯 >培训

example

struct epoll_event ev,events[MAXSOCKETNUM];

epfd=epoll_create(MAXSOCKETNUM); listenfd = socket(AF_INET, SOCK_STR

EAM, 0); bind(listenfd,(sockaddr *) serveraddr, siz

eof(serveraddr)); listen(listenfd, 1024);

ev.data.fd=listenfd; ev.events=EPOLLIN; epoll_ctl(epfd,EPOLL_CTL_ADD,listenf

d, ev); while(1 ) {

nfds=epoll_wait(epfd,events,MAXSOCKETNUM,timeout);

for(i=0;i<nfds;++i) {

if(events[i].data.fd==listenfd) accept(listenfd...);

else if(events[i].events & EPOLLIN) read(events[i].data.fd...

else if(events[i].events & EPOLLOUT) write(events[i].data.fd...

else {close(events[i].data.fd);

epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd, &ev); }

Page 35: 架构平台部 < 网络通讯 >培训

推荐书目:

<Unix網絡編程 > 淸華大學詘版社

Page 36: 架构平台部 < 网络通讯 >培训

课后作业作业内容:

做一个 http 服务器,提供下载文件功能。要求:在浏览器地址栏输入: http://172.16.16.16:8080/htdocs/aaa.b

mp ,能够下载一个文件http://172.16.16.16:8080/htdocs/aaa.html ,能够提供网页浏览http协议自己找资料

作业要求:性能要尽可能的高,选用合理的模型

作业例子程序,附件

Page 37: 架构平台部 < 网络通讯 >培训