欧军 吴清秀 裴云 张洪
海南软件职业技术学院 海南 571400
Linux是一个免费的类Unix操作系统,最初是由芬兰人Linus Torvalds于1991年开发。Linux操作系统具有良好的稳定性以及强大的网络功能。Linux系统平台被广泛应用到网络通信程序的开发中。网络通信编程要用到网络套接字(Socket)。Socket是一种描述符,它是网络通信的基本操作单元。数据传输是一种特殊的 I/O,提供了不同主机间的进程互相通信的端点,这些进程在通信前各自建立一个Socket,并通过对Socket 的读写操作实现网络通信的功能。
Socket为套接字,是一种双向的通信端口。网络程序设计全靠套接字接受和发送信息。Socket 的英文原意就是“孔”或“插座”,现在,作为BSD UNIX 的进程通讯机制,取其后一种意义。日常生活中常见的插座,有的是信号插座,有的是电源插座,有的可以接受信号(或能量),有的可以发送信号(或能量)。假如电话线与电话机之间安放一个插座(相当于二者之间的接口,这一部分装置物理上是存在的)则Socket非常相似于电话插座。
图1 Socket 通信示意图
图1为使用socket进行通信的示意图。Socket通信与打电话具有相似之处,电话的通话双方相当于相互通信的两个进程;通话双方所在的地区(享有一个全局惟一的区号)相当于一个网络,区号是它的网络地址。例如,用户甲要给用户丁打电话,在通话之前,用户甲必须要使用一部电话机,这部电话机就对应有一个Socket 号;同时要知道用户丁的电话号码,相当于对方有一个Socket。然后向用户丁拨电话,相当于发出连接请求。如果用户丁在场并且处于空闲状态(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是向电话机发出信号和从电话机接受信号的过程,相当于向Socket 发送数据和从Socket 接受数据。通话结束后,一方挂起电话机,相当于关闭Socket,撤消连接。在电话系统中,通信的双方只需要知道本地电话机和对方电话号码就可以了,其他的相关通信设施并不需要知道,这一点与Socket机制非常相似。Socket 利用网间通信设施实现进程通信,但它对通信设施的细节毫不关心。
通过以上的例子,可以得出Socket 实质上是提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。正如打电话之前,双方必须各自拥有一台电话机一样。但是这时候会出现一个现象:进程通信时,如何来确认接收到的数据所要发送的进程。譬如,当用户甲给用户丁打电话,当用户丁所在的电话响后,当接电话的不是用户丁时,用户甲肯定会说:“我找用户丁,请帮我转接一下”。那么在进程通信中是如何实现的呢?需要一种标识信息,用于描述网络通信数据发往的进程。TCP/IP协议提出了协议端口的概念,用于标识通信的进程。当进程与某个端口绑定后,操作系统会将收到的给该端口的数据发往该进程。与文件描述符类似,每一个端口都有被称为端口号的整数类型的标识符,该标识符用于区分不同的端口。不同的协议可以使用相同的端口号进行数据传输。例如,TCP使用了344的端口号,UDP同样也可以使用344端口号进行数据传输。端口号为一个16位的无符号整数,其取值范围为0-65535。低于256的端口被作为系统的保留端口号,主要用于系统进程的通信,不在这一范围的端口号被称为自由端口号,可以由进程自由使用。每一个Socket 都用一个半相关描述:{协议,本地地址,本地端口},一个完整的Socket 则用一个相关描述{协议,本地地址,本地端口,远程地址,远程端口}。每一个 Socket 有一个本地的惟一Socket 号,由操作系统分配。最重要的是,Socket 是面向客户-服务器模型而设计的,针对客户和服务器程序提供不同的Socket 系统调用。客户随机申请一个Socket 号(相当于一个想打电话的人可以在任何一台入网的电话上拨叫呼叫);服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。
Socket 利用客户-服务器模式巧妙的解决了进程之间建立通信连接的问题。服务器Socket 为全局所公认非常重要。两个完全随机的用户进程之间,因为没有任何一方的Socket是固定的,就像打电话却不知道别人的电话号码,要通话是不可能的。
Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发 TCP/IP网络上的应用程序。要学 Internet上的 TCP/IP网络编程,必须理解Socket接口。
Socket接口设计者最先是将接口放在 Unix操作系统里面的。如果了解 Unix系统的输入和输出的话,就很容易了解 Socket。网络的 Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
套接字有三种类型,其中流式套接字可以实现可靠的数据传输。根据套接字类型的不同,套接字通信又分为面向连接的通信和无连接的通信。面向连接的套接字通信使用的协议为TCP协议。该协议可以保证数据的可靠传输,当然这一可靠性是以一定的性能损失为代价的。无连接的套接字通信方式使用 UDP协议。对比面向连接的套接字通信,这一方式无需在客户机与服务器之间建立连接,所以使用这一方式无法保证数据的可靠传输。网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端。网络程序是先有服务器程序启动,等待客户端的程序运行并建立连接。一般的来说是服务端的程序在一个端口上监听,直到有一个客户端的程序发来了请求,服务器端根据该请求提供相应的服务。图2为套接字的工作流程。为了实现服务器与客户机间的通信,服务器与客户机都必须创建套接字。服务器在创建套接字后,需要指定监听的端口来等待客户机,因此,还有绑定端口号的操作。之后,服务器将处于监听状态,等待客户机来连接指定端口。当接收到客户机的连接请求后,服务器调用Accept函数来建立与客户机间的通信。在成功建立通信后,就可以通过Read函数或Write函数进行通信。客户端处的流程与服务器相比,简单一些。客户端在创建套接字后,调用Connect函数去连接服务器指定的端口。在服务器接收连接后,客户机与服务器之间就可以通过Write函数和Read函数实现数据通信了。
图2 Socket程序设计流程
通过设计客户机/服务器模式程序来进一步深入分析socket通信原理。server.c和client.c使用流套接字将服务器的信息传递给客户机。server.c为服务端的实现。首先创造建套接字,通信域为PF_INET,然后调用bind 函数将指定的端口等到信息与套接字关联起来,调用 listen函数实现对指定端口的监听。当有连接请求时,通过调用accept函数建立与客户机的连接,最后调用 write和read函数来与客户机进行信息交换。client.c为客户端程序,实现较为简单。首先调用connect 函数来连接指定的服务器和端口,然后通过write 函数和read函数来实现与服务器之间的通信。
使用gcc编译server.c和client.c,获得可执行文件server和client。
[root@localhost src]#gcc server.c -o server
[root@localhost src]#gcc client.c -o client
先执行 server,然后运行 client。 ./server portnumber& (portnumber随便取一个大于1204且不在/etc/services中出现的号码 就用8888好了),然后运行 。
[root@localhost src]# ./server 10284
[1] 13583
[root@localhost src]# ./client localhost 1028
Server get connection from 127.0.0.1 I have received: Welcome to HNCST!
在Linux系统中,不同主机或相同主机之间的不同进程可以通过套接字实现通信。套接字通信是一种服务器/客户机模型的通信方式。面向连接的套接字通信使用的协议为TCP协议。该协议可以保证数据的可靠传输,当然这一可靠性是以一定的性能损失为代价的。通过设计服务器端和客户端程序,使用流套接字实现了网络中主机间的面向连接通信。
[1]朱居正,高冰.Red Hat Enterprise Linux网络管理[M].北京:清华大学出版社.2005.
[2]张轶博,孙占峰.Linux应用大全[M].北京:机械工业出版社.2000.
[3]骆耀祖.Linux网络服务器管理教程[M].电子工业出版社.2007.
[4]陈忠文.Linux操作系统实训教程[M].中国电力出版社.2006.
[5]冯昊.Linux服务器配置与管理(第2版) [M].清华大学出版社.2009.
[6]何文华,梁竞敏.Linux操作系统实验与实训[M].人民邮电出版社.2007.