第8章Socket网络程序开发

第9章 Socket网络程序开发
9.1 Socket简介
?

计算机网络是由一系列网络通信协议组成的,其中的核心协议是传输层的TCP/IP和 UDP协议。TCP是面向连接的,通信双方保持一条通路,好比目前的电话线,使用 telnet登陆BBS,用的就是TCP协议;UDP是无连接的,通信双方都不保持对方的状态, 浏览器访问Internet时使用的HTTP协议就是基于UDP协议的。TCP和UDP协议都非常复 杂,尤其是TCP协议,为了保证网络传输的正确性和有效性,必须进行一系列复杂的纠 错和排序等处理。 Socket是建立在传输层协议(主要是TCP和UDP)上的一种套接字规范,最初是由美 国加州Berkley大学提出,它定义两台计算机间进行通信的规范(也是一种编程规范), 如果说两台计算机是利用一个“通道“进行通信,那么这个“通道“的两端就是两个 套接字。套接字屏蔽了底层通信软件和具体操作系统的差异,使得任何两台安装了TCP 协议软件和实现了套接字规范的计算机之间的通信成为可能。 微软的Windows Socket规范(简称winsock)对Berkley的套接字规范进行了扩展,利用 标准的Socket的方法,可以同任何平台上的Socket进行通信;利用其扩展,可以更有效 地实现在Windows平台上计算机间的通信。在Delphi中,其底层的Socket也应该是 Windows的Socket。Socket减轻了编写计算机间通信软件的难度,但总的说来还是相当复 杂的(这一点在后面具体会讲到);Inprise在Delphi中对Windows Socket进行了有效的封 装,使得用户可以很方便地编写网络通信程序。

五层模型图
应用层(HTTP、POP3 、SMTP、FTP等
socket 传输层(TCP、UDP) 网络层(IP、IPX)

数据链路层(Eth、 ATM、PPP等)
物理层

9.2 Socket通信过程分析
?

?

Socket最常用的范例便是客户机/服务器模型。在这种方案中客户应 用程序向服务器程序请求服务。这种方式隐含了在建立客户机/服务 器间通讯时的非对称性。客户机/服务器模型工作时要求有一套为客 户机和服务器所共识的惯例来保证服务能够被提供(或被接受)。这 一套惯例包含了一套协议。它必须在通讯的两头都被实现。根据不同 的实际情况,协议可能是对称的或是非对称的。在对称的协议中,每 一方都有可能扮演主从角色;在非对称协议中,一方被不可改变地认 为是主机,而另一方则是从机。一个对称协议的例子是Internet中用 于终端仿真的TELNET。而非对称协议的例子是Internet中的FTP。无 论具体的协议是对称的或是非对称的,当服务被提供时必然存在“客 户进程”和“服务进程”。 一个服务程序通常在一个众所周知的地址监听对服务的请求,也 就是说,服务进程一直处于休眠状态,直到一个客户对这个服务的地 址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提 供服务-对客户的请求作出适当的反应。这一请求/相应的过程可以 简单的用图2-1表示。虽然基于连接的服务是设计客户机/服务器应用 程序时的标准,但有些服务也是可以通过数据报套接口提供的。

客户机
进程通讯
请求

服务器

设施
请求

响应

响应

图 2 -1 客 户 机 /服 务 器 模 型

9.3 Socket API函数
(1)WSAStartup函数
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData ); 使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函 数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明 副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的 Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系 统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的 Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库 中的其它Socket函数了。该函数执行成功后返回0。 例:假如一个程序要使用2.1版本的Socket,那么程序代码如下 wVersionRequested = MAKEWORD( 2, 1 ); err = WSAStartup( wVersionRequested, &wsaData );

?

?

(2)WSACleanup函数 int WSACleanup (void); 应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函 数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。 (3)socket函数 SOCKET socket( int af, int type, int protocol ); 应用程序调用socket函数来创建一个能够进行网络通信的套接字。第 一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族, 该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类 型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个 参数指定应用程序所使用的通信协议。该函数如果调用成功就返回新 创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接 字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接 字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关 系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存 放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应 的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描 述符表但是套接字数据结构都是在操作系统的内核缓冲里。下面是一 个创建流套接字的例子: struct protoent *ppe; ppe=getprotobyname("tcp"); SOCKET ListenSocket=socket(PF_INET,SOCK_STREAM,ppe>p_proto);

