linux程序设计——套接字(第十五章)

58 篇文章 0 订阅
57 篇文章 9 订阅

第15章    套接字

在本章中,介绍进程间通信的另一种方法,与第13,14章讨论的方法相比,它有着明显的不同.当目前为止,讨论的所有机制都依靠一台计算机系统的共享资源实现.这里的资源可以是文件系统空间,共享的物理内存或消息队列,但只有运行在同一台机器上的进程才能使用它们.
伯克利版本的UNIX系统引入了一种新的通信工具---- 套接字接口(socket interface),它是管道概念的一种扩展.linux系统支持套接字接口.可以与使用管道类似的方法来使用套接字,但套接字还包括了计算机网络中的通信. 一台机器上的进程可以使用套接字和另外一台机器上的进程通信,这样就可以支持分布在网络中的客户/服务器系统.同一台机器上的进程之间也可以使用套接字进行通信.
此外,微软的Windows系统也通过可公开获取的Windows Socket技术规范实现套接字接口.Windows系统的套接字服务是由系统文件Winsock.dll来提供的.因此,Windows程序可以通过网络和linux/Unix计算机进行通信来实现客户/服务器系统,反之亦然,虽然WinSock的编程接口和UNIX套接字不尽相同,但它同样是以套接字为基础的.

15.1    什么是套接字

套接字(socket)是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上运行,也可以跨网络进行.linux所提供的功能(如打印服务,连接数据库和提供Web页面)和网络工具(如用于远程登陆的rlogin和用于文件传输的ftp)通常都是通过套接字来进行通信的.
套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分开来.套接字机制可以实现将多个客户连接到一个服务器.

15.2    套接字连接

在开始学习linux系统中的套接字连接是如何建立之前,需要先理解 套接字应用程序是如何通过套接字来维持一个连接的.
首先,服务器应用程序用系统调用socket来创建一个套接字,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他进程共享.
接下来,服务器进程会给套接字起个名字
.本地套接字的名字是linux文件系统中的文件名,一般放在/tmp或/usr/tmp目录中.对于网络套接字,它的名字是与客户连接的特定网络有关的服务标识符(端口号或访问点).这个标识符允许linux将进入的针对特定端口号的连接转到正确的服务器进程.例如,Web服务器一般在80端口上创建一个套接字,这是一个专用于此目的的标识符.Web浏览器直到对于用户想要访问的Web站点,应该使用端口号80来建立HTTP连接.用系统调用bind来给套接字命令,然后服务器进程就开始等待客户连接到这个命名套接字.系统调用listen的作用是创建一个队列并将其用于存放来自客户的进入连接,服务器通过系统调用accept来接收客户的连接.
服务器调用accept时,它会创建一个与原有的命名套接字不同的新套接字.这新套接字只用于与这个特定的客户进行通信,而命名套接字则被保留下来继续处理来自其他客户的连接.如果服务器编写得当,它就可以充分利用多个连接带来的好处.Web服务器就会这么做以同时服务来自许多客户的页面请求.对一个简单的服务器来说,后续的客户将在监听队列中等待,直到服务器再次准备就绪.
基于套接字系统的客户端更加简单.客户首先调用socket创建一个未命名的套接字,然后将服务器的命名套接字作为一个地址调用connect与服务器建立连接.
一旦连接建立,就可以像使用底层的文件描述符那样用套接字来实现双向的数据通信.

编写程序client1.c
/*************************************************************************
 > File Name:    client1.c
 > Description:  client1.c程序是套接字客户端程序,它创建一个未命名的套接字,然后把它连接到服务器套接字server_socket.
 > Author:       Liubingbing
 > Created Time: 2015年07月20日 星期一 20时35分43秒
 > Other:        client1.c
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

int main()
{
	int sockfd;
	int len;
	struct sockaddr_un address;
	int result;
	char ch = 'A';

	/* socket函数为客户创建一个套接字
	 * 第一个参数指定协议族,AF_UNIX表示为UNIX域协议(文件系统套接字)
	 * 第二个参数指定这个套接字的通信类型,SOCK_STREAM表示一个有序,可靠,面向连接的双向字节流
	 * 第三个参数指定使用的协议,一般默认为0 
	 * 返回描述符 */
	sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
	/* 根据服务器的情况给套接字命名 */
	address.sun_family = AF_UNIX;
	strcpy(address.sun_path, "server_socket");
	len = sizeof(address);
	/* 将套接字连接到服务器的套接字 */
	result = connect(sockfd, (struct sockaddr *)&address, len);

	if (result == -1) {
		perror("osps: client1");
		exit(1);
	}

	/* 现在可以通过sockfd进行读写操作 */
	write(sockfd, &ch, 1);
	read(sockfd, &ch, 1);
	printf("char from server = %c\n", ch);
	close(sockfd);
	exit(0);
}
它创建一个未命名的套接字,然后把它连接到服务器套接字server_socket.
运行程序client1时,它会失败,因为还没有创建服务器端的命名套接字,如下所示:

