1、套接字和网络通信,1 基本概念,介绍在UNIX环境下,如何使用套接字API编写网络应用程序。大部分网络应用系统都可以分为客户端(client)和服务器端(server),就是通常所说的C/S结构,最典型也最常见的,上网就是一种C/S模式的应用,浏览器ie是客户端,web服务器作为服务器端。,1 基本概念,网间进程通信网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。 其次,操作系统支持的网络协议众多
2、,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。,1 基本概念,端口 端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问。,1 基本概念,端口 端口号的分配有两种基本分配方式:全局分配;本地分配TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为
3、两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。,1 基本概念,地址 网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址: 1. 某一主机可与多个网络相连,必须指定一特定网络地址; 2. 网络上每一台主机应有其唯一的地址; 3. 每一主机上的每一进程应有在该主机上的唯一标识符。 通常主机地址由网络ID和主机ID组成,在TC
4、P/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。,基本概念,网络字节顺序 不同的计算机存放多字节值的顺序不同,因此为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式。 连接 两个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。,基本概念,半相关 网络中用一个三元组可以在全局唯一标志一个进程: (协议,本地地址,本地端口号) 这样一个三元组,叫做一个半相关(half-association),它指定连接的每半部分。 全相关 一个完整的网间进程通信需要由两个
5、进程组成,并且只能使用同一种高层协议。因此一个完整的网间通信需要一个五元组来标识: (协议,本地地址,本地端口号,远地地址,远地端口号) 这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。,基本概念,服务方式:面向连接服务,无连接服务面向连接服务面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地址,而使用连接号。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。 无连接服务无连接服务是
6、邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。,基本概念,客户/服务器模式服务器方: 1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求; 2. 等待客户请求到达该端口; 3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,
7、并终止。 4. 返回第二步,等待另一客户请求。 5. 关闭服务器,基本概念,客户方: 1. 打开一通信通道,并连接到服务器所在主机的特定端口; 2. 向服务器发服务请求报文,等待并接收应答;继续提出请求 3. 请求结束后关闭通信通道并终止。,2 套接字,1. 套接字的经典定义,Linux系统是通过提供套接字(socket)来进行网络编程的.网络程序通过socket和其它几个函数的调用,会返回一个 通讯的文件描述符,我们可以将这个描述符看成普通的文件的描述符来操作,这就是linux的设备无关性的 好处.我们可以通过向描述符读写操作实现网络之间的数据交流. 从用户的角度来看,套接字是网络通信端点的
8、一种抽象概念,它为用户提供了一种在网络上发送和接收数据的机制。,2 套接字,2. 套接字的域, 类型 和 协议,域 说明我们网络程序所在的主机采用的通讯协族(AF_UNIX 或 AF_INET),类型 说明套接字类型,可以是 SOCK_STREAM, SOCK_DGRAM 或 SOCK_RAW,协议 指定套接字使用的协议,当新创建一个套接字时,需要指定上述3个特性,2 套接字,3.创建套接字,用户需要进行任何网络通信时,需要创建一个套接字,#include int socket(int domain, int type,int protocol);,domain:网络程序所在的主机采用的通讯协
9、族(AF_UNIX和AF_INET等). AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信,type:网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流. SOCK_DGRAM表明用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.,protocol:由于指定了type,所以这个地方一般只要用0来代替就可以了 sock et为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,2 套接
10、字,3.创建套接字,#include int sockfd; sockfd = socket( AF_INET, SOCK_STREAM, 0 );,2 套接字,4.关闭套接字,#include int close(int sockfd);,3 套接字地址,套接字名字,= 套接地址,3 套接字地址,1.主机的IP地址,在IP网络中,每台机器都有一个 IP 地址,一个32位的数字,它唯一地标识这台机器。,通常,32位的IP地址可以由四个用点分开的数字表示,每个数字都是一个8位长的整数,例如16.42.0.9。,3 套接字地址,主机的IP地址 : 特殊IP地址,在UNIX系统中,定义了一些具有特殊
11、意义的IP地址,声明在头文件中,INADDR_LOOPBACK 表示本机器地址,统一定义为127.0.0.1,INADDR_BROADCAST 广播地址,用来进行消息广播的特定地址,INADDR_ANY 通配名,3 套接字地址,(a) 主机的IP地址,/ IP地址的内部表示: #include struct in_addr in_addr_t s_addr; ,32位无符号整数,3 套接字地址,(a) 主机的IP地址,字符串IP和32位IP地址转换函数,int inet_aton (char *name, struct in_addr *addr);,char* inet_ntoa (stru
12、ct in_addr in);,函数里面 a 代表 ascii n 代表network.第一个函数表示将a.b.c.d的IP转换为32位的IP,存储在 inp指针里面.第二个是将32位IP转换为a.b.c.d的格式.,3 套接字地址,(b) 主机名,为了方便记忆,计算机都有一个主机名,这样就不用记复杂的数点形式的IP地址,在目前所有的操作系统中都有一个hosts文件,用于记录主机名和数点形式的IP地址的映射。,3 套接字地址,UNIX系统为处理主机信息,定义了 hostent 结构描述主机名相关信息,include struct hostent char * h_name; /* 主机的正式名
13、称 */ char * h_aliases; /* 主机的别名 */ int h_addrtype; /* 主机的地址类型 AF_INET*/ int h_length; /* 主机的地址长度 对于IP4 是4字节32位*/ char * h_addr_list; /* 主机的IP地址列表 */ #define h_addr h_addr_list0 /* 主机的第一个IP地址*/,3 套接字地址,UNIX系统为获得主机信息的相关函数,#include struct hostent* gethostbyname( char *name); struct hostent* gethostbyad
14、dr( void *addr,size_t length, int type );,gethostbyname可以将机器名(如 )转换为一个结构指针.在这个结构里面储存了域名的信息 gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针. 这两个函数失败时返回NULL.,3 套接字地址,2.服务与端口,套接字地址 机器的IP地址 端口号,标识一台计算机,区别同一台计算机上的不同服务程序,3 套接字地址,2 服务与端口,UNIX系统中的端口分配,0, 不使用,11023, 知名端口(保留端口),限定为标准服务使用,如ftp 的21端口,http的80端口等,102
15、45000,可以被任意的客户端程序使用,500165535,为其他服务程序预留,3 套接字地址,UNIX系统为处理服务信息,定义了 servent 结构描述服务相关信息,include struct servent char * s_name; /服务的正式名称char* s_aliases; /服务的可选别名int s_port; /服务使用的端口号char * s_proto; /与该服务一起使用的协议名 ,3 套接字地址,UNIX系统为获得服务信息的相关函数,#include struct servent* getservbyname(char *name,char* proto); S
16、truct servent* getservbyport(int port, char* proto);,3 套接字地址,不同的计算机系统采用不同的字节序存储数据,Little-Endian字节序:将低字节存储在起始地址,由Intel体系采用,Big-Endian字节序:将高字节存储在起始地址,由Macintosh体系采用,3 套接字地址,主机字节序和网络字节序,主机字节序:将某给定系统所用的字节序称为主机字节序,网络字节序:网络上传输的数据使用规定的字节序,即网络字节序,采用big-endian,3 套接字地址,主机字节序和网络字节序的转换,UNIX系统中提供了一组函数用来进行网络字节序和主
17、机字节序的转换,#include uint16_t htons ( uint16_t hostshort ); uint32_t htonl ( uint32_t hostlong );uint16_t ntohs ( uint16_t netshort ); uint32_t ntohl ( uint32_t netlong );,其中h 代表host, n 代表 network.s 代表short l 代表long 第一个函数的意义是将本机器上的long数据转化为网络上的long. 其他几个函数的意义也差不多,4 套接字地址,套接字地址的数据结构,Internet通信域地址数据结构: #i
18、nclude struct sockaddr unisgned short as_family;/地址协议族,为 AF_INETchar sa_data14; /保留未用;,4 套接字地址,套接字地址的数据结构,Internet通信域地址数据结构: #include struct sockaddr_in sa_family_t sin_family; /地址协议族,为 AF_INETin_port_t sin_port; /主机端口号struct in_addr sin_addr; /主机的internet地址unsigned char sin_zero; /保留未用 ,示例 构造一个inte
19、rnet套接字地址,流程 sock = socket(AF_INET, SOCK_STREAM, 0 ); struct sockaddr_in name ; 3.init_sockaddr( ,#include void init_sockaddr(struct sockaddr_in *name,char* hostname, char * serv) struct hostent *hp;char *host, myname256;if( hostname = null ) gethostname(myname, sizeof(myname);host = myname;elsehost
20、 = hostname;,hp = gethostbyname( host );bzero( name, sizeof( struct sockaddr ) );if( hp-h_addrtype = AF_INET ) name-sin_family = AF_INET;bcopy( hp-h_addr_list0, ,5 命名套接字,命名套接字,调用socket函数创建套接字仅返回一个套接字描述符,没有地址信息,套接字必须与地址绑定,才能被其他进程所访问,因此需要对套接字命名,5 命名套接字,命名函数,#include int bind( int socket, struct sockad
21、dr *address,socklen_t address_len );,sockfd:是由socket调用返回的文件描述符. my_addr:是一个指向sockaddr的指针 addrlen:是sockaddr结构的长度.,UNIX域和internet域都使用bind函数来设置套接字地址,因此使用通用地址结构 struct sockaddr ,对于网络通信,必须用sockaddr_in构造一个地址,当调用bind时,将类型强制转换成 struct sockaddr,5 命名套接字,示例:,#include void make_socket( unsigned short int port )
22、 int i,sock;struct sockaddr_in address;sock = socket( AF_INET, SOCK_STREAM, 0);address.sin_family = AF_INET;address.sin_port = htons( port ); address.sin_addr.s_addr = htonl( INADDR_ANY );i = bind( sock, (struct sockaddr*) ,5 命名套接字,示例说明:,函数make_socket不同于前面的init_socket,它没有查找和使用机器的internet地址,而是使用通配名IN
23、ADDR_ANY作为套接字的地址.系统解释这个地址为程序运行所在机器的任意合法的网络地址,这种类型的套接字可以在机器的不同ip地址上提供服务.,函数make_socket的参数port和通配名常数INADDR_ANY都是主机字节顺序,在填入sockaddr_in时,需要转换为网络字节顺序.,5 命名套接字,示例:,#include void make_socket( unsigned short int port, char *ipaddress ) int i,sock;struct sockaddr_in address;sock = socket( AF_INET, SOCK_STREA
24、M, 0);address.sin_family = AF_INET;address.sin_port = htons( port ); inet_aton( ipaddress, ,6 套接字通信模式,通信模式,同一台计算机中的客户和服务进程之间以 有连接 模式使用套接字,套接字类型为SOCK_STREAM ,这种模式称为面向连接的c/s模式,使用协议TCP,不同计算机中的客户和服务进程之间以 有连接 模式使用套接字, 模式1的网络扩展,同一台计算机中的客户和服务进程之间以 无连接 方式使用套接字,套接字类型为 SOCK_DGRAM,使用协议UDP,不同计算机中的客户和服务进程之间以无连接模
25、式使用套接字, 模式3的网络扩展,6 套接字通信模式,流套接字通信模式,6 套接字通信模式,数据报套接字通信模式,7 流套接字操作,通信模式,最常见的流套接字通信模式是与另一个特定的套接字建立连接,然后与那个套接字一次又一次地交换数据,请求连接,请求连接是客户端的动作.,客户端套接字是主动套接字,客户进程通过调用connect函数建立与有名的套接字的连接,7 流套接字操作,请求连接,#include int connect(int sockfd, struct sockaddr *serv_addr,socklen_t serv_len );,sockfd:socket返回的文件描述符. se
26、rv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址 serv_len:serv_addr的长度 connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1.,7 流套接字操作,请求连接 示例,#include int socket_connect(char *hostname, char *servport) int sockfd;struct sockaddr_in sockaddr;init_sockaddr( ,7 流套接字操作,接收连接,接收连接是服务端的动作.,为了接收客户端的连接请求,服务程序在调用bin
27、d进行命名后,要执行两个步骤:,调用 listen 函数创建一个连接请求的侦听队列,调用 accept 函数接收连接,7 流套接字操作,接收连接 创建连接队列,为了创建一个连接队列,必须调用listen函数,#include int listen(int sockfd, int backlog );,sockfd:是bind后的文件描述符. backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示排队长度.listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.,7 流套接字操作,接收连接 接收客户连接,Listen建立队列后,可以调用a
28、ccept来接收套接字上到达的连接请求,#include int accept(int sockfd, struct sockaddr *clientaddr,socklen_t address_len );,sockfd:是listen后的文件描述符. clientaddr,addresslen是用来给客户端的程序填写的,服务器端只要传递指针就可以了.,bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1,7
29、 流套接字操作,send 和 recv,套接字之间一旦建立了连接,就可以开始传送数据.我们可以使用标准读写函数read和write对套接字进行读写. read(socket, buffer, len) 和 write( socket, buffer, len),套接字库还提供send和recv两个函数进行数据读写,它们类似于read和write,但只能用于套接字.同时它们还具备标准I/O函数没有的功能.,7 流套接字操作,send 和 recv,#include int send( int sockfd,void* buffer, size_t length, int flag ); int
30、recv( int sockfd, void* buffer, size_t length, int flag );,sockfd:本地套接字描述符. buffer:保存有发送数据的缓冲区的指针,长度有length指定 flags:. 传输控制方式(0,MSG_DONTROUTE/MSG_OOB/MSG_PEEK/MSG_WAITALL),7 流套接字操作,流套接字操作示例:客户程序向服务程序请求得到日期和时间信息,/客户端程序client.c ,将用到子函数socket_connect #include void main() int sockfd, n;char recvbuffer128
31、;sockfd = socket_connect ( “192.168.10.3”, “13”);while( (n= recv(sockfd,recvbuffer, 127, 0) 0 ) recvbuffern = 0;printf(“%s”, recvbuffer); close( sockfd ); ,7 流套接字操作,流套接字操作示例:客户程序向服务程序请求得到日期和时间信息,/服务端程序server.c , 将用到子函数make_socket #include #define MAX_LISTEN 30 void main() int listen_fd, connected_fd
32、;char buffer128;time_t ticks;struct sockaddr_in client_addr; listen_fd = make_socket(13);listen( listen_fd, MAX_LISTEN );,7 流套接字操作,流套接字操作示例:客户程序向服务程序请求得到日期和时间信息,for ( ; ; )connected_fd = accept ( listen_fd,(struct sockaddr*)client_addr, sizeof(client_addr);printf(“connect from %s, port %dn”,inet_nto
33、a(client_addr.sin_addr.s_addr), ntohs(client_addr.sin_port) );ticks = time( NULL );sprintf( buf, “the time is %srn”, ctime(,9 数据报套接字,通信模式,数据报套接字采用UDP协议, 是无连接,不可靠的.,客户不与服务器建立连接,只是通过sendto函数发送数据报.,服务端也不需要接收来自客户的连接,不需要监听队列,只是不停地调用recvfrom函数接收数据.,9 数据报套接字,支持函数,#include int sendto ( int socket, void *buf
34、fer, size_t size,int flag,struct sockaddr* to, size_t addrlen); int recvfrom( int socket, void* buffer, size_t size,int flag,struct sockaddr *from, size_t *addrlen);,struct sockaddr 的指针,它包含了目的地的 IP 地址和端口信息。 tolen 可以简单地设置为 sizeof(struct sockaddr)。,9 数据报套接字,示例:基于UDP的C/S应用,服务端将客户端发来的信息直接回应到客户端,即是著名的ech
35、o服务!,/ server 端程序 #include #define MAX 512 int main() int sock, size, nbytes;struct sockaddr_in name;char messageMAX; sock = socket( AF_INET, SOCK_DGRAM, 0 );bzero( ,9 数据报套接字,/ server 端程序name.sin_family = AF_INET;name.sin_addr.s_addr = htonl( INADDR_ANY );name.sin_port = htons( 7 );bind( sock, (struct sockaddr*) ,9 数据报套接字,/ client 端程序 int main() int sock, n;struct sockaddr_in name;char bufferMAX;sock = socket( AF_INET, SOCK_DGRAM, 0 );init_sockaddr( ,