?

?

(4)closesocket函数 int closesocket( SOCKET s ); closesocket函数用来关闭一个描述符为s套接字。由于每个进程中 都有一个套接字描述符表,表中的每个套接字描述符都对应了一个 位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接 字描述符指向同一个套接字数据结构。套接字数据结构中专门有一 个字段存放该结构的被引用次数,即有多少个套接字描述符指向该 结构。当调用closesocket函数时,操作系统先检查套接字数据结构 中的该字段的值,如果为1,就表明只有一个套接字描述符指向它, 因此操作系统就先把s在套接字描述符表中对应的那条表项清除, 并且释放s对应的套接字数据结构;如果该字段大于1,那么操作系 统仅仅清除s在套接字描述符表中的对应表项,并且把s对应的套接 字数据结构的引用次数减1。 closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。 (5)send函数 int send(SOCKET s, const char FAR *buf, int len, int flags ); 不论是客户还是服务器应用程序都用send函数来向TCP连接的另 一端发送数据。客户程序一般用send函数向服务器发送请求,而服 务器则通常用send函数来向客户程序发送应答。该函数的第一个参 数指定发送端套接字描述符;第二个参数指明一个存放应用程序要 发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数; 第四个参数一般置0。这里只描述同步Socket的send函数的执行流程。 当调用该函数时,send先比较待发送数据的长度len和套接字s的发 送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回 SOCKET_ERROR;

?

(6)recv函数 int recv(SOCKET s,char FAR *buf, int len, int flags ); 不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端 接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参 数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;第三 个参数指明buf的长度;第四个参数一般置0。这里只描述同步Socket 的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s 的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲 中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s 的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查 套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接 收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把 数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注 意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用 几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是 copy数据,真正的接收数据是协议来完成的),recv函数返回其实际 copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR; 如果recv函数在等待协议接收数据时网络中断了,那么它返回0。 注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开 了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号 的默认处理是进程终止。

(7)bind函数 int bind( SOCKET s, const struct sockaddr FAR *name, int namelen ); 当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址 和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP 地址和一个特定的端口号。客户程序一般不必调用bind函数来为其 Socket绑定IP地址和断口号。该函数的第一个参数指定待绑定的Socket 描述符;第二个参数指定一个sockaddr结构, (8)八、listen函数 int listen( SOCKET s, int backlog ); 服务程序可以调用listen函数使其流套接字s处于监听状态。处于监听 状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳 backlog个客户连接请求。假如该函数执行成功,则返回0;如果执行 失败,则返回SOCKET_ERROR。
?

?

(9)accept函数 SOCKET accept( SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen ); 服务程序调用accept函数从处于监听状态的流套接字s的客户连接请 求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来 与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字 的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失 败就返回INVALID_SOCKET。该函数的第一个参数指定处于监听状 态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地 址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构 的长度。下面是一个调用accept的例子: struct sockaddr_in ServerSocketAddr; int addrlen; addrlen=sizeof(ServerSocketAddr); ServerSocket=accept(ListenSocket,(struct sockaddr *)&ServerSocketAdd r,&addrlen);

?

(10)connect函数 int connect( SOCKET s, const struct sockaddr FAR *name, int namelen ); 客户程序调用connect函数来使客户Socket s与监听于name 所指定的计算机的特定端口上的服务Socket进行连接。如果 连接成功,connect返回0;如果失败则返回 SOCKET_ERROR。下面是一个例子: struct sockaddr_in daddr; memset((void *)&daddr,0,sizeof(daddr)); daddr.sin_family=AF_INET; daddr.sin_port=htons(8888); daddr.sin_addr.s_addr=inet_addr("133.197.22.4"); connect(ClientSocket,(struct sockaddr *)&daddr,sizeof(daddr) );