编写程序server1.c,它接受来自客户程序的连接。它首先创建一个服务器套接字,将它绑定到一个命名,然后监听队列,开始接受客户的连接。
/*************************************************************************
 > File Name:    server1.c
 > Description:  server1.c程序是服务器程序,它接收来自客户程序的连接.它首先创建一个服务器套接字,将它绑定到一个名字,然后创建监听队列,接受客户的连接.
 > Author:       Liubingbing
 > Created Time: 2015年07月20日 星期一 20时36分01秒
 > Other:        server1.c
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

int main()
{
	int server_sockfd, client_sockfd;
	int server_len, client_len;
	struct sockaddr_un server_address;
	struct sockaddr_un client_address;

	/* 删除之前的套接字,为服务器创建一个未命名的套接字*/
	unlink("server_socket");
	server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
	/* 命名套接字 */
	server_address.sun_family = AF_UNIX;
	strcpy(server_address.sun_path, "server_socket");
	server_len = sizeof(server_address);
	bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
	/* 创建一个连接队列,开始等待客户进行连接 */
	listen(server_sockfd, 5);
	while (1) {
		char ch;
		printf("server waiting\n");
		client_len = sizeof(client_address);
		client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);
		read(client_sockfd, &ch, 1);
		ch ++;
		write(client_sockfd, &ch, 1);
		close(client_sockfd);
	}
}
这个例子中的服务器程序一次只能为一个客户服务。它从客户那里读取一个字符,增加它的值,然后再把它写回去。
在更复杂的系统中,服务器需要为每个客户执行更多的处理工作,这种一次只为一个客户服务的做法就变得不可接受。
因为其他客户只能等到服务器结束上一个客户的处理任务后才能处理它的连接。后面将介绍允许同时处理多个连接的解决方案。
运行服务器程序时,它创建一个套接字开始等待客户的连接。如果在后台启动它,让它独立地运行,就可以在前台启动客户程序。如下所示:
$ ./server1 &

现在运行客户程序,就可以成功地连接到服务器。因为服务器套接字已经存在,所以可以连接到它并与服务器进行通信。服务器在开始等待客户连接时会打印出一条消息,上面的例子中,服务器等待的是一个文件系统套接字,所以可以用普通的ls命令来看到它。
注意:用完一个套接字后,就应该把它删除,即使是在程序因接收到一个信号而异常终止的情况下也应该这样做。这可以避免文件系统因充斥着无用的文件而变得混乱。
$ ls -lF server_socket

访问权限前面的字符s和这一行末尾的等号=表示该设备的类型是"套接字"。套接字的创建过程与普通文件一样,它的访问权限会被当前的掩码值所修改。
如果使用ps命令,可以看到服务器正运行在后台,它目前处理休眠状态,因此它没有消耗CPU资源。如下所示:
$ ps lx


服务器的输出和客户的输出在终端上混在一起,但还是可以看出服务器从客户那里接收了一个字符,将它的值增加,然后再返回它。
接着服务器继续运行并等待下一个客户的到来。如果同时运行多个客户,它们将依次服务,看到的数据可能更加混乱。如下所示:
$ ./client1 & ./client1 & ./client1 &

15.2.1    套接字属性

要想完全理解上面的例子中使用的系统调用,需要学习一些UNIX网络方面的知识。
套接字的特性由3个属性确定,它们是:域(domain),类型(type)和协议(protocol)。套接字还用地址作为它的名字。地址的格式随域(又被称为协议族,protocol family)的不同而不同。每个协议又可以使用一个或多个地址来定义地址格式。
1.套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它指的是Internet网络,许多linux局域网使用的都是该网络。
当然因特网自身使用的也是它。其底层的协议——网际协议IP只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即人们常说的IP地址。
"下一代"互联网协议Ipv6被设计用于客户标准IP带来的一些问题,特别是可用地址数量有限的问题。Ipv6使用一个不同的套接字域AF_INET6和一个不同的地址格式。
虽然几乎总是用域名来指定因特网上的联网机器,但它们都会被替换为底层的IP地址。例如192.168.1.99就是一个IP地址。所有的IP地址都用4个数字表示,每个数字都小于256,即所谓的点分四元组表示法(dotted quad)。
当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址。
服务器计算机上可能同时又多个服务正在运行。客户可以通过IP端口来指定一台联网机器上的某个特定服务。
在系统内部,端口通过分配一个唯一的16位的整数来标识,在系统外部,则需要通过IP地址和端口号的组合来确定。套接字作为通信的终点,它必须在开始通信之前绑定一个端口。
服务器在特定的端口等待客户的连接。知名服务所分配的端口号在所有linux和UNIX机器上都是一样的。它们通常小于1024,比如打印机缓冲队列进程(515)、rlogin(513)、ftp(21)和httpd(80)等。其中最后一个就是Web服务器的标准端口。 一般情况下,小于1024的端口号都是为系统服务保留的,并且所服务的进程都必须具有超级用户权限。
X/Open规范在头文件netdb.h中定义了一个常量IPPORT_RESERVED,它代表保留端口号的最大值。
因为标准服务都对应标准的端口号,所以计算机之间可以轻松的互联,而不需要首先协商一个正确的端口号。本地服务可以使用非标准的端口地址。
第一个例子中的域是UNIX文件系统域AF_UNIX,即使是一台还未联网的计算机上的套接字也可以使用这个域。这个域的底层协议就是文件输入/输出,而它的地址就是文件名。
服务器套接字的地址是server_socket,当运行服务器程序时,就可以在当前目录下看到这个地址。
其他可以使用的域还包括:基于ISO标准协议的网络所使用的AF_ISO和用于施乐(Xerox)网络系统的AF_XNS域。
2.套接字类型
一个套接字域可能有多种不同的通信方式,而每种通信方式又有其不同的特性,但AF_UNIX域的套接字没有这样的问题,它们提供了一个可靠的双向通信路径。
在网络域中,需要注意底层网络的特性,以及不同的通信机制是如何受到它们的影响的。
因特网协议提供了两种通信机制:流(istream)和数据报(datagram)。它们有着截然不同的服务层次。
流套接字
流套接字(在某些方面类似于标准的输入/输出流)提供的是一个有序、可靠、双向字节流的连接。因此,发送的数据可以确保不会丢失、复制或乱序到达,并且在这一过程中发生的错误也不会显示出来。

