Sockets
(一) 需要掌握的要点
1) Socket是如何连接操作的
2) 套接字的连接,地址和通信
3) 网络信息及互联网守护进程
4) 客服端和服务器端
(二) Socket的概念
ØSocket是一种允许客户/服务器双系统被发展不仅仅局限于单一机器的本地通信也可以通过网络实现通信的一种通信机制。
(三) Socket connections
1) 一个服务器应用程序创建一个socket,并把一个像文件描述符一样的标志给服务进程,创建socket是使用一个系统调用socket,这个标志是不能跟其他进程共享的。
2) 服务进程给创建的套接字绑定一个名字,在linux下对于本地套接字将给出一个文件名,这个文件名通常能在/tmp或者/usr/tmp里面找到。对于网络套接字来说,文件名将作为服务标志符(端口号/接入点)关联到一个特殊的网络which客户可以连接。一个套接字被命名通过系统调用bind,然后服务进程 等待客户连接这个已经被命名的套接字。listen系统调用可以创建一个用来保存进入的连接请求。服务端可以接受他们通过使用系统调用accept。
3) 当服务端调用了accept并连接上了,一个新的套接字将被创建(不同于被命名了的套接字),这个新的套接字被单独用来与连接进来的客户进行通信,命名了的套接字将继续用来建立于下一个客户的连接。
4) 客户端的套接字系统是很简单的,先通过socket系统调用来创建一个没有被命名的套接字,然后调用connect系统调用通过使用服务器那个被命名了的套接字作为地址来与服务器建立连接。
5) 一旦建立连接后,套接字就可以像使用底层文件描述符那样,进行双向通信了。
(四) 套接字属性
套接字的特点是具有三个属性:域名、类型、协议。Socket使用地址来作为自己的名字,这个地址的格式很大部分是依赖于域名(也被称为协议族)。每种协议族都可以使用一种或多种地址族去定义它的地址格式。
1) Socket domains
l概念:域名是用来指定套接字使用的网络通信媒介。很多套接字使用的协议族是AF_INET,AF_INET协议族使用internet网络用于很多linux局域网上面,当然还有internet自己本身啦。其中最基本的协议就是Internet protocol(IP),IP地址是由4个号码来组成的。每个号码不能都必须小于256。这种分法号称四点分。当一个客户端需要通过套接字来穿过网络连接上服务器就需要知道服务器的IP地址。
2) Socket Types
l一个套接字协议族会提供多种不同的通信方式,每一种都会有不同的特性。在网络协议族里面,我们必须了解基本的网络特性和不同的通信机制是怎么被影响的。IP协议提供了两种通信机制用于不同水平的服务:stream和datagrams。
lStream Socket(数据流套接字)
a) 提供一种顺序的可靠双向通信的连接。所以数据发送被保证不会丢失,当传输中出现错误时数据将被复制传送,当数据太大时会被分裂传输在重组。
b) 数据流通过使用类型SOCK_STREAM指明,在AF_INET协议族通过TCP/IP协议连接。
lDatagram Socket(数据报套接字)
a) 通过使用类型SOCK_DGRAM至指明,在AF_INET协议族里面通过UDP/IP协议提过一个不按次序不可靠的连接,但是它在资源方面是很省的,因为他们不需要一直保持网络上的连接,他们传输很快因为他们不需要在传输前建立连接。
3) 套接字协议
l在传输机制里面是允许一种以上的传输协议通过套接字类型来选择。
(五) 创建一个套接字
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, intprotocol);
domain:
AF_UNIX:用于本地套接字的执行通过unix或linux的文件系统
AF_INET:用于unix的网络套接字,AF_INET套接字经常被编程用于通过TCP/IP网络来通信包括Internet。
Type:可能的值有SOCK_STREAM和SOCK_DGRAM。
Protocol:这个参数主要被决定于domain和type,这个参数一般都不设置,0是代表使用默认协议。
返回值:返回一个标示符,这个标示符在很多操作上很像文件描述符。一旦这个套接字连接上另一个套接字后,就可以使用read和write系统调用通过这个标志符来发送接收发送数据在套接字上面了。Close系统调用用于接收套接字的连接。
(五) Socket Addresses(套接字地址)
不同的套接字域是有他们不同的地址格式的,以下是比较常用套接字域的地址结构:
lAF_UNIX:
Struct sockaddr_un(){
Sa_family_t sun_family; /*AF_UNIX*/
Char sun_path[]; /*pathname*/
};
定义在sys/un.h里面。
Sun_family:指定协议族;
Sun_path:指定地址。
lAF_INET:
Struct sockaddr_in(){
short int sin_family; /*AF_INET*/
unsigned short int sin_port; /*portnumber,16位*/
struct in_addr sin_addr; /*Internet address,32位*/
};
Struct in_addr{
Unsigned long int s_addr;
};
这4个字节的IP地址组成单一的32位地址值。AF_INET套接字被完全描述通过他的域,IP地址还有端口号。
(其中的IP地址比如“127.0.0.1”,需要调用inet_addr函数对这个文本进行转换成适合套接字地址的格式。比如:inet_addr(“127.0.0.1”);如果希望不要求接受客户的s_addr,就是只要是有客户连接进来都接受,那么可以直接使用INADDR_ANY来指明。)
(六) Naming a socket
在服务进程里面需要对套接字进行命名,所以,在AF_UNIX套接字是被关联上一个文件系统路径名,而在AF_INET套接字里面它是被关联上IP端口号。为套接字命名通过调用bind系统调用:
#include<sys/socket.h>
Int bind(int socket,const struct sockaddr *address , size_t address_len);
Socket :所要调用的套接字的标示符;
Address:为需要对上面标示符所标示的套接字进行命名;
Address_len: 地址address结构体的长度;
(在上面有一点需要注意的是我们所传入的地址结构指针都要强制转换为通用的地址指针类型(structsockaddr *))
返回值:0时成功,-1时失败同时通过以下值设置errno变量。
(五) Creating a socketqueue
服务端程序想要在一个套接字上接受进来的连接请求,就必须创建一个队列用来存储挂起了的请求通过listen系统调用。
#include <sys/socket.h>
Int listen(int socket,int backlog);
Backlog:设置队列的大小,所允许进入的最大连接数,当连接被挂起的数目大于这个值,大于的那些请求连接的都被拒绝以至于客户端连接失败。
这个机制允许申请连接的请求被挂起当服务端程序正在慢与应付前一个客户的时候。Backlog经常被设置为5。
返回值:0时成功,-1失败并设置errno变量包括EBADF,EINVAL,和ENOTSOCK.
(六) Accept connections
当服务器套接字被创建了并且被命名了,那么它就可以使用accept系统调用来等待连接进来的客户请求了,accept等待的时候会被阻塞。
#include<sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t*address_len);
address :为所要等待的客户连接请的地址;这里可以通过设置为NULL来指明不要求连接进来的客户是谁。
Address_len:用来设置address的长度,如果address的长度大于address_len,那么address会被剪短,接收到连接后这个值会被设置为这个客户地址结构的真正长度。
返回值:当有客户被挂起时,accept将返回一个新套接字(用于跟当前申请连接的客户进行通信,这个套接字拥有跟服务器listen套接字一样的类型)的标志符。-1时错误,当设置了O_NONBLOCK,errno会被存入EWOULDBLOCK,当进程产生中断时errno被存入EINTR。
如果不想accept等不到客户连接请求时block,那可以通过在套接字标识
符使用O_NONBLOCK,使用fcntl函数在你的代码如下:
int flags =fcntl(socket,F_GETFL,0);
fcntl(socket,F_SETFL,O_NONBLOCK|flags);
(七) Requesting connection(used for client)
客户端程序连接服务端通过在自己未命名的socket和服务端listen socket建立连接,通过调用connect系统调用。
#include <sys/socket.h>
Int connect(int socket,const struct sockaddr *address,size_taddress_len);
Socket:指明要连接服务端套接字的套接字描述符。
Address:指明要连接的服务端套接字地址。
Address_len:指明address的长度。
返回值:0:成功 -1:错误,可能的错误值如下:
l 加入连接不能被马上建立,那么connect将被阻塞直到一段不确定的超时周期后连接失败。然而如果调用connect期间被信号中断了话,connect调用失败(errno被设置为EINTR),但是连接企图不被终止,它将被异步的建立起来(加入不超时),所以在这里程序在以后要记得检测看看连接是否成功了。
l 像accept一样,connect的默认阻塞情况也可以通过在标志符设置O_NONBLOCK标志来改变。这样当连接不上errno会被设置为EINPROGRESS并且连接会被改变为异步建立。
l 尽管异步通信很难被处理,但是通过使用一个系统调用select来在一个套接字文件标示符检测套接字是不是准备好可写了。
(八) Closing a socket
如果要在客户端或者服务端终止套接字的连接,可以调用close函数,就好像使用文件描述符一样。但是你必须保持关闭套接字在两个端点。在服务端,你必须使用read直到返回0;需要注意的是close调用会被阻塞假如socket还有没有传输完成的数据,这是一种面向连接的类型,而且可以显式通过SOCK_LINGER项来设置。
(九) Host and NetworkByte Ordering
l在计算机中,每个地址单元是一个字节,由于不同计算机对整型值的存储在字节次序上的不同(也就是存在大小端之分),如果在两台存储方式不一致的计算机进行简单逐个逐个字节的拷贝的时候,那么这两个不同的机器不能对这个整型数值保持一致的表示。
l为了使不同存储方式的计算机能够对一个多字节的整型值在经过网络传输后保持对整数值的一致表示。我们就需要定义一种网络定序,那么以后在客户端和服务端的程序就必须在传输之前必须转换内部整型的存储格式为网络定序。
l这样的转换(为了程序的可移植性,尽管你知道当前通信的两台计算存储格式是一样的也要使用以下的转换)主要通过以下几个函数来实现的:
这些函数转换16位跟32位整型值在本地主机和标准网络定序之间。根据函数名称可以判断出各个函数的转换含义。
For example :
(五) Network information
l我们可以通过在已知服务的列表/etc/services(用来给端口号设计一个有象征性的名字这样客户就可以使用这个名字代替端口号了。)中添加自己的服务。
l另外,我们还可以调用主机数据库函数通过计算机的名字来解析出主机地址。实现这种功能主要通过咨询网络配置文件如/etc/hosts,或者网络信息服务such as NIS(网络信息服务)和DNS(域名服务)。
l主机数据库函数被声明如下:
同样,关于服务还有端口号的信息可以被获取通过以下几个服务信息函数:
如果我们通过gethostbyname来获取主机数据库信息,而且希望大印结果,那么需要使用下面的函数转换网络主机地址到4点分格式的字符串:
返回值:-1为失败。
l还有一个新函数可以用来获取主机名的。
返回值:0成功,-1失败。
lFor example:(这里不进行错误检测)
Char myname[256];
Char *host,**names,**addrs;
Struct hostent *hostinfo;
Gethostname(myname,255);
Host = myname;
Hostinfo = gethostbyname(host);
Printf(“Name:%s\n”,hostinfo->h_name);
Printf(“results for host %s:\n”,host);
Printf(“aliases:”);
names = hostinfo->h_aliases;
While(*names){
Printf(“ %s”,*names);
Names++;
}
Printf(“\n”);
/*这里一般要插入判断地址类型是否为AF_INET,*/
Addrs = hostinfo->h_addr_list;
While(*addrs){
Printf(“ %s”,inet_ntoa(*(struct in_addr *)*addrs));
Addrs++;
}
Printf(“\n”);
Exit(0);
假如计算机名为tilde:执行后将打印:
Results for host tilde:(对应host)
Name : tilde.localnet(对应hostinfo->h_name)
Aliases: tilde (对应hostinfo->aliases)
192.168.1.1 158.152.x.x(对应hostinfo->h_addr_list)
(五) The Internet Daemon(xinetd/inetd)
UNIX系统提供若干个网络服务一直打开,这些网络守护进程(xinetd(in linux systems) or inetd (in unix systems))在一系列的端口地址监听连接,当有客户连接一个服务的时候,守护进程运行适当的服务。这样削短了执行服务所必须的诗句,它们能够按照规定被启动。
l关于xinetd的常规配置文件典型的在/etc/xinetd.conf和在/etc/xinetd.d目录。