9.4 Delphi Socket控件的使用
?

Winsocket细分为两种组件:ClientSocket和ServerSocket,它们分 别作为客户端和服务器端的组件。通过这两种组件之间的通信,再加 上辅助的应用程序代码,就可以实现一个简单的通信程序。当然,如 果你想在客户端程序中再引入ServerSocket的话,那么客户端程序就 可以充当服务器了,可以对其他的客户端程序的请求进行响应。

如果正在编写服务器端程序,就必须设置ServerSocket组件的 Port属性。设置此参数主要是因为在同一台计算机上可能运行着多个 服务器程序,而它们可能总在不停地接受来自于远程客户端程序的连 接请求。也可以设置Service属性,它指示了ServerSocket所提供的 服务类型,比如:FTP、HTTP等,然后设置Active属性为True。 如果正在编写客户端程序,则设置ClientServer组件的属性就多 一些。Port属性应设置成和服务器端的Port属性值一致,另外Host的 属性必须正确设置,它是一个只读属性,在设计时不可用。Host指示 了客户程序所要连接的远程服务器的主机名。也可以设置Address属 性,也就是远程主机的IP地址

?

建立与远程计算机的连接

要在远程计算机系统之间进行数据传输,首先必须在通信的两台 主机之间建立连接。
服务器端的ServerSocket组件调用Open方法初始化Socket连接, 同时也就设置了Active属性为True,将ServerSocket组件设置成侦听 模式,随时侦测是否有连接请求。 如果服务器接受了客户程序的连接请求,则触发OnAccept事件, 如下代码就是处理接受连接后服务器程序所要做的工作。 procedure Myform..ServerSocketAccept(Sender: TObject, Socket: TCustomWinSocket); begin IsServer := True; end; 在客户端程序中,ClientSocket组件则设置Port、Host等必须的 属性,然后设置Active属性为True,提出连接请求。

?
?

数据发送与接收
一旦服务器端接受了客户机方面的连接请求,客户机就可以发送数据。 这时,在客户机和服务器之间就拥有了一个Socket,通过此Socket双 方实现通信。所以Socket属性很重要,它又拥有很多的方法,用其中 的几个简单的方法,就可以实现数据的发送和接收。 客户机端用如下形式: ClientSocket1.socket.sendtext('string you want to send'); 在服务器端采用如下形式: ServerSocket1.socket.recievetext( str: string);

此函数返回接收到的字符串的长度,将字符串存储在变量str中。
上述是数据传输的最简单的例子,你还可以采用Socket属性所提 供的其他方法来实现复杂的数据传输。

9.5 Delphi网络通信实例----简单聊 天工具
?

下面是一个简单的聊天程序,其中客户机和服务机是同一个程序,当 客户机(服务器)在一个memo1中输入一段文字然后敲入回车,该段 文字就可以显示在服务器(客户机)的memo2中,反之亦成立。具体步 骤如下: 1、新建一个form,任意命名,不妨设之为chatForm;放上一个 MainMenu(在Standard栏中),建立ListenItem、ConnectItem、 Disconnect和Exit菜单项;在从Internet栏中选择TServerSocket、 TClientSocket添加到chatForm中,其中把TClientSocket的名字设为 ClientSocket, port设为1025,默认的active为false;把 TServerSocket的名字设为ServerSocket,port设为1025,默认的 active为false,其他的不变;再放入两个memo,一个命名为memo1, 另外一个命名为memo2,其中把memo2的color设置为灰色,因为主要用 来显示对方的输入。下面我们一边编写代码一边解释原因。

?

2、双击ListemItem。写入如下代码: procedure TChatForm.ListenItemClick(Sender: TObject); begin ListenItem.Checked := not ListenItem.Checked; if ListenItem.Checked then begin ClientSocket.Active := False; ServerSocket.Active := True; end else begin if ServerSocket.Active then ServerSocket.Active := False; end; end; 该程序段的说明如下:当用户选择ListemItem时,该ListenItem 取反,如果选中的话,说明处于Listen状态,读者要了解的是: listen是Socket作为Server时一个专有的方法,如果处于listen,则 ServerSocket设置为活动状态;否则,取消listen,则关闭 ServerSocket。实际上,只有用户一开始选择该菜单项,表明该程序 用作Server。反之,如果用户选择ConnectItem,则必然作为Client使 用。 end;