大的信息将被分片、传输、再重组。这很像一个文件流,它接收大量的数据,然后以小数据库的形式将它们写入底层磁盘。流套接字的行为是可预见的。
流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。它们也是AF_UNIX域中常用的套接字类型。
TCP/IP代表的是传输控制协议/网际控制协议(Transmission Constrol Protocol和Internet Protocol).
IP协议是针对数据包的底层协议,它提供从一台计算机通过网络到达另一台计算机的路由。
TCP协议提供排序、流控和重传,以确保大数据的传输可以完整地到达目的地或报告一个适当的错误条件。
数据报套接字
与流套接字相反,由类型SOCK_DGRAM指定的数据报套接字不建立和维持一个连接。它对可以发送的数据报的长度有限制。数据报作为一个单独的网路信息被传输,它可能会丢失、复制或乱序到达。

数据报套接字是在AF_INET域中通过UDP/IP连接实现的,它提供的是一种无序的不可靠服务(UDP代表的是用户数据报协议)。
但从资源的角度来看,相对来说它们开销比较小,因为不需要维持网络连接,而且无需花费时间建立连接,所以它们的速度也很快。
数据报适用于信息服务中的"单次(single-shot)"查询,它主要用于提供日常状态信息或执行低优先级的日志记录。
它的优点是服务器的崩溃不会给客户造成不方便,也不会要求客户重启,因为基于数据报的服务器通常不保留连接信息,所以它们可以在不打扰其客户的前提下停止并重启。
3.套接字协议
只要底层的传输机制不止一个协议来提供要求的套接字类型,就可以为套接字选择一个特定的协议。

15.2.2    创建套接字

函数作用
socket系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问套接字。
函数原型
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
创建的套接字是一条通信线路的一个端点。
函数参数
第一个参数:domain参数指定协议族
第二个参数:type参数指定这个套接字的通信类型
第三个参数:protocol参数指定使用的协议。
domain参数可以指定的协议族如下所示:
域                          说明
AF_UNIX                  UNIX域协议(文件系统套接字)
AF_INET                  ARPA因特网协议(UNIX网络套接字)
AF_ISO                   ISO标准协议
AF_NS                    施乐(Xerox)网络系统协议
AF_IPS                   Novell IPX协议
AF_APPLETALK   Appletalk DDS
最常用的套接字域是AF_UNIX和AF_INET,前者用于通过UNIX和linux文件系统的本地套接字,后者用于UNIX网络套接字.AF_INET套接字可以用于通过包括因特网在内的TCP/IP网络进行通信的程序.微软Windows系统的Winsock接口也提供了对这个套接字域的访问功能.
socket函数的参数type指定用于新套接字的通信特性.它取值包括SOCK_STREAM和SOCK_DGRAM.
SOCK_STREAM是一个有序,可靠,面向连接的双向字节流.对AF_INET域套接字来说,它默认是通过一个TCP连接来提供这一特性的,TCP连接在两个流套接字端点之间建立.数据可以通过套接字连接进行双向传递.TCP协议所提供的机制可以用于分片和重组长消息,并且可以重传可能在网络中丢失的数据.
SOCK_DGRAM是数据报服务,可以用它来发送最大长度固定(通常比较小)的消息,但消息是否被正确传递或消息是否不会乱序到达并没有保证.对于AF_INET域套接字来说,这种类型的通信是由UDP数据报来提供的.
通信所用的协议一般是由套接字类型和套接字域来决定,通常不需要选择.只有当需要选择时,才会用到protocol参数,就爱那个该参数设置为0表示使用默认协议.
函数返回值
socket系统调用分那会一个描述符,它在许多方面都类似于底层的文件描述符,当这个套接字连接到另一端的套接字后,就可以用read和write系统调用,通过这个描述符来在套接字上发送和接收数据了.close系统调用用于结束套接字连接.


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值