Berkeley socket 套接字编程详解

                                              Berkeley 套接字
6.1 引言
网络程序设计全靠套接字接受和发送信息,尽管套接字这个词好象显得有些神秘,但
其实这个概念极易理解。
这章主要讲述Sockets API(Application Program Interface),以及一些其他的细节(比
如Socket 的历史、数据中的常用结构等),通过这些介绍,使读者慢慢掌握Linux 下的Socket
编程。
6.2 概述
在开始介绍有关编程的知识之前,首先让我们来了解一些与socket 有关的背景知识。
6.2.1 Socket 的历史
在80 年代早期,远景研究规划局(Advanced Research Projects Agency, ARPA)资助了
佳利福尼亚大学伯克利分校的一个研究组,让他们将TCP/IP 软件移植到UNIX 操作系统
中,并将结果提供给其他网点。作为项目的一部分,设计者们创建了一个接口,应用进程
使用这个接口可以方便的进行通信。他们决定,只要有可能就使用以有的系统调用,对那
些不能方便的容入已有的函数集的情况,就再增加新的系统调用以支持TCP/IP 功能。
这样做的结果就出现了插口接口(Berkeley Socket),这个系统被称为Berkeley UNIX
或BSD UNIX。(TCP/IP 首次出现在BSD 4.1 版本release 4.1 of Berkeley Software
Distribution)。
由许多计算机厂商,都采用了Berkeley UNIX,于是许多机器上都可以使用Socket 了。
这样,Socket 接口就被广泛使用,到现在已经成为事实上的标准。(图6-1)
6.2.2 Socket 的功能
Socket 的英文原意就是“孔”或“插座”,现在,作为BSD UNIX 的进程通讯机制,
取其后一种意义。日常生活中常见的插座,有的是信号插座,有的是电源插座,有的可以
接受信号(或能量),有的可以发送信号(或能量)。假如电话线与电话机之间安放一个插
座(相当于二者之间的接口,这一部分装置物理上是存在的)则Socket 非常相似于电话插
座。
将电话系统与面向连接的Socket 机制相比,有着惊人相似的地方。以一个国家级的电
话网为例。电话的通话双方相当于相互通信的两个进程;通话双方所在的地区(享有一个
全局唯一的区号)相当于一个网络,区号是它的网络地址;区内的一个单位的交换机相当
于一台主机,主机分配给每个用户的局内号码相当于Socket 号(下面将谈到)。
第6 章berkeley 套接字- 137 -
图6-1 socket 接口示意图
任何用户在通话之前,首先要占有一部电话机,相当于申请一个Socket 号;同时要知
道对方的电话号码,相当于对方有一个Socket。然后向对方拨号呼叫,相当于发出连接请
求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并
空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正
式通话,相当于连接成功。双方通话的过程,是向电话机发出信号和从电话机接受信号的
过程,相当于向Socket 发送数据和从Socket 接受数据。通话结束后,一方挂起电话机,
相当于关闭Socket,撤消连接。
在电话系统中,一般用户只能感受到本地电话机和对方电话号码的存在,建立通话的
过程、话音传输的过程以及整个电话系统的技术细节对它都是透明的,这也与Socket 机制
非常相似。Socket 利用网间网通信设施实现进程通信,但它对通信设施的细节毫不关心,
只要通信设施能提供足够的通信能力,它就满足了。
至此,我们对Socket 进行了直观的描述。抽象出来,Socket 实质上提供了进程通信的
端点。进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互
通信的。正如打电话之前,双方必须各自拥有一台电话机一样。
每一个Socket 都用一个半相关描述:
{协议,本地地址,本地端口}
一个完整的Socket 则用一个相关描述
{协议,本地地址,本地端口,远程地址,远程端口}
每一个Socket 有一个本地的唯一Socket 号,由操作系统分配。
最重要的是,Socket 是面向客户-服务器模型而设计的,针对客户和服务器程序提供
不同的Socket 系统调用。客户随机申请一个Socket 号(相当于一个想打电话的人可以在
- 138 - Linux网络编程
任何一台入网的电话上拨叫呼叫);服务器拥有全局公认的Socket,任何客户都可以向它
发出连接请求和信息请求(相当于一个被呼叫的电话拥有一个呼叫方知道的电话号码)。
Socket 利用客户— 服务器模式巧妙的解决了进程之间建立通信连接的问题。服务器
Socket 为全局所公认非常重要。两个完全随机的用户进程之间,因为没有任何一方的Socket
是固定的,就像打电话却不知道别人的电话号码,要通话是不可能的。
6.2.3 套接字的三种类型
套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)
及原始套接字。
1.流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。如果你通过流式套接字发送了顺
序的数据:“1”、“2”。那么数据到达远程时候的顺序也是“1”、“2”。
流式套接字可以做什么呢?你听说过Telnet 应用程序吗?听过?哦,最常用的BBS 服
务,以及系统的远程登陆都是通过Telnet 协议连接的。Telnet 就是一个流式连接。你是否
希望你在Telnet 应用程序上输入的字符(或汉字)在到达远程应用程序的时候是以你输入
的顺序到达的?答案应该是肯定的吧。还有WWW 浏览器,它使用的HTTP 协议也是通过
流式套接字来获取网页的。事实上,如果你Telnet 到一个Web Site 的80 端口上,然后输
入“GET 网页路径名”然后按两下回车(或者是两下Ctrl+回车)然后你就得到了“网页
路径名”所代表的网页!
流式套接字是怎样保证这种应用层次上的数据传输质量呢?它使用了TCP( The
Transmission Control Protocol)协议(可以参考RFC-793 来得到TCP 的细节)。TCP 保证
了你的数据传输是正确的,并且是顺序的。TCP 是经常出现的TCP/IP 中的前半部分。IP
代表Internet Protocol(因特网协议,参考RFC-791)IP 只处理网络路由。
第6 章berkeley 套接字- 139 -
图6-2 面向连接的socket 的工作流程
2.数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序
的,并且不保证可靠,无差错。原始套接字允许对低层协议如IP 或ICMP 直接访问,主要
用于新的网络协议实现的测试等。
数据报套接字(Datagram Sockets)怎样呢?为什么它叫做“无连接”?应该怎样处理
它们呢?为什么它们是不可靠的?好的,这里有一些事实:
l 如果你发送了一个数据报,它可能不会到达。
l 它可能会以不同的顺序到达。
l 如果它到达了,它包含的数据中可能存在错误。
数据报套接字也使用IP,但是它不使用TCP,它使用使用者数据报协议UDP(User
Datagram Protocol 可以参考RFC 768)
为什么说它们是“无连接”的呢?因为它(UDP)不像流式套接字那样维护一个打开
- 140 - Linux网络编程
的连接,你只需要把数据打成一个包,把远程的IP 贴上去,然后把这个包发送出去。这个
过程是不需要建立连接的。UDP 的应用例子有: tftp, bootp 等。
那么,数据包既然会丢失,怎样能保证程序能够正常工作呢?事实上,每个使用UDP
的程序都要有自己的对数据进行确认的协议。比如, TFTP 协议定义了对于每一个发送出
去的数据包,远程在接受到之后都要回送一个数据包告诉本地程序:“我已经拿到了!”(一
个“ACK” 包)。如果数据包发的送者在5 秒内没有的得到回应,它就会重新发送这个
数据包直到数据包接受者回送了“ACK” 信号。这些知识对编写一个使用UDP 协议的
程序员来说是非常必要的。
无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务
程序之间的相互作用。若使用无连接的套接字编程,程序的流程可以用图6-3 表示。
图6-3 无连接的socket 工作流程
面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而
且往往是并发服务器。使用面向连接的套接字编程,可以通过图6-2 来表示。
套接字工作过程如下:服务器首先启动,通过调用socket()建立一个套接字,然后调用
bind()将该套接字和本地网络地址联系在一起,再调用listen()使套接字做好侦听的准备,
并规定它的请求队列的长度,之后就调用accept()来接收连接。客户在建立套接字后就可调
用connect()和服务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用read()
和write()来发送和接收数据。最后,待数据传送结束后,双方调用close()关闭套接字。
3.原始套接字
原始套接字主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是
没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。
第6 章berkeley 套接字- 141 -
6.3 Linux 支配的网络协议
网络协议是系统进行系统与系统间通讯的的接口。在Linux 系统上, TCP/IP
(Transmission Control / Internet Protocol)是最常见的。TCP/IP 是一个网络协议协议族,
我们将在下面进行详细介绍。
6.3.1 什么是TCP/IP?
用简单的话来讲, TCP/IP 是一个网络协议族的名字,协议是所有软件产品必须遵守
的、能够保证各种软件产品能够正确通讯的规则。协议还定义了每一部分数据块怎样管理
所传输的数据。
精确一点说,一个协议定义了两个应用程序或是计算机之间能够进行互相通讯,对于
其中的每一个(应用程序或计算机)都保证使用同样的标准。TCP/IP 代表传输控制协议/
网络协议(注意:它们是两个不同的协议!),它是做为软件的网络组成部件而设计的。每
个TCP/IP 的协议都有他专门的工作,比如万维网(WWW),发送电子邮件(E-mail),传
输文件(Ftp),提供远程登陆服务等。
TCP/IP 协议可以根据提供的不同的服务分为几组:
1.控制数据的协议
TCP(传输控制协议Transmission Control Protocol)以连接为基础,也就是说两台电脑
必须先建立一个连接,然后才能传输数据。事实上,发送和接受的电脑必须一直互相通讯
和联系。
UDP(使用者数据报协议User Datagram Protocol)它是一个无连接服务,数据可以直
接发送而不必在两台电脑之间建立一个网络连接。它和有连接的TCP 相比,占用带宽少,
但是你不知道你的数据是否真正到达了你的客户端,而客户端收到的数据也不知道是否还
是原来的发送顺序。
2.数据路由协议
路由协议分析数据包的地址并且决定传输数据到目的电脑最佳路线。他们也可以把大
的数据分成几部分,并且在目的地再把他们组合起来。
IP(因特网协议Internet Protocol)处理实际上传输数据。
ICMP(因特网控制信息协议Internet Control Message Protocol)处理IP 的状态信息,
比如能影响路由决策的数据错误或改变。
RIP(路由信息协议Routing Information Protocol)它是几个决定信息传输的最佳路由
路线协议中的一个。
OSPF(Open Shortest Path First)一个用来决定路由的协议。网络地址协议决定了命名
电脑地址的方法:使用一个唯一的数字和一个字母名字。
ARP(地址决定协议Address Resolution Protocol)确定网络上一台电脑的数字地址。
DNS(域名系统Domain Name System)从机器的名字确定一个机器的数字地址。
RARP(反向地址决定协议Reverse Address Resolution Protocol)确定网络上一台计算
机的地址,和ARP(地址决定协议Address Resolution Protocol)正好相反。
3.用户服务
BOOTP(启动协议Boot Protocol) 由网络服务器上取得启动信息,然后将本地的网
- 142 - Linux网络编程
络计算机启动。
FTP(文件传输协议File Transfer Protocol)通过国际互连网从一台计算机上传输一个
或多个文件到另外一台计算机。
TELNET(远程登陆)允许一个远程登陆,使用者可以从网络上的一台机器通过TELNET
连线到另一台机器,就像使用者直接在本地操作一样。
EGP(外部网关协议Exterior Gateway Protocol)为外部网络传输路由信息。
GGP(网关到网关协议Gateway-to-Gateway Protocol)在网关和网关之间传输路由协
议。
IGP(内部网关协议Interior Gateway Protocol)在内部网络传输路由信息。
3.其他协议(也为网络提供了重要的服务)
NFS(网络文件系统Network File System)允许将一台机器的目录被另一台机器上的
用户安装(Mount)到自己的机器上,就像是对本地文件系统进行操作一样进行各式各样
的操作。
NIS(网络信息服务Network Information Service)对整个网络用户的用户名、密码进
行统一管理,简化在NIS 服务下整个网络登陆的用户名/密码检查。
RPC(远程过程调用Remote Procedure Call)通过它可以允许远程的应用程序通过简
单的、有效的手段联系本地的应用程序,反之也是。
SMTP(简单邮件传输协议Simple Mail Transfer Protocol)一个专门为电子邮件在多台
机器中传输的协议,平时发邮件的SMTP 服务器提供的必然服务。
SNMP(简单网络管理协议Simple Network Management Protocol)这是一项为超级用
户准备的服务,超级用户可以通过它来进行简单的网络管理。
6.4 套接字地址
好了,关于socket 的背景知识我们已经讲得够多了,下面,就让我们正式开始揭开socket
的神秘面纱吧!
6.4.1 什么是Socket?
大家经常谈论“Socket”(套接字),那么一个套接字究竟是什么呢?
一个套接字可以这样来解释:它是通过标准的UNIX 文件描述符和其他的程序通讯的
一个方法。
6.4.2 Socket 描述符
使用UNIX 的黑客高手有这么一句话:“恩,在UNIX 系统中,任何东西都是一个文
件。”这句话描述了这样一个事实:在UNIX 系统中,任何对I/O 的操作,都是通过读或写
一个文件描述符来实现的。
一个文件描述符只是一个简单的整形数值,代表一个被打开的文件(这里的文件是广
义的文件,并不只代表不同的磁盘文件,它可以代表一个网络上的连接,一个先进先出队
列,一个终端显示屏幕,以及其他的一切)。在UNIX 系统中任何东西都是一个文件!!所
以如果你想通过Internet 和另外一个程序通讯的话,你将会是通过一个文件来描述符实现
的。你最好相信这一点。
第6 章berkeley 套接字- 143 -
好的,你已经相信Socket 是一个文件描述符了,那么我们应该怎样才能得到这个代表
网络连接的文件描述符呢?你现在一定非常在意这个问题。是这样的:你首先调用系统函
数socket(),它返回一个套接字(Socket)描述符,然后你就可以通过对这个套接字描述符
进行一些操作:系统函数send() 和recv()(你可以使用“man”命令来查找系统帮助:man
send, man recv)。
你会想:“套接字描述符是一个文件描述符,为什么不能用对文件操作的write() 和
read() 来进行套接字通讯呢?”事实上, write() 和read() 是可以对套接字描述符进行操
作的,但是,通过使用send() 和recv() 函数,你可以对网络数据的传输进行更好的控制!
6.4.3 一个套接字是怎样在网络上传输数据的?
我们已经谈过了网络协议层,那么我们还应该继续多了解一些东西:物理网络上的数
据是怎样传送的。
我们可以认为是这样的:
数据被分成一个一个的包(Packet),包的数据头(或数据尾)被第一层协议(比如TFTP
协议) 加上第一层协议数据;然后整个包(包括内部加入的TFTP 信息头)被下层协议再
次包装(比如UDP),再这之后数据包会再次被下层协议包装(比如IP 协议),最后是被
最底层的硬件层(物理层)包装上最后一层信息(Ethernet 信息头)。
当接受端的计算机接收到这个包后,硬件首先剥去数据包中的Ethernet 信息头,然后
内核在剥去IP 和UDP 信息头,最后把数据包提交给TFTP 应用程序,由TFTP 剥去TFTP
信息头,最后得到了原始数据。
下面我们再大致回顾一下著名的网络层次模型。
通过这个网络模型,你可以写套接字的应用程序而不必在乎事实上数据在物理层中的
传输方法(无论是以太网,还是并口、AUI 或是其他的什么方法)。
因为已经有程序在底层为你处理了这些问题了。下面是OSI 模型,你可以记住它来应
付一些测验。
l 应用层
l 表示层
l 会话层
l 传输层
l 网络层
l 数据链路层
l 物理层
物理层就是硬件层(比如并口,以太网)。应用程序层离物理层很远很远,以至于它
可以不受物理层的影响。
上面这个模型是最一般的模型,但是在Linux 中,真正用到的模型是下面这样子的:
l 应用层(Telnet,Ftp,等等)
l 主机间对话层(TCP 和UDP)
l 网络层(IP 和路由)
l 网络底层(相当于OSI 模型中网络、数据链路和物理层)
现在,你大概已经明白各个协议层是怎样对原始数据进行包装和解包的了吧。看见对
- 144 - Linux网络编程
于每一个数据包有多少项工作需要做了吗?对!你对每一个数据包都需要自己用“cat”命
令来查看协议信息头!
开个玩笑。对流式套接字你所需要做的只是调用send() 函数来发送数据。而对于数据
报套接字,你需要自己加个信息头,然后调用sendto() 函数把数据发送出去。Linux 系统
内核中已经建立了Transport Layer 和Internet Layer。硬件负责NetworkAccess Layer。简单
而有效,不是吗?
6.5 套接字的一些基本知识
好的,从现在开始,我们应该谈些和程序有关的事情了。
6.5.1 基本结构
首先,我想介绍一些使用套接字编程中常见的网络数据结构对大家会很有帮助。
1.struct sockaddr
这个结构用来存储套接字地址。
数据定义:
struct sockaddr {
unsigned short sa_family; /* address族, AF_xxx */
char sa_data[14]; /* 14 bytes的协议地址*/
};
sa_family 一般来说,都是“AFINET”。
sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一
切的。
为了处理struct sockaddr, 程序员建立了另外一个相似的结构struct sockaddr_in:
struct sockaddr_in (“in” 代表“Internet”)
struct sockaddr_in {
short int sin_family; /* Internet地址族*/
unsigned short int sin_port; /* 端口号*/
struct in_addr sin_addr; /* Internet地址*/
unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/
};
这个结构提供了方便的手段来访问socket address(struct sockaddr)结构中的每一个元
素。注意sin_zero[8] 是为了是两个结构在内存中具有相同的尺寸,使用sockaddr_in 的时
候要把sin_zero 全部设成零值(使用bzero()或memset()函数)。而且,有一点很重要,就
是一个指向struct sockaddr_in 的指针可以声明指向一个sturct sockaddr 的结构。所以虽然
socket() 函数需要一个structaddr * ,你也可以给他一个sockaddr_in * 。注意在struct
sockaddr_in 中,sin_family 相当于在struct sockaddr 中的sa_family,需要设成“AF_INET”。
最后一定要保证sin_port 和sin_addr 必须是网络字节顺序(见下节)!
2.struct in_addr
其定义如下:
/* 因特网地址(a structure for historical reasons) */
第6 章berkeley 套接字- 145 -
struct in_addr {
unsigned long s_addr;
};
如果你声明了一个“ ina ” 作为一个struct sockaddr_in 的结构, 那么
“ina.sin_addr.s_addr”就是4 个字节的IP 地址(按网络字节顺序排放)。需要注意的是,
即使你的系统仍然使用联合而不是结构来表示struct in_addr,你仍然可以用上面的方法得
到4 个字节的IP 地址(一些#defines 帮了你的忙)。
6.5.2 基本转换函数
在前面提到了网络字节顺序。那么什么是网络字节顺序,它有什么特殊性,又如何将
我们通常使用的数据转换成这种格式呢?
1.网络字节顺序
因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,
而有的系统是底位在前,高位在后),而网络传输的数据大家是一定要统一顺序的。所以
对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换(比如IP 地
址的表示,端口号的表示)。但是内部字节顺序和网络字节顺序相同的机器该怎么办呢?
是这样的:它们也要调用转换函数,但是真正转换还是不转换是由系统函数自己来决定的。
2.有关的转化函数
我们通常使用的有两种数据类型:短型(两个字节)和长型(四个字节)。下面介绍
的这些转换函数对于这两类的无符号整型变量都可以进行正确的转换。
如果你想将一个短型数据从主机字节顺序转换到网络字节顺序的话,有这样一个函
数:它是以“h”开头的(代表“主机”);紧跟着它的是“to”,代表“转换到”;然后是“n”
代表“网络”;最后是“s”,代表“短型数据”。H-to-n-s,就是htons() 函数(可以使用Host
to Network Short 来助记)
很简单吧??我没有理解的时候觉得这个函数不好记呢??
你可以使用“n”,“h”,“to”,“s”,“l”的任意组合??当然,你要在可能的情况下
进行组合。比如,系统是没有stolh() 函数的(Short to Long Host?)。
下面给出套接字字节转换程序的列表:
l htons()——“Host to Network Short” 主机字节顺序转换为网络字节顺序(对无符号
短型进行操作4 bytes)
l htonl()——“Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符
号长型进行操作8 bytes)
l ntohs()——“Network to Host Short “ 网络字节顺序转换为主机字节顺序(对无符
号短型进行操作4 bytes)
l ntohl()——“Network to Host Long “ 网络字节顺序转换为主机字节顺序(对无符
号长型进行操作8 bytes)
注意:现在你可能认为自己已经精通于这几个函数的用处了??你可能会想:“恩??在我的68000
机器内部,字节的表示顺序已经是网络字节顺序了,那么我的程序里就不必调用htonl() 来转换我的IP 地
址了”。是的,你可能是对的。但是假如你把你的程序移植到一个内部字节顺序和网络字节顺序相反的机
器上,你的程序就会运行不正常!所以,一定要记住:在你把数据发送到Internet 之前,一定要把它的字
- 146 - Linux网络编程
节顺序从主机字节顺序转换到网络字节顺序!
在struct sockaddr_in 中的sin_addr 和sin_port 他们的字节顺序都是网络字节顺序,而
sin_family 却不是网络字节顺序的。为什么呢?
这个是因为sin_addr 和sin_port 是从IP 和UDP 协议层取出来的数据,而在IP 和UDP
协议层,是直接和网络相关的,所以,它们必须使用网络字节顺序。然而, sin_family 域
只是内核用来判断struct sockaddr_in 是存储的什么类型的数据,并且, sin_family 永远也
不会被发送到网络上,所以可以使用主机字节顺序来存储。
3.IP 地址转换
很幸运, Linux 系统提供和很多用于转换IP 地址的函数,使你不必自己再写出一段
费力不讨好的子程序来吃力的变换IP。
首先,让我假设你有一个struct sockaddr_in ina,并且你的IP 是166.111.69.52 ,你想
把你的IP 存储到ina 中。你可以使用的函数: inet_addr() ,它能够把一个用数字和点表
示IP 地址的字符串转换成一个无符号长整型。你可以像下面这样使用它:
ina.sin_addr.s_addr = inet_addr(“166.111.69.52”);
注意:
l inet_addr() 返回的地址已经是网络字节顺序了,你没有必要再去调用htonl() 函数,是不是很
方便呢?
l 上面的用法并不是一个很好的习惯,因为上面的代码没有进行错误检查。如果inet_addr() 函数
执行错误,它将会返回–1??等等!二进制的无符号整数值–1 相当于什么?相当于255.255.255.255 !! 一
个广播用的IP 地址!没有办法,你只能在你自己的程序里进行对症下药的错误检查了。
好,现在我们已经可以把字符串的IP 地址转换成长整型了。那么还有没有其他的方法
呢?如果你有一个struct in_addr 并且你想把它代表的IP 地址打印出来(按照数字.数字.数
字.数字的格式)??
这里,你可以使用函数inet_ntoa()(“ntoa”代表“Network to ASCII”):
printf(“%s”, inet_ntoa(ina.sin_addr));
这段代码将会把struct in_addr 里面存储的网络地址以数字.数字.数字.数字的格式显
示出来。
注意:
l inet_ntoa() 使用struct in_addr 作为一个参数,不是一个长整型值。
l inet_ntoa() 返回一个字符指针,它指向一个定义在函数inet_ntoa() 中的static 类型字符串。所
以每次你调用inet_ntoa(),都会改变最后一次调用inet_ntoa() 函数时得到的结果。
比如:
char *a1, a2;
a1 = inet_ntoa(ina1.sin_addr); /* this is 166.111.69.52 */
a2 = inet_ntoa(ina2.sin_addr); /* this is 166.111.69.53 */
printf(“address 1: %s/n”,a1);
printf(“address 2: %s/n”,a2);
将会显示出:
address 1: 166.111.69.53
address 2: 166.111.69.53
第6 章berkeley 套接字- 147 -
如果你想把结果保存下来,那么你可以在每次调用inet_ntoa() 后调用strcpy() 将结果存到另外
一个你自己的字符串中。
在后面,将会介绍怎样把域名转换为IP。
6.6 基本套接字调用
Linux 支持伯克利(BSD)风格的套接字编程.它同时支持面向连接和不连接类型的
套接字。
在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接.再不连接通
讯中数据被作为信息的一部分被交换.无论那一种方式,服务器总是最先启动,把自己绑
定(Banding)在一个套接字上,然后侦听信息.服务器究竟怎样试图去侦听就得依靠你编
程所设定的连接的类型了。
你需要了解的一些系统调用:
l socket()
l bind()
l connect()
l listen()
l accept()
l send()
l recv()
l sendto()
l recvfrom()
l close()
l shutdown()
l setsockopt()
l getsockopt()
l getpeername()
l getsockname()
l gethostbyname()
l gethostbyaddr()
l getprotobyname()
l fcntl()
我们将在以下详细介绍这些系统调用。
6.6.1 socket() 函数
取得套接字描述符!(记得我们以前说过的吗?它其实就是一个文件描述符)
socket 函数的定义是下面这样子的:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain , int type , int protocol);
- 148 - Linux网络编程
你是否对int domain 和int type、int protocol 有些疑惑呢?调用socket()的参数是什么
呢?
首先,domain 需要被设置为“AF_INET”,就像上面的struct sockaddr_in。然后,type
参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,
只需要把protocol 设置为0 。
注意:事实上, domain 参数可以取除了“ AF_INET ”外的很多值,types 参数也可以取除了
“SOCK_STREAM”或“SOCK_DGRAM”的另外类型。具体可以参考socket 的man pages(帮助页)。
套接字创建时没有指定名字.客户机用套接字的名字读写它。这就是下面的绑定函数
所要做之事.
socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,
socket()函数返回–1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的man
pages)
6.6.2 bind() 函数
bind()函数可以帮助你指定一个套接字使用的端口。
当你使用socket() 函数得到一个套接字描述符,你也许需要将socket 绑定上一个你的
机器上的端口。
l 当你需要进行端口监听listen()操作,等待接受一个连入请求的时候,一般都需要
经过这一步。比如网络泥巴(MUD),Telnet a.b.c.d 4000。
l 如果你只是想进行连接一台服务器,也就是进行connect() 操作的时候,这一步
并不是必须的。
bind()的系统调用声明如下:
#include <sys/types.
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值