1 / 28

Socket 编程 ( 上 )

Socket 编程 ( 上 ). Socket programme. 课程目标. Socket 编程基本知识 Socket 常用函数 C/S 结构 TCP 编程. Socket 简介. BSD Socket 接口是 TCP/IP 网络的 API 。 在 Linux 、 Unix 和 Windows 中均实现了这个接口。 BSD Socket 是目前开发网络应用的主要接口,绝大部分网络应用均 可用 Socket 来开发。 一个 Socket 队列是 IP 应用的基本单位,两个机器通讯相当于两 个机器的两个 Socket 互相通讯的过程。

chaeli
Télécharger la présentation

Socket 编程 ( 上 )

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Socket 编程(上) Socket programme

  2. 课程目标 • Socket编程基本知识 • Socket常用函数 • C/S结构 • TCP编程

  3. Socket简介 • BSD Socket接口是TCP/IP网络的API。 • 在Linux、Unix和Windows中均实现了这个接口。BSD Socket是目前开发网络应用的主要接口,绝大部分网络应用均 可用Socket来开发。 • 一个Socket队列是IP应用的基本单位,两个机器通讯相当于两 个机器的两个Socket互相通讯的过程。 • Socket的本意是插座,每一个激活的Socket可以看成是一个跟 本地某个IP端口绑定的IP包队列。 • Socket接口最先是在Unix操作系统中实现的,一个激活的 Socket被设计成特殊的I/O文件,因此Socket也是一种文件描 述符,对Socket的操作类似对一个普通文件的操作。

  4. Socket如何表示IP地址 • IP协议规定:ipv4地址占4个字节,ipv6地址占16字节。所有形式的地址最终都要转 换成这种格式,socket库才能处理。 • 但socket库的地址结构的出发点是试图兼容和处理不同网络的地址形式,而不是仅仅 针对IP地址,因此被设计成一个复杂的联合结构,看起来不好处理。以下是通用地址 结构,用sa_family来指示是哪种类型的网络地址。 struct sockaddr { unsigned short sa_family; /* 地址族,AF_xxx */ char sa_data[14]; /* 14字节的协议地址 */ }; • sa_family为AF_INET表示ipv4地址,此时sockaddr的定义变成如下形式。ipv4大 部分情况下处理struct sockaddr_in,struct sockaddr_in定义在netinet/in.h中。 struct sockaddr_in { short int sin_family; /* 地址族 */ unsigned short intsin_port;/* 端口号 */ struct in_addr sin_addr; /* IP地址 */ unsigned char sin_zero[8]; /* 填充0以保持与struct sockaddr同样大小 */ };

  5. Socket如何表示IP地址(2) • in_addr定义如下: typedef struct in_addr { union { struct {u_char s_b1,s_b2,s_b3,s_b4;}s_un_b; struct {u_short s_w1,s_w2;}s_un_w; u_long s_addr; }s_un; }in_addr; • 这一形式的定义把4个byte的IP地址分成了三种格式,以便各种情况都能处理,实际应用中绝大部分是使用u_long s_addr。 • 综上所述,设置一个ipv4的地址需要对socketaddr_in结构中的三个成员赋值: • sa_family=AF_INET,sin_port设为端口,sin_addr.s_addr设为整数形式的 地址。 由于socketaddr先天性的原因,这个结构被搞得非常复杂,这是开发者要注意的。

  6. 其它形式的地址表示 • 人们常用点分表示法的格式表示IP地址,即形如“192.168.0.1” 的字符串表示法格式,但这样的格式需要转成整数才能被socket 库使用,因此socket库提供如下的转换函数: • #include <arpa/inet.h> • intinet_aton(constchar *strptr,struct in_addr *addrptr); • 点分字符串转in_addr结构 • 返回:1—串有效,0—串出错 • in_addr_tinet_addr(constchar*strptr); • 点分字符串转in_addr结构中的u_long型 • 返回:若成功,返回32位二进制的网络字节序地址;若出错,则返回 INADDR_NONE • 类似inet_aton,但inet_addr更为通用 • char*inet_ntoa(structin_addrinaddr); • 将in_addr结构转为点分法字符串 • 返回:指向点分十进制数串的指针 • 注意转换后点分格式是带端口的,比如:202.116.34.194.4000表 示IP为202.116.34.194,端口为4000

  7. 其它形式的地址表示(2) • 新版转换函数支持ipv6地址转换 • intinet_pton(intfamily,constchar*strptr,void*addrptr); • constchar*inet_ntop(intfamily,constvoid *addrptr,char*strptr,size_tlen); 函数名中的p表示presentation,即点分地址表示法,如:202.116.34.194。函数名中的n表示numeric数值格式。这两个函数中的端口数字都是网络序。 参见“源代码/tcp/inet_pton.c”,源程序中的“ntohl()”、“htonl()”函数见下页“网络字节序,本机字节序”。

  8. 网络字节序,本机字节序 • 不同的计算机系统采用不同的字节序存储数据,同样一个4字节的32位整数在不同的 软硬件系统的内存中存储的方式就不同。字节序分为小端字节序(Little Endian)和大 端字节序(Big Endian)。Intel处理器大多数使用小端字节序,Motorola处理器大多 数使用大端(Big Endian)字节序。 小端字节序:低位字节存储在起始地址(或低地址) 大端字节序:高位字节存储在起始地址(或低地址) • IP协议是适用于不同操作系统及CPU的传输数据的协议,要通过IP协议传输数据就必 须统一规定字节序,否则在传输时会发生混乱。这个统一规定的字节序叫网络序, TCP/IP规定网络序采用大端字节序。相对的, CPU本身的数字表示顺序称为本机 序。 • IP包在发送前必须把本机序转换为网络序,在接收后也需要把网络序转为本机序,这 样才能正常使用。 • 在各种操作系统中都会实现四个转换函数 • include <netinet/in.h> • unit16_t htons(uint16_t host); • unit32_t htonl(uint32_t host); • unit16_t ntohs(uint16_t net); • unit32_t ntohl(uint32_t net);

  9. Socket基本函数 • socket():在本地创建一个Socket,可以认为是创建了一个队列。 • intsocket(intdomain,inttype, intprotocol); • domain:说明网络程序所在的主机采用的通讯协议族,取值有AF_UNIX 和AF_INET。AF_UNIX只能够用于单一的Unix系统进程间通信,而 AF_INET是针对internet的,因此允许与远程主机通信。 • type:网络程序所采用的通讯协议(如:SOCK_STREAM、 SOCK_DGRAM、SOCK_RAW等)。SOCK_STREAM表明采用的是 TCP协议,这样网络会提供按顺序的、可靠的、双向的、面向连接的比特 流。SOCK_DGRAM表明采用的是UDP协议,这样网络只会提供定长 的、不可靠的、无连接的数据通信。SOCK_RAW表示原始的Socket,即 需要用户自行处理IP包头。 • protocol:由于指定了type,所以protocol一般只要用0作为参数值就可 以了。 socket()为网络通讯做基本的准备,成功时返回文件描述符,失败时返 回-1,查看errno可知道出错的详细情况。

  10. Socket基本函数(2) • bind():把一个Socket跟端口绑定,这样Socket才能被外部访 问。 • intbind(intsockfd,structsockaddr*my_addr,int addrlen); • sockfd:socket()返回的文件描述符。 • my_addr:一个指向sockaddr的指针,但一般使用sockaddr_in定义 (见第4页“Socket如何表示IP地址”)来处理ipv4地址。因为主要使用 internet,所以sockaddr_in的sin_family成员一般为AF_INET; sin_port成员是要监听的端口号;sin_addr成员设置为INADDR_ANY 表示可以和任何主机通信;sin_zero[8]只是用来填充。 • addrlen:sockaddr结构的大小,即:sizeof(sockaddr)。 bind将本地的端口同socket()返回的文件描述符捆绑在一起,成功时返回 0,失败时的情况和socket()一样。

  11. Socket基本函数(3) • listen():侦听某个端口。 • int listen(int sockfd,int backlog); • sockfd :socket()返回的文件描述符。 • backlog:当有多个客户端程序和服务端相连时,此参数设置 客户端请求排队的最大长度。 listen()将bind()使用的文件描述符变为监听套接字,返回值 情况和bind()一样。

  12. Socket基本函数(4) • accept():由listen()侦听后,调用此函数等待客户端的连接请求以建立连接。 • int accept(int sockfd, struct sockaddr *addr,int *addrlen); • sockfd:调用listen()后的文件描述符(即:监听套接字)。 • addr、addrlen指向的内存是用来让客户端的程序(一般是 客户端的connect()函数)填写的,服务器端只要传递指针就可以了。 accept()调用时,服务器端的程序会一直阻塞到有一个客户 端程序发出连接。accept()成功时返回一个文件描述符,然 后服务器端可以对该描述符进行读写,失败时返回-1。 bind()、listen()和accept()是服务器端用的函数。

  13. Socket基本函数(5) • connect():联接远程某个Socket。 • int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); • sockfd:socket()返回的用于同服务器端通讯的文件描述 符。 • serv_addr:用于储存服务器端的连接信息,其中的 sin_addr成员存放服务器端的地址。 • addrlen:sockaddr结构的大小,即:sizeof(sockaddr)。 connect()是客户端用来同服务器端建立连接的函数,成功时 返回0,失败时返回-1。

  14. Socket基本函数(6) • read():数据读取,Linux专用函数。 • ssize_tread(intfd,void*buf,size_tnbyte); read()函数负责从文件描述符fd中读取nbytes字节内容到buffer。成功 时,返回实际所读的字节数(返回值为0表示已经读到文件尾);返回值 小于0(返回值一般为-1)表示出现了错误(错误为EINTR说明是由中断 引起的,错误为ECONNREST表示网络连接出了问题)。 • write():数据写入,Linux专用函数。 • ssize_twrite(intfd,constvoid*buf,size_tnbytes); write()函数将buf中的nbytes字节内容写入文件描述符fd。成功时返回 实际所写的字节数,表示写了部分或者是全部的数据;失败时返回-1并设 置errno变量(错误为EINTR表示在写的时候出现了中断错误,错误为 EPIPE表示网络连接出现了问题,如对方已经关闭了连接)。

  15. Socket基本函数(7) • recv()和send():TCP专用收发函数,用于在面向连接的 Socket上进行数据传输。 • intrecv(intfd, void*buf, intlen, unsignedintflags); fd是用于接受数据的Socket的描述符;buf为存放接收到的数据的缓冲 区;len是以字节为单位的缓冲区的长度;flags一般情况下被置为0 (关于该参数的用法可参照man手册)。 recv()返回实际接收的字节数,当出现错误时,返回-1并置相应的errno 值。 • intsend(intfd,constvoid*msg,intlen, unsignedintflags); fd是用于发送数据的Socket的描述符;msg是一个指向要发送的数据的 指针;len是以字节为单位的数据的长度;flags也被置为0。 send()返回实际发送的字节数,可能会小于所希望发送的数据数。在程序 中应该将send()的返回值与欲发送的字节数进行比较。当send()的返回 值与len不匹配时,应该对这种情况进行处理。 • TCP编程中,recv()/send()比read()/write()更有通用性,建 议使用前者。

  16. Socket基本函数(8) • recvfrom()和sendto():UDP专用收发函数,用于在无连接的数据报 Socket上进行数据传输。由于本地Socket并没有与远端机器建立连接,所以 在发送数据时应指明目的地址。 • intrecvfrom(intfd,void*buf,intlen,unsignedintflags,structsockaddr*from, int fromlen); 该函数比recv()函数多两个参数。from指向源机的IP地址及端口号信息,fromlen常被置为 sizeof(structsockaddr)。当recvfrom()返回时,fromlen中为实际存入from指向的结构 中的数据字节数。 recvfrom()函数返回实际接收到的数据字节数或当出现错误时返回-1,并置相应的errno。 • intsendto(intfd,constvoid*msg,intlen,unsignedintflags,const struct sockaddr*to,inttolen); 该函数比send()函数多两个参数。to指向目地机的IP地址和端口号信息,tolen常被赋值为sizeof(structsockaddr)。 sendto函数也返回实际发送的数据字节数或在出现发送错误时返回-1。 当对数据报Socket调用connect()函数时,也可利用recv()和send()进行数据传输,但该 Socket仍然是数据报Socket,并且利用传输层的UDP服务,在实际发送或接收数据报时, 内核会自动为之加上目地和源地址信息。 • UDP编程中,recvfrom()/sendto()比read()/write()更有通用性,建议使 用前者。

  17. Socket 编程模型 • 当前的Socket编程模型一般都是C/S(服务器/客户端)结构,即相互通信的网络程序中,一方称为客户程序(client),另一方称为服务器程序(server)。 • C/S结构中,客户端向服务器发送请求,服务器作出响应。象常见的“浏览器/web服务器”、“FTP客户端/FTP服务器”就是典型的C/S结构。 • 一个服务器可以同时接受多个客户端请求。 • 在Socket编程中,服务器和客户端的编程流程有一些不同。随后的课程将针对TCP、UDP通信编程来教学服务器/客户端的开发流程。

  18. TCP服务器程序编写步骤 • 创建套接口(socket())→绑定套接口(bind())→设置套接口为监听模式,进入被动接受连接请求状态(listen())→接受请求,建立连接(accept())→读/写数据(read()/write()或recv()/send())→终止连接(close())。 参见“源代码/tcp/server.c”。

  19. TCP客户端程序编写流程 • 创建套接口(socket())→与远程服务程序连接 (connect())→读/写数据(read()/write()或 recv()/send())→终止连接(close())。 参见“源代码/tcp/client.c”。

  20. TCP服务器模型(1)   • 循环服务器:TCP服务器 • TCP服务器接受一个客户端的连接,然后处理,完成了这个客户端的所有事务后断开 连接。 • socket(...); bind(...); listen(...); while(1) { accept(...); while(1) { read(...); process(...); write(...); } close(...); } • TCP循环服务器一次只能处理一个客户端的请求,只有在这个客户端的所有事务都完 成后,服务器才可以继续后面客户端的请求。这样如果有一个客户端占住服务器不 放,其它的客户机都不能工作。因此,TCP服务器一般很少用循环服务器模型,这种 设计只有参考价值,并无实际意义。

  21. TCP服务器模型(2) • 并发服务器 • 为了弥补TCP循环服务器的缺陷,人们又想出了并发服务器的模型。并发服务器的思想是每一个 客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。 • socket(...);bind(...);listen(...); while(1) { accept(...); if(fork(...)==0) { while(1) { read(...); process(...); write(...); } close(...); exit(...); } close(...); } • 这个模型采用fork()创建子进程,用一个子进程处理一个客户端请求,效率较高。此模型还可换 成多线程模式,即在fork()处换用pthread_create()来创建线程处理请求。

  22. 阻塞与非阻塞 • 阻塞服务中,当服务器运行到accept()语句而没有客户连接服务请求到来,那么会发 生什么情况?这时服务器就会在accept()语句上停止以等待连接服务请求的到来。同 样,当程序运行到接收数据语句recv()时,如果没有数据可以读取,则程序同样会停 止在接收语句上。这种情况称为阻塞(blocking)。 • 如果仅希望服务器注意检查是否有客户在请求连接,有就接受连接,否则就继续做其 它事情,则可以通过将Socket设置为非阻塞方式来实现:非阻塞Socket在没有客户 请求时就使accept()调用立即返回 。 • 通过设置Socket为非阻塞方式,可以实现“轮询”若干Socket。当企图从一个没有数据 等待处理的非阻塞Socket读入数据时,函数将立即返回,并且置返回值为-1,置 errno为EWOULDBLOCK。但是这种“轮询”会使CPU处于忙等待状态,从而降低性 能。考虑到这种情况,如果希望服务器在监听连接服务请求的同时从已经建立的连接 中读取数据,你也许会想到用一个accept()语句和多个recv()语句,但是由于 accept()及recv()都是会阻塞的,所以这个想法显然不会成功。 • 用非阻塞的Socket会大大地浪费系统资源,而调用select()会有效地解决这个问题。 它允许在阻塞时把进程本身挂起来,同时使系统内核监听所要求的一组文件描述符的 任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用就会返回 “通知”该文件描述符已准备好的信息,从而实现了随机地为进程选择一个可进行的活 动,避免由进程本身测试是否存在可进行的活动而浪费CPU开销。

  23. TCP服务器模型(3) • 单进程并发服务器: I/O多路复用 • I/O多路复用函数 • intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,struct timeval*timeout); 进程在读写文件时有可能被阻塞,直到一定的条件满足。比如从一个套接字读数据时,如果缓 冲区中没有数据可读(通信的对方还没有发送数据过来),读调用就会等待(被阻塞)直到缓 冲区中有数据可读。如果不希望被阻塞,选择之一是使用select()系统调用。只要设置好 select()的各个参数,那么当文件可以读写的时候,select()就返回“通知”可以读写的信息。 参数介绍:nfds 所有需要监控的文件描述符中最大的那一个加1;readfds 所有要读的文件描 述符的集合;writefds 所有要写的文件描述符的集合;exceptfds 其它的服要向我们通知的文 件描述符;timeout 超时设置。 调用select()时进程会一直阻塞直到以下的一种情况发生: 1)有文件可以读;2)有文件可以写;3)超时设置的时间到。 • void FD_SET(int fd,fd_set *fdset); • 将fd加入到fdset中 • void FD_CLR(int fd,fd_set *fdset); • 将fd从fdset里面清除 • void FD_ZERO(fd_set *fdset); • 将所有的文件描述符从fdset中清除 • int FD_ISSET(int fd,fd_set *fdset); • 判断fd是否在fdset集合中

  24. TCP服务器模型(3)续 • socket(...); bind(...); listen(...); while(1) { FD_ZERO(…); select(…); if(FD_ISSET(svr_fd,…)) //如果是服务器侦听套接字被触发,说明一个新的连接请 //求建立 { new_fd = accept(…); //建立新的客户端连接 FD_SET(new_fd,…); //加入到监听文件描述符中去 } else //是一个客户端操作 { //进行read()或者write()操作 } } • 多路复用I/O可以解决资源限制的问题。由于采用select()机制,因此当没有字符可读 时程序处于阻塞状态,这样可最小程度的占用CPU资源。

  25. 多路复用流程 • 典型的IO多路复用单 进程并发服务器流程

  26. 课堂练习 • echo server采用多进程来实现客户端请求,现要求将处理模型为fork创建的多进程模型改为多线程模型,即每个客户端的处理通过创建一个线程来负责完成。 • 请将File服务器和客户端分布在两台机器上运行,并用TCPDump来监视运行状态。

  27. 扩展练习 • TCP客户端或服务器端的编写都有固定的流程,请设计一组通用接口来简化Socket编程开发。 • 一般会有如下接口 • open_tcp_server(short port); • connect_tcp_server(char * ipaddr,short port); 请设计这两组接口。

  28. 谢谢,请提问 在疯狂的时代把握未来

More Related