?

3、双击ConnectItem,敲入以下代码。 procedure TChatForm.ConnectItemClick(Sender: TObject); begin if ClientSocket.Active then ClientSocket.Active := False; if InputQuery('Computer to connect to', 'Address Name:', Server) then if Length(Server) > 0 then with ClientSocket do begin Host := Server; Active := True; ListenItem.Checked := False; end; end; 这段程序的主要功能就是当用户选择ConnectItem菜单项时,设置应 用程序为客户机,弹出input框,让用户输入服务器的地址。这也就是我 们不一开始固定ClientSocket的host的原因,这样用户可以动态地连接 不同的服务器。读者需要了解的是主机地址只是Socket作为客户机时具 有的一个属性,Socket作为服务器时“一般“不用地址,因为它同本机 绑定。

?

?

4、在memo1的keydown方法中写入如下代码: procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_Return then if IsServer then ServerSocket.Socket.Connections[0].SendText(Memo1.Lines[Memo1 .Lines.Count - 1]) else ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count 1]); end; 该段代码的作用很明显,就是开始发消息了。其中如果是Server 的话,它只向第一个客户机发消息,由于一个服务器可以连接多个客 户机,而同客户机的每一个连接都由一个Socket来维持,因此 ServerSocket.Socket.Connnections数组中存储的就是同Client维持 连接的Socket。在标准Socket中,服务器方的Socket通过accept()方 法的返回值获取维持同客户机连接的Socket,而发送、接受消息的方法 分别为send(sendto)和recv(recvfrom), Delphi对此进行了封装。 5、其余代码的简要介绍。 procedure TChatForm.ServerSocketAccept(Sender: TObject; Socket: TCustomWinSocket); begin IsServer := True; end;

?

ServerSocket的Accept方法,当客户机第一次连接时完成,通过其参 数可以认为,它是在标准的accept方法后执行的,因为有 TCustomWinSocket这个参数类型,它应该是标准Server方Socket的返 回值。 procedure TChatForm.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket); begin Memo2.Lines.Add(Socket.ReceiveText); end; procedure TChatForm.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket); begin Memo2.Lines.Add(Socket.ReceiveText); end; 这两段代码分别是服务器方和客户机方在收到对方的消息时,由 Delphi触发的,作用是在memo2中显示收到的消息。其中, ClientSocketRead中的Socket实际上就是Socket本身,而在 ServerSocketClientRead中的Socket实际上是 ServerSocket.Socket.Connection[]中的某个Socket。不过在Delphi 中,对服务器方的Socket进行了有效的封装。

?

?

?

?

procedure TChatForm.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket); begin Memo2.Lines.Clear; end; procedure TChatForm.ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin ListenItemClick(nil); end; 这两段比较简单。其中ServerSocketClientConnect在 ServerSocket收到一个新的连接时触发。而 ClientSocketDisconnect在ClientSocket发出Disconncet时触发。 procedure TChatForm.Exit1Click(Sender: TObject); begin ServerSocket.Close; ClientSocket.Close; Close; end; procedure TChatForm.Disconnect1Click(Sender: TObject); begin ClientSocket.Active := False; ServerSocket.Active := True; end;


相关文档

  • 第八章Socket网络开发
  • C#网络程序开发-Socket篇
  • 【第8章】网络协议栈4 - Socket编程
  • 第8章_基于Socket和数据报编程的网络开发
  • C#.Net网络程序开发-Socket篇
  • Visual C#.Net 网络程序开发-Socket篇
  • Socket网络程序开发汇总
  • Socket网络程序开发剖析
  • Visual C#.Net 网络程序开发-Socket
  • C#网络程序开发-Socket篇非常适合新手
  • 电脑版