FreeBSD套接字模型
BSD套接字构建在基本的UNIX®模型上: 一切都是文件。那么,在我们的例子中,套接字将使我接收一个HTTP文件,就这么说。然后我们要负责将 PNG文件从中提取出来。

  由于联网的复杂性,我们不能只使用 open系统调用,或open() C 函数。而是我们需要分几步 “打开”一个套接字。

  一旦我们做了这些,我们就能以处理任何文件描述符 的方式处理套接字。我们从它读取 (read),向它写入(write),建立管道(pipe), 必定还要关闭(close)它。
   重要的套接字函数
  FreeBSD提供了与套接字相关的不同函数, “打开”一个套接字我们只需要四个函数。 有时我们只需要两个。
   1 客户端-服务器差异
  典型情况中,以套接字为基础的数据通信一端是一个 服务器,另一端是一个客户端。

   1.1 通用元素
   1.1.1 socket

  这一个函数在客户端和服务器都要使用:socket(2)。它是这样被声明的:

    int socket(int domain, int type, int protocol);

  返回值的类型与open的相同,一个整数。 FreeBSD从和文件句柄相同的池中分配它的值。这就是允许套接字被以对文件相同的方式处理的原因。

  参数domain告诉系统你需要使用什么协议族。有许多种协议族存在,有些是某些厂商专有的,其它的都非常通用。协议族的声明在 sys/socket.h中

  使用PF_INET是对于 UDP, TCP 和其它网间协议(IPv4)的情况。

  对于参数type有五个定义好的值,也在 sys/socket.h中。这些值都以 “SOCK_”开头。 其中最通用的是SOCK_STREAM, 它告诉系统你正需要一个可靠的流传送服务 (和PF_INET一起使用时是指 TCP)。

  如果指定SOCK_DGRAM, 你是在请求无连接报文传送服务 (在我们的情形中是UDP)。

  如何你需要处理基层协议 (例如IP),或者甚至是网络接口 (例如,以太网),你就需要指定 SOCK_RAW。

  最后,参数protocol取决于前两个参数,并非总是有意义。在以上情形中,使用取值0。

  未连接的套接字: 对于函数socket 我们还没有指定我们要连往什么其它(主机)系统。 我们新建的套接字还是未连接的。这是有意的:拿电话类比,我们刚把调制解调器接在电话线上。我们既没有告诉调制解调器发起一个呼叫,也不会应答电话振铃。

   1.1.2 sockaddr
  各种各样的套接字函数需要指定地址,那是一小块内存空间 (用C语言术语是指向一小块内存空间的指针)。在 sys/socket.h中有各种各样如 struct sockaddr的声明。 这个结构是这样被声明的:

/*
  * 内核用来存储大多数种类地址的结构
 */
struct sockaddr {
     unsigned char   sa_len;     /* 总长度 */
     sa_family_t sa_family;  /* 地址族 */
    char        sa_data[14];    /* 地址值,实际可能更长 */
 };
#define SOCK_MAXADDRLEN 255     /* 可能的最长的地址长度 */
  注意对于sa_data域的定义具有不确定性。 那只是被定义为14字节的数组, 注释暗示内容可能超过14字节

  这种不确定性是经过深思熟虑的。套接字是个非常强大的接口。多数人可能认为比Internet接口强不到哪里 ──大多数应用现在很可能都用它 ──套接字可被用于几乎任何种类的进程间通信, Internet(更精确的说是IP)只是其中的一种。

  sys/socket.h提到的各种类型的协议 将被按照地址族对待,并把它们就列在 sockaddr定义的前面:

/*
* 地址族
*/
#define AF_UNSPEC       0               /* 未指定 */
#define AF_LOCAL        1               /* 本机 (管道,portal) */
#define AF_UNIX         AF_LOCAL        /* 为了向前兼容 */
#define AF_INET         2               /* 网间协议: UDP, TCP, 等等 */
#define AF_IMPLINK      3               /* arpanet imp 地址 */
#define AF_PUP          4               /* pup 协议: 例如BSP */
#define AF_CHAOS        5               /* MIT CHAOS 协议 */
#define AF_NS           6               /* 施乐(XEROX) NS 协议 */
#define AF_ISO          7               /* ISO 协议 */
#define AF_OSI          AF_ISO
#define AF_ECMA         8               /* 欧洲计算机制造商协会 */
#define AF_DATAKIT      9               /* datakit 协议 */
#define AF_CCITT        10              /* CCITT 协议, X.25 等 */
#define AF_SNA          11              /* IBM SNA */
#define AF_DECnet       12              /* DECnet */
#define AF_DLI          13              /* DEC 直接数据链路接口 */
#define AF_LAT          14              /* LAT */
#define AF_HYLINK       15              /* NSC Hyperchannel */
#define AF_APPLETALK    16              /* Apple Talk */
#define AF_ROUTE        17              /* 内部路由协议 */
#define AF_LINK         18              /* 协路层接口 */
#define pseudo_AF_XTP   19              /* eXpress Transfer Protocol (no AF) */
#define AF_COIP         20              /* 面向连接的IP, 又名 ST II */
#define AF_CNT          21              /* Computer Network Technology */
#define pseudo_AF_RTIP  22              /* 用于识别RTIP包 */
#define AF_IPX          23              /* Novell 网间协议 */
#define AF_SIP          24              /* Simple 网间协议 */
#define pseudo_AF_PIP   25              /* 用于识别PIP包 */
#define AF_ISDN         26              /* 综合业务数字网(Integrated Services Digital Network) */
#define AF_E164         AF_ISDN         /* CCITT E.164 推荐 */
#define pseudo_AF_KEY   27              /* 内部密钥管理功能 */
#define AF_INET6        28              /* IPv6 */
#define AF_NATM         29              /* 本征ATM访问 */
#define AF_ATM          30              /* ATM */
#define pseudo_AF_HDRCMPLT 31           /* 由BPF使用,就不必在接口输出例程
                                        * 中重写头文件了
                                        */
#define AF_NETGRAPH     32              /* Netgraph 套接字 */
#define AF_SLOW         33              /* 802.3ad 慢速协议 */
#define AF_SCLUSTER     34              /* Sitara 集群协议 */
#define AF_ARP          35
#define AF_BLUETOOTH    36              /* 蓝牙套接字 */
#define AF_MAX          37
  用于指定IP的是 AF_INET。这个符号对应着常量 2。

  在sockaddr中的域 sa_family指定地址族, 从而决定预先只确定下大致字节数的 sa_data的实际大小。

  特别是当地址族 是AF_INET时,我们可以使用 struct sockaddr_in,这可在 netinet/in.h中找到,任何需要 sockaddr的地方都以此作为实际替代。

/*
* 套接字地址,Internet风格
*/
struct sockaddr_in {
   uint8_t     sin_len;
   sa_family_t sin_family;
   in_port_t   sin_port;
   struct  in_addr sin_addr;
   char    sin_zero[8];
};
  我们可这样描绘它的结构:


三个重要的域是: sin_family,结构体的字节1; sin_port,16位值,在字节2和3; sin_addr,一个32位整数,表示 IP地址,存储在字节4-7。

  现在,让我们尝试填满它。让我们假设我们正在写一个 daytime协议的客户端,这个协议只是简单的规定服务器写出一个代表当前日期和时间文本字符串到端口13。 我们需要使用 TCP/IP,所以我们需要指定在地址族域指定 AF_INET。 AF_INET被定义为 2。让我们使用 IP地址192.43.244.18,这指向 美国联邦政府(time.nist.gov)的服务器。


顺便说一下,域sin_addr被声明为类型 struct in_addr,这个类型定义在 netinet/in.h之中:

/*
* Internet 地址 (由历史原因而形成的结构)
*/
struct in_addr {
   in_addr_t s_addr;
};
  而in_addr_t是一个32位整数。

  192.43.244.18 只是为了表示32位整数的方便写法,按每个八位字节列出, 以最高位的字节开始。

  到目前为止,我已经看见了sockaddr。我们的计算机并不将短整数存储为一个16位实体,而是一个2字节序列。同样的,计算机将32位整数存储为4字节序列。

  想象我们这样写程序:

  sa.sin_family      = AF_INET;
   sa.sin_port        = 13;
   sa.sin_addr.s_addr = (((((192 << 8) | 43) << 8) | 244) << 8) | 18;
  结果会是什么样的呢?

  好,那当然是要依赖于其它因素的。在Pentium®或其它x86 为基础的计算机上,它会像这样:



  在另一个不同的系统上,它可能会是:



在一台PDP计算机上,它可能又是另一个样子。 不过上面两种情况是今天最常用的了。

译者注: PDP的字节顺序在英语中称为middle-endian或mixed-endian。例如,原数0x44332211会被PDP存储为0x33441122。 VAX也采用这种字节顺序。



  通常,要书写可移植的代码,程序员假设不存在那些差异。他们回避这种差异(除了他们使用汇编语言写代码的时候)。唉,可你不能在为套接字写代码时那样轻易的回避这种差异。

  为什么?

  因为当与另一台计算机通信时, 你通常不知道对方存储数据时是先存放最高位字节 (MSB)还是最低位字节 (LSB)。

  你可能会有问题,“那么,套接字可以为我把握这种差异吗?”

  它不能。

  这个回答可能先是让你感到惊讶, 请记住通用的套接字接口只明白结构体sockaddr 中的域sa_len和sa_family。 你不必担心那里的字节顺序(当然, 在FreeBSD上sa_family只有一个字节, 但是许多其它的 UNIX® 系统没有 sa_len 并使用2字节给 sa_family,而且数据使用何种顺序都取决于计算机(译者注:此处英文原文的用词为“is native to”))。

  其余的数据,也就只剩下sa_data[14]。 依照地址族,套接字只是将那些数据转发到目的地。

  事实上,我们输入一个端口号, 是为了让其它计算机知道我们需要什么服务。并且,当我们提供服务时, 只有读取了端口号我们才知道其它计算机期望从我们这里获得什么服务。另一方面,套接字只将端口号作为数据转发,完全不去理会(译者注:此处英文原文用词为“interpret”)其中的内容。

  同样的,我们输入IP地址,告诉途经的每台计算机要将我们的数据发送到哪里。 套接字依然只将其按数据转发。

  那就是为什么我们(指程序员,而不是套接字)不得不把使用在我们的计算机上的字节顺序和发送给其它计算机时使用的传统字节顺序区分开来。

  我们将把我们的计算机上使用的字节顺序称为 主机字节顺序, 或者就是主机顺序.

  有一个在IP发送多字节数据的传统: 最高位字节(MSB)优先。 这,我们将用网络字节顺序提及, 或者简单的称为网络顺序。

  现在,如果我们在Intel计算机上编译上面的代码, 我们的主机字节顺序将产生:



  但是网络字节顺序 要求我们先存储数据的最高位字节(MSB):



不幸的是,我们的主机顺序 恰恰与网络顺序相反。

  我们有几种方法解决这个问题。一种是在我们的代码中 倒置数值:

  sa.sin_family      = AF_INET;
   sa.sin_port        = 13 << 8;
   sa.sin_addr.s_addr = (((((18 << 8) | 244) << 8) | 43) << 8) | 192;
  这将欺骗我们的编译器把数据按网络字节顺序存储。在一些情形中,这的确是个有效的办法 (例如,用汇编语言编程)。然而,在多数情形中,这会导致一个问题。

  想象一下,你用C语言写了一个套接字程序。 你知道它将运行在一台Pentium计算机上, 于是你倒着输入你的所有常量,并且把它们强置为 网络字节顺序。 它工作正常。

  然而,有一台,你所信任的旧 Pentium 变成一台生了锈的旧 Pentium。你把它更换为一个 主机顺序与 网络顺序相同的系统。 你需要重新编译你的所有软件。你的所有软件中除了你写的那个程序,都继续工作正常。

  你早已经忘记你将全部常量强置为与 主机顺序相反。你花费宝贵时间拽头发,呼唤你曾经听到过的(有些是你编造的)所有上帝的名字, 用击球棍敲打你的显示器,还上演所有其它的传统仪式 试图找到一个原本好端端的程序突然完成不能工作的原因。

  最终,你找到了原因,发了一通誓言, 开始重写你的代码。

  幸运的是,你不是第一个面对这个问题的人。 其它人已经创建 htons(3) 和 htonl(3) C 语言函数分别将 short and long 从主机字节顺序转换为 网络字节顺序, 并且还有 ntohs(3) 和 ntohl(3) C 语言函数进行着另外的转换。

  在最高位字节(MSB)-最前 的系统上,这些函数什么都不做。在 最低位字节(LSB)-最前的系统上它们将值转换为正确的顺序。

  这样一来,无论你的软件在什么系统上编译, 如果你使用这些函数,你的数据最终都将是正确的顺序。
1.2 客户端函数
  典型情况中,客户端初始化到服务器的连接。 客户端知道要呼叫哪台服务器:它知道服务器的IP地址,并且知道服务器驻守的 端口。这就好比你拿起电话拨号码 (地址),然后,有人应答, 呼叫负责狂欢的人 (端口)。

 1.2.1 connect
  一旦一个客户端已经建立了一个套接字,就需要把它连接到一个远方系统的一个端口上。这使用 connect(2):

int connect(int s, const struct sockaddr *name, socklen_t namelen);
  参数 s 是套接字, 那是由函数socket返回的值。 name 是一个指向 sockaddr的指针,这个结构体我们已经展开讨论过了。 最后,namelen通知系统 在我们的sockaddr结构体中有多少字节。

  如果 connect 成功, 返回 0。否则返回 -1 并将错误码存放于 errno之中。

  有许多种connect可能失败的原因。例如,试图发起一个Internet连接时, IP 地址可能不存在,或可能停机, 或者就是太忙,或者可能没有在指定端口上有服务器监听。或者直接拒绝任何特定代码的请求。

1.2.2 我们的第一个客户端
  现在我们知道足够多去写一个非常简单的客户端, 一个从192.43.244.18获取当前时间并打印到 stdout的程序。

/*
* daytime.c
*
* G. Adam Stanislav 编程
*/
#include
#include
#include
#include

int main() {
 register int s;
 register int bytes;
 struct sockaddr_in sa;
 char buffer[BUFSIZ+1];

 if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
   perror("socket");
   return 1;
 }

 bzero(&sa, sizeof sa);

 sa.sin_family = AF_INET;
 sa.sin_port = htons(13);
 sa.sin_addr.s_addr = htonl((((((192 << 8) | 43) << 8) | 244) << 8) | 18);
 if (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
   perror("connect");
   close(s);
   return 2;
 }

 while ((bytes = read(s, buffer, BUFSIZ)) > 0)
   write(1, buffer, bytes);

 close(s);
 return 0;
}
  继续,把它输入到你的编辑器中,保存为 daytime.c,然后编译并运行:

% cc -O3 -o daytime daytime.c
% ./daytime

52079 01-06-19 02:29:25 50 0 1 543.9 UTC(NIST) *
%
  在这一情形中,日期是2001年6月19日,时间是 02:29:25 UTC。你的结果会很自然的变化。

1.3 服务器函数
  典型的服务器不初始化连接。 相反,服务器等待客户端呼叫并请求服务。服务器不知道客户端什么时候会呼叫, 也不知道有多少客户端会呼叫。服务器就是这样静坐在那儿,耐心等待,一会儿,又一会儿, 它突然发觉自身被从许多客户端来的请求围困,所有的呼叫都同时来到。

  套接字接口提供三个基本的函数处理这种情况。

1.3.1 bind
  端口像是电话线分机:在你拨一个号码后, 你拨分机到一个特定的人或部门。

  有65535个 IP 端口,但是一台服务器通常只处理从其中一个端口进入的请求。这就像告诉电话室操作员我们处于工作状态并在一个特定分机应答电话。 我们使用 bind(2) 告诉套接字我们要服务的端口。

int bind(int s, const struct sockaddr *addr, socklen_t addrlen);
  除了在 addr 中指定端口, 服务器还可以包含其自身的 IP 地址。不过,也可以就使用符号常量 INADDR_ANY,指示服务于无论哪个 IP上的指定端口上的请求。 这个符号和几个相同的常量,声明在 netinet/in.h之中。

#define    INADDR_ANY      (u_int32_t)0x00000000
  想象我们正在为 daytime协议在 TCP/IP的基础上写一个服务器。 回想起使用端口13。我们的sockaddr_in 结构应当像这样:



1.3.2 listen
  继续我们的办公室电话类比, 在你告诉电话中心操作员你会在哪个分机后,现在你走进你的办公室,确认你自己的电话已插上并且振铃已被打开。还有,你确认呼叫等待功能开启,这样即使你正在与其它人通话, 也可听见电话振铃。

  服务器执守所有经过函数 listen(2) 操作的套接字。

int listen(int s, int backlog);
  在这里,变量backlog 告诉套接字在忙于处理上一个请求时还可以接受多少个进入的请求。换句话说,这决定了挂起连接的队列的最大大小。

7.5.1.3.3 accept
  在你听见电话铃响后,你应答呼叫接起电话。 现在你已经建立起一个与你的客户的连接。这个连接保持到你或你的客户挂线。

  服务器通过使用函数 accept(2) 接受连接。

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
  注意,这次 addrlen 是一个指针。这是必要的,因为在此情形中套接字要 填上 addr,这是一个 sockaddr_in 结构体。

  返回值是一个整数。其实, accept 返回一个 新套接字。你将使用这个新套接字与客户通信。

  老套接字会发生什么呢?它继续监听更多的请求 (想起我们传给listen的变量 backlog了吗?),直到我们 close(关闭) 它。

  现在,新套接字仅对通信有意义,是完全接通的。 我们不能再把它传给 listen接受更多的连接。

1.3.4 我们的第一个服务器
  我们的第一个服务器会比我们的第一个客户端复杂一些:我们不仅用到了更多的套接字函数, 还需要把程序写成一个守护程序。

  这最好写成:在绑定端口后建立一个子进程。 主进程随后退出,将控制权交回给 shell (或者任何调用主进程的程序)。

  子进程调用 listen,然后启动一个无休止循环。这个循环接受连接,提供服务, 最后关闭连接的套接字。

/*
* daytimed - 端口 13 的服务器
*
* G. Adam Stanislav 编程
* 2001年6月19日
*/
#include
#include
#include
#include
#include
#include

#define BACKLOG 4

int main() {
   register int s, c;
   int b;
   struct sockaddr_in sa;
   time_t t;
   struct tm *tm;
   FILE *client;

   if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
       perror("socket");
       return 1;
   }

   bzero(&sa, sizeof sa);

   sa.sin_family = AF_INET;
   sa.sin_port   = htons(13);

   if (INADDR_ANY)
       sa.sin_addr.s_addr = htonl(INADDR_ANY);

   if (bind(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
       perror("bind");
       return 2;
   }

   switch (fork()) {
       case -1:
           perror("fork");
           return 3;
           break;
       default:
           close(s);
           return 0;
           break;
       case 0:
           break;
   }

   listen(s, BACKLOG);

   for (;;) {
       b = sizeof sa;

       if ((c = accept(s, (struct sockaddr *)&sa, &b)) < 0) {
           perror("daytimed accept");
           return 4;
       }

       if ((client = fdopen(c, "w")) == NULL) {
           perror("daytimed fdopen");
           return 5;
       }

       if ((t = time(NULL)) < 0) {
           perror("daytimed time");

           return 6;
       }

       tm = gmtime(&t);
       fprintf(client, "%.4i-%.2i-%.2iT%.2i:%.2i:%.2iZ\n",
           tm->tm_year + 1900,
           tm->tm_mon + 1,
           tm->tm_mday,
           tm->tm_hour,
           tm->tm_min,
           tm->tm_sec);

       fclose(client);
   }
}
  我们开始于建立一个套接字。然后我们填好 sockaddr_in 类型的结构体 sa。注意, INADDR_ANY的特定使用方法:

   if (INADDR_ANY)
       sa.sin_addr.s_addr = htonl(INADDR_ANY);
  这个常量的值是0。由于我们已经使用 bzero于整个结构体, 再把成员设为0将是冗余。 但是如果我们把代码移植到其它一些 INADDR_ANY可能不是0的系统上, 我们就需要把实际值指定给 sa.sin_addr.s_addr。多数现在C语言 编译器已足够智能,会注意到 INADDR_ANY是一个常量。由于它是0,他们将会优化那段代码外的整个条件语句。

  在我们成功调用bind后, 我们已经准备好成为一个 守护进程:我们使用 fork建立一个子进程。 同在父进程和子进程里,变量s都是套接字。 父进程不再需要它,于是调用了close, 然后返回0通知父进程的父进程成功终止。

  此时,子进程继续在后台工作。 它调用listen并设置 backlog 为 4。这里并不需要设置一个很大的值, 因为 daytime 不是个总有许多客户请求的协议,并且总可以立即处理每个请求。

  最后,守护进程开始无休止循环,按照如下步骤:

调用accept。 在这里等待直到一个客户端与之联系。在这里,接收一个新套接字,c, 用来与其特定的客户通信。

使用 C 语言函数 fdopen 把套接字从一个 低级 文件描述符 转变成一个 C语言风格的 FILE 指针。 这使得后面可以使用 fprintf。

检查时间,按 ISO 8601格式打印到 “文件” client。 然后使用 fclose 关闭文件。这会把套接字一同自动关闭。

  我们可把这些步骤 概括 起来,作为模型用于许多其它服务器:



  这个流程图很好的描述了顺序服务器, 那是在某一时刻只能服务一个客户的服务器,就像我们的daytime服务器能做的那样。这只能存在于客户端与服务器没有真正的“对话”的时候:服务器一检测到一个与客户的连接,就送出一些数据并关闭连接。整个操作只花费若干纳秒就完成了。

  这张流程图的好处是,除了在父进程 fork之后和父进程退出前的短暂时间内, 一直只有一个进程活跃:我们的服务器不占用许多内存和其它系统资源。

  注意我们已经将初始化守护进程 加入到我们的流程图中。我们不需要初始化我们自己的守护进程 (译者注:这里仅指上面的示例程序。一般写程序时都是需要的。), 但这是在程序流程中设置signal 处理程序、 打开我们可能需要的文件等操作的好地方。

  几乎流程图中的所有部分都可以用于描述许多不同的服务器。 条目 serve 是个例外,我们考虑为一个 “黑盒子”,那是你要为你自己的服务器专门设计的东西, 并且 “接到其余部分上”。

  并非所有协议都那么简单。许多协议收到一个来自客户的请求,回复请求,然后接收下一个来自同一客户的请求。 因此,那些协议不知道将要服务客户多长时间。这些服务器通常为每个客户启动一个新进程 当新进程服务它的客户时,守护进程可以继续监听更多的连接。

  现在,继续,保存上面的源代码为 daytimed.c (用字母d 结束守护程序名是个风俗)。在你编译好后,尝试运行:

% ./daytimed
bind: Permission denied
%
  这里发生了什么?正如你将回想起的, daytime协议使用端口13。 但是所有1024以下的端口保留给超级用户 (否则,任何人都可以启动一个守护进程伪装一个常用端口的服务, 这就导致了一个安全漏洞)。

  再试一次,这次以超级用户的身份:

# ./daytimed
#
  怎么……什么都没有?让我们再试一次:

# ./daytimed

bind: Address already in use
#
  在一个时刻,每个端口只能被一个程序绑定。我们的第一个尝试真的成功了:启动了守护子进程并安静的返回。守护子进程仍然在运行,并且继续运行到你关闭它,或是它使用的系统调用失败,或是你重启计算机时。

  好,我们知道它正在后台运行着。 但是它正在正常工作吗?我们如何知道它是个正常的 daytime 服务器?只需简单的:

% telnet localhost 13

Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
2001-06-19T21:04:42Z
Connection closed by foreign host.
%
  telnet 尝试新协议 IPv6,失败了。又重新尝试 IPv4,而后成功了。守护进程工作正常。

  如果你可以通过telnet 访问另一个 UNIX 系统,你可以用测试远程访问服务器。 我们计算机没有静态 IP 地址, 所以我这样做:

% who

whizkid          ttyp0   Jun 19 16:59   (216.127.220.143)
xxx              ttyp1   Jun 19 16:06   (xx.xx.xx.xx)
% telnet 216.127.220.143 13

Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:11Z
Connection closed by foreign host.
%
  又工作正常了。使用域名还会工作正常吗?

% telnet r47.bfm.org 13

Trying 216.127.220.143...
Connected to r47.bfm.org.
Escape character is '^]'.
2001-06-19T21:31:40Z
Connection closed by foreign host.
%
  顺序说一句,telnet 在我们的守护进程关闭套接字之后打印消息 Connection closed by foreign host (连接被外部主机关闭)。这告诉我们,实际上,在我们的代码中使用 fclose(client); 的工作情况就像前面说的一样。


socket编程原理
2.1 问题的引入
Unix系统的I/O命令集,是从Maltics和早期系统中的命令演变出来的,其模式为打开一读/写一关闭(open-write-read-close)。在一个用户进程进行I/O操作时,它首先调用“打开”获得对指定文件或设备的使用权,并返回称为文件描述符的整型数,以描述用户在打开的文件或设备上进行I/O操作的进程。然后这个用户进程多次调用“读/写”以传输数据。当所有的传输操作完成后,用户进程关闭调用,通知操作系统已经完成了对某对象的使用。




TCP/IP协议被集成到UNIX内核中时,相当于在UNIX系统引入了一种新型的I/O操作。UNIX用户进程与网络协议的交互作用比用户进程与传统的I/O设备相互作用复杂得多。首先,进行网络操作的两个进程在不同机器上,如何建立它们之间的联系?其次,网络协议存在多种,如何建立一种通用机制以支持多种协议?这些都是网络应用编程界面所要解决的问题。

在UNIX系统中,网络应用编程界面有两类:UNIX BSD的套接字(socket)和UNIX System V的TLI。由于Sun公司采用了支持TCP/IP的UNIX BSD操作系统,使TCP/IP的应用有更大的发展,其网络应用编程界面──套接字(socket)在网络软件中被广泛应用,至今已引进微机操作系统Dos和Windows系统中,成为开发网络应用软件的强有力工具,本章将要详细讨论这个问题。

2.2 套接字编程基本概念
在开始使用套接字编程之前,首先必须建立以下概念。

2.2.1 网间进程通信
进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如UNIX BSD中的管道(pipe)、命名管道(named pipe)和软中断信号(signal),UNIX system V的消息(message)、共享存储区(shared memory)和信号量(semaphore)等,但都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。

其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。

为了解决上述问题,TCP/IP协议引入了下列几个概念。


端口

网络中可以被命名和寻址的通信端口,是操作系统可分配的一种资源。

按照OSI七层协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址就不仅仅是主机地址了,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocol port,简称端口)的概念,用于标识通信的进程。

端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(即进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似于一般的I/O操作,进程获取一个端口,相当于获取本地唯一的I/O文件,可以用一般的读写原语访问之。

类似于文件描述符,每个端口都拥有一个叫端口号(port number)的整数型标识符,用于区别不同端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。

端口号的分配是一个重要问题。有两种基本分配方式:第一种叫全局分配,这是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众。第二种是本地分配,又称动态连接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地唯一的端口号,进程再通过合适的系统调用将自己与该端口号联系起来(绑扎)。TCP/IP端口号的分配中综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程。因此,每一个标准服务器都拥有一个全局公认的端口(即周知口,well-known port),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作保留端口。


地址

网络通信中通信的两个进程分别在不同的机器上。在互连网络中,两台机器可能位于不同的网络,这些网络通过网络互连设备(网关,网桥,路由器等)连接。因此需要三级寻址:

1. 某一主机可与多个网络相连,必须指定一特定网络地址;

2. 网络上每一台主机应有其唯一的地址;

3. 每一主机上的每一进程应有在该主机上的唯一标识符。

通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。


网络字节顺序

不同的计算机存放多字节值的顺序不同,有的机器在起始地址存放低位字节(低价先存),有的存高位字节(高价先存)。为保证数据的正确性,在网络协议中须指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存格式,它们均含在协议头文件中。


连接

两个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。


半相关

综上所述,网络中用一个三元组可以在全局唯一标志一个进程:

(协议,本地地址,本地端口号)

这样一个三元组,叫做一个半相关(half-association),它指定连接的每半部分。


全相关

一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:

(协议,本地地址,本地端口号,远地地址,远地端口号)

这样一个五元组,叫做一个相关(association),即两个协议相同的半相关才能组合成一个合适的相关,或完全指定组成一连接。

2.2.2 服务方式 www.it55.com在线教程
在网络分层结构中,各层之间是严格单向依赖的,各层次的分工和协作集中体现在相邻层之间的界面上。“服务”是描述相邻层之间关系的抽象概念,即网络中各层向紧邻上层提供的一组操作。下层是服务提供者,上层是请求服务的用户。服务的表现形式是原语(primitive),如系统调用或库函数。系统调用是操作系统内核向网络应用程序或高层协议提供的服务原语。网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值。

在OSI的术语中,网络层及其以下各层又称为通信子网,只提供点到点通信,没有程序或进程的概念。而传输层实现的是“端到端”通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序(报文排序),连接管理等问题,为此提供不同的服务方式:


面向连接(虚电路)或无连接

面向连接服务是电话系统服务模式的抽象,即每一次完整的数据传输都要经过建立连接,使用连接,终止连接的过程。在数据传输过程中,各数据分组不携带目的地址,而使用连接号(connect ID)。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。

无连接服务是邮政系统服务的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复与重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。

下面给出这两种服务的类型及应用中的例子:


服务类型 www.it55.com在线教程
服 务
例 子

面向连接
可靠的报文流

可靠的字节流

不可靠的连接
文件传输(FTP)

远程登录(Telnet)

数字话音

无连接
不可靠的数据报

有确认的数据报

请求-应答
电子邮件(E-mail)

电子邮件中的挂号信

网络数据库查询



顺序

在网络传输中,两个连续报文在端-端通信中可能经过不同路径,这样到达目的地时的顺序可能会与发送时不同。“顺序”是指接收数据顺序与发送数据顺序相同。TCP协议提供这项服务。


差错控制

保证应用程序接收的数据无差错的一种机制。检查差错的方法一般是采用检验“检查和(Checksum)”的方法。而保证传送无差错的方法是双方采用确认应答技术。TCP协议提供这项服务。


流控制

在数据传输过程中控制数据传输速率的一种机制,以保证数据不被丢失。TCP协议提供这项服务。


字节流

字节流方式指的是仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界。TCP协议提供字节流服务。


报文

接收方要保存发送方的报文边界。UDP协议提供报文服务。


全双工/半双工

端-端间数据同时以两个方向/一个方向传送。


缓存/带外数据

在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读或写任意数量的字节。为保证传输正确或采用有流控制的协议时,都要进行缓存。但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存。

在数据传送过程中,希望不通过常规传输方式传送给用户以便及时处理的某一类信息,如UNIX系统的中断键(Delete或Control-c)、终端流控制符(Control-s和Control-q),称为带外数据。逻辑上看,好象用户进程使用了一个独立的通道传输这些数据。该通道与每对连接的流相联系。由于Berkeley Software Distribution中对带外数据的实现与RFC 1122中规定的Host Agreement不一致,为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据外,最好不使用它。

2.2.3 客户/服务器模式

IT资讯之家 www.it55.com


在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(ClIEnt/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务器模式的TCP/IP。
客户/服务器模式在操作过程中采取的是主动请求方式:

首先服务器方要先启动,并根据请求提供相应服务:

1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求;

2. 等待客户请求到达该端口;

3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。

4. 返回第二步,等待另一客户请求。

5. 关闭服务器


客户方:

1. 打开一通信通道,并连接到服务器所在主机的特定端口;

2. 向服务器发服务请求报文,等待并接收应答;继续提出请求......

3. 请求结束后关闭通信通道并终止。


从上面所描述过程可知:

1. 客户与服务器进程的作用是非对称的,因此编码不同。

2. 服务进程一般是先于客户请求而启动的。只要系统运行,该服务进程一直存在,直到正常或强迫终止。

2.2.4 套接字类型 it55.com
TCP/IP的socket提供下列三种类型套接字。


流式套接字(SOCK_STREAM)

提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。


数据报式套接字(SOCK_DGRAM)

提供了一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。


原始式套接字(SOCK_RAW)

该接口允许对较低层协议,如IP、ICMP直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。

2.3 基本套接字系统调用
为了更好地说明套接字编程原理,下面给出几个基本套接字系统调用说明。

2.3.1 创建套接字──socket()
应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:

SOCKET PASCAL FAR socket(int af, int type, int protocol);

该调用要接收三个参数:af、type、protocol。参数af指定通信发生的区域,UNIX系统支持的地址族有:AF_UNIX、AF_INET、AF_NS等,而DOS、WINDOWS中仅支持AF_INET,它是网际网区域。因此,地址族与协议族相同。参数type 描述要建立的套接字的类型。参数protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此,socket()系统调用实际上指定了相关五元组中的“协议”这一元。

有关socket()的详细描述参看5.2.23。

2.3.2 指定本地地址──bind()

http://www.it55.com/


当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);

参数s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数name 是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。namelen表明了name的长度。

如果没有错误发生,bind()返回0。否则返回值SOCKET_ERROR。

地址在建立套接字通信过程中起着重要作用,作为一个网络应用程序设计者对套接字地址结构必须有明确认识。例如,UNIX BSD有一组描述套接字地址的数据结构,其中使用TCP/IP协议的地址结构为:

struct sockaddr_in{

short sin_family; /*AF_INET*/

u_short sin_port; /*16位端口号,网络字节顺序*/

struct in_addr sin_addr; /*32位IP地址,网络字节顺序*/

char sin_zero[8]; /*保留*/

}

有关bind()的详细描述参看5.2.2。

2.3.3 建立套接字连接──connect()与accept()

www.it55.com在线教程


这两个系统调用用于完成一个完整相关的建立,其中connect()用于建立连接。无连接的套接字进程也可以调用connect(),但这时在进程之间没有实际的报文交换,调用将从本地操作系统直接返回。这样做的优点是程序员不必为每一数据指定目的地址,而且如果收到的一个数据报,其目的端口未与任何套接字建立“连接”,便能判断该端口不可操作。而accept()用于使服务器等待来自某客户进程的实际连接。
connect()的调用格式如下:

int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR * name, int namelen);

参数s是欲建立连接的本地套接字描述符。参数name指出说明对方套接字地址结构的指针。对方套接字地址长度由namelen说明。

如果没有错误发生,connect()返回0。否则返回值SOCKET_ERROR。在面向连接的协议中,该调用导致本地系统和外部系统之间连接实际建立。

由于地址族总被包含在套接字地址结构的前两个字节中,并通过socket()调用与某个协议族相关。因此bind()和connect()无须协议作为参数。

有关connect()的详细描述参看5.2.4。

accept()的调用格式如下:

SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);

参数s为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。addr 指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。addrlen 为客户方套接字地址的长度(字节数)。如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。

accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr 指向一个初始值为空的地址结构,而addrlen 的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr 和addrlen,并创建一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。

有关accept()的详细描述参看5.2.1。

四个套接字系统调用,socket()、bind()、connect()、accept(),可以完成一个完全五元相关的建立。socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关。bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:在服务器方,无论是否面向连接,均要调用bind();在客户方,若采用面向连接,则可以不调用bind(),而通过connect()自动完成。若采用无连接,客户方必须使用bind()以获得一个唯一的地址。

以上讨论仅对客户/服务器模式而言,实际上套接字的使用是非常灵活的,唯一需遵循的原则是进程通信之前,必须建立完整的相关。

2.3.4 监听连接──listen()

IT资讯之家 www.it55.com


此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);

参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。

listen()在执行调用过程中可为没有调用过bind()的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列。

调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给s赋于一个名字之后调用,而且一定要在accept()之前调用。

有关listen()的详细描述参看5.2.13。

2.2.3节中提到在客户/服务器模式中,有两种类型的服务:重复服务和并发服务。accept()调用为实现并发服务提供了极大方便,因为它要返回一个新的套接字号,其典型结构为:

int initsockid, newsockid;

if ((initsockid = socket(....)) < 0)

error(“can't create socket”);

if (bind(initsockid,....) < 0)

error(“bind error”);

if (listen(initsockid , 5) < 0)

error(“listen error”);

for (;;) {

newsockid = accept(initsockid, ...) /* 阻塞 */

if (newsockid < 0)

error(“accept error“);

if (fork() == 0){ /* 子进程 */

closesocket(initsockid);

do(newsockid); /* 处理请求 */

exit(0);

}

closesocket(newsockid); /* 父进程 */

}

这段程序执行的结果是newsockid与客户的套接字建立相关,子进程启动后,关闭继承下来的主服务器的initsockid,并利用新的newsockid与客户通信。主服务器的initsockid可继续等待新的客户连接请求。由于在Unix等抢先多任务系统中,在系统调度下,多个进程可以同时进行。因此,使用并发服务器可以使服务器进程在同一时间可以有多个子进程和不同的客户程序连接、通信。在客户程序看来,服务器可以同时并发地处理多个客户的请求,这就是并发服务器名称的来由。

面向连接服务器也可以是重复服务器,其结构如下:

int initsockid, newsockid;

if ((initsockid = socket(....))<0)

error(“can't create socket”);

if (bind(initsockid,....)<0)

error(“bind error”);

if (listen(initsockid,5)<0)

error(“listen error”);

for (;;) {

newsockid = accept(initsockid, ...) /* 阻塞 */

if (newsockid < 0)

error(“accept error“);

do(newsockid); /* 处理请求 */

closesocket(newsockid);

}

重复服务器在一个时间只能和一个客户程序建立连接,它对多个客户程序的处理是采用循环的方式重复进行,因此叫重复服务器。并发服务器和重复服务器各有利弊:并发服务器可以改善客户程序的响应速度,但它增加了系统调度的开销;重复服务器正好与其相反,因此用户在决定是使用并发服务器还是重复服务器时,要根据应用的实际情况来定。

2.3.5 数据传输──send()与recv() http://www.it55.com/
当一个连接建立以后,就可以传输数据了。常用的系统调用有send()和recv()。

send()调用用于在参数s指定的已连接的数据报或流套接字上发送输出数据,格式如下:

int PASCAL FAR send(SOCKET s, const char FAR *buf, int len, int flags);

参数s为已连接的本地套接字描述符。buf 指向存有发送数据的缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否发送带外数据等。如果没有错误发生,send()返回总共发送的字节数。否则它返回SOCKET_ERROR。

有关send()的详细描述参看5.2.19。

recv()调用用于在参数s指定的已连接的数据报或流套接字上接收输入数据,格式如下:

int PASCAL FAR recv(SOCKET s, char FAR *buf, int len, int flags);

参数s 为已连接的套接字描述符。buf指向接收输入数据缓冲区的指针,其长度由len 指定。flags 指定传输控制方式,如是否接收带外数据等。如果没有错误发生,recv()返回总共接收的字节数。如果连接被关闭,返回0。否则它返回SOCKET_ERROR。

有关recv()的详细描述参看5.2.16。

2.3.6 输入/输出多路复用──select() 免费资源www.it55.com
select()调用用来检测一个或多个套接字的状态。对每一个套接字来说,这个调用可以请求读、写或错误状态方面的信息。请求给定状态的套接字集合由一个fd_set结构指示。在返回时,此结构被更新,以反映那些满足特定条件的套接字的子集,同时, select()调用返回满足条件的套接字的数目,其调用格式如下:

int PASCAL FAR select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);

参数nfds指明被检查的套接字描述符的值域,此变量一般被忽略。

参数readfds指向要做读检测的套接字描述符集合的指针,调用者希望从中读取数据。参数writefds 指向要做写检测的套接字描述符集合的指针。exceptfds指向要检测是否出错的套接字描述符集合的指针。timeout指向select()函数等待的最大时间,如果设为NULL则为阻塞操作。select()返回包含在fd_set结构中已准备好的套接字描述符的总数目,或者是发生错误则返回SOCKET_ERROR。

有关select()的详细描述参看5.2.18。

2.3.7 关闭套接字──closesocket()
closesocket()关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。closesocket()的调用格式如下:

BOOL PASCAL FAR closesocket(SOCKET s);

参数s待关闭的套接字描述符。如果没有错误发生,closesocket()返回0。否则返回值SOCKET_ERROR。

有关closesocket()的详细描述参看5.2.3。 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn

2.4 典型套接字调用过程举例
如前所述,TCP/IP协议的应用一般采用客户/服务器模式,因此在实际应用中,必须有客户和服务器两个进程,并且首先启动服务器,其系统调用时序图如下。

it55.com


面向连接的协议(如TCP)的套接字系统调用如图2.1所示:

服务器必须首先启动,直到它执行完accept()调用,进入等待状态后,方能接收客户请求。假如客户在此前启动,则connect()将返回出错代码,连接不成功。


图2.1 面向连接的套接字系统调用时序图


无连接协议的套接字调用如图2.2所示:


图2.2 无连接协议的套接字调用时序图

无连接服务器也必须先启动,否则客户请求传不到服务进程。无连接客户不调用connect()。因此在数据发送之前,客户与服务器之间尚未建立完全相关,但各自通过socket()和bind()建立了半相关。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态地建立了全相关。


实例

本实例使用面向连接协议的客户/服务器模式,其流程如图2.3所示:


图2.3 面向连接的应用程序流程图

服务器方程序:


/* File Name: streams.c */

#include

#include

#define TRUE 1

/* 这个程序建立一个套接字,然后开始无限循环;每当它通过循环接收到一个连接,则打印出一个信息。当连接断开,或接收到终止信息,则此连接结束,程序再接收一个新的连接。命令行的格式是:streams */


main( )

{

int sock, length;

struct sockaddr_in server;

struct sockaddr tcpaddr;

int msgsock;

char buf[1024];

int rval, len;


/* 建立套接字 */

sock = socket(AF_INET, SOCK_STREAM, 0);

if (sock < 0) {

perror(“opening stream socket”);

exit(1);

}


/* 使用任意端口命名套接字 */

server.sin_family = AF_INET;

server.sin_port = INADDR_ANY;

if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {

perror(“binding stream socket”);

exit(1);

}


/* 找出指定的端口号并打印出来 */

length = sizeof(server);

if (getsockname(sock, (struct sockaddr *)&server, &length) < 0) {

perror(“getting socket name”);

exit(1);

}

printf(“socket port #%d/n”, ntohs(server.sin_port));


/* 开始接收连接 */

listen(sock, 5);

len = sizeof(struct sockaddr);

do {

msgsock = accept(sock, (struct sockaddr *)&tcpaddr, (int *)&len);

if (msgsock == -1)

perror(“accept”);

else do{

memset(buf, 0, sizeof(buf));

if ((rval = recv(msgsock, buf, 1024)) < 0)

perror(“reading stream message”);

if (rval == 0)

printf(“ending connection /n”);

else

printf(“-->%s/n”, buf);

}while (rval != 0);

closesocket(msgsock);

} while (TRUE);


/* 因为这个程序已经有了一个无限循环,所以套接字“sock”从来不显式关闭。然而,当进程被杀死或正常终止时,所有套接字都将自动地被关闭。*/

exit(0);

}


客户方程序:

/* File Name: streamc.c */

#include

#include

#define DATA “half a league, half a league ...”

/* 这个程序建立套接字,然后与命令行给出的套接字连接;连接结束时,在连接上发送

一个消息,然后关闭套接字。命令行的格式是:streamc 主机名 端口号

端口号要与服务器程序的端口号相同 */

main(argc, argv)

int argc;

char *argv[ ];

{

int sock;

struct sockaddr_in server;

struct hostent *hp, *gethostbyname( );

char buf[1024];


/* 建立套接字 */

sock = socket(AF_INET, SOCK_STREAM, 0);

if (sock < 0) {

perror(“opening stream socket”);

exit(1);

}


/* 使用命令行中指定的名字连接套接字 */

server.sin_family = AF_INET;

hp = gethostbyname(argv[1]);

if (hp == 0) {

fprintf(stderr, “%s: unknown host /n”, argv[1]);

exit(2);

}

memcpy((char*)&server.sin_addr, (char*)hp->h_addr, hp->h_length);

sever.sin_port = htons(atoi(argv[2]));


if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0) {

perror(“connecting stream socket”);

exit(3);

}


if (send(sock, DATA, sizeof(DATA)) < 0)

perror(“sending on stream socket”);

closesocket(sock);

exit(0);

} sflj www.it55.com kg^&fgd

2.5 一个通用的实例程序
在上一节中,我们介绍了一个简单的socket程序实例。从这个例子我们可以看出,使用socket编程几乎有一个模式,即所有的程序几乎毫无例外地按相同的顺序调用相同的函数。因此我们可以设想,设计一个中间层,它向上提供几个简单的函数,程序只要调用这几个函数就可以实现普通情况下的数据传输,程序设计者不必太多地关心socket程序设计的细节。
本节我们将介绍一个通用的网络程序接口,它向上层提供几个简单的函数,程序设计者只要使用这几个函数就可以完成绝大多数情况下的网络数据传输。这些函数将socket编程和上层隔离开来,它使用面向连接的流式套接字,采用非阻塞的工作机制,程序只要调用这些函数查询网络消息并作出相应的响应即可。这些函数包括:

l InitSocketsStruct:初始化socket结构,获取服务端口号。客户程序使用。

l InitPassiveSock:初始化socket结构,获取服务端口号,建立主套接字。服务器程序使用。

l CloseMainSock:关闭主套接字。服务器程序使用。

l CreateConnection:建立连接。客户程序使用。

l AcceptConnection:接收连接。服务器程序使用。

l CloseConnection:关闭连接。

l QuerySocketsMsg:查询套接字消息。

l SendPacket:发送数据。

l RecvPacket:接收数据。

2.5.1 头文件 www.it55.com在线教程
/* File Name: tcpsock.h */

/* 头文件包括socket程序经常用到的系统头文件(本例中给出的是SCO Unix下的头文件,其它版本的Unix的头文件可能略有不同),并定义了我们自己的两个数据结构及其实例变量,以及我们提供的函数说明。*/

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


typedef struct SocketsMsg{ /* 套接字消息结构 */

int AcceptNum; /* 指示是否有外来连接等待接收 */

int ReadNum; /* 有外来数据等待读取的连接数 */

int ReadQueue[32]; /* 有外来数据等待读取的连接队列 */

int WriteNum; /* 可以发送数据的连接数 */

int WriteQueue[32]; /* 可以发送数据的连接队列 */

int ExceptNum; /* 有例外的连接数 */

int ExceptQueue[32]; /* 有例外的连接队列 */

} SocketsMsg;


typedef struct Sockets { /* 套接字结构 */

int DaemonSock; /* 主套接字 */

int SockNum; /* 数据套接字数目 */

int Sockets[64]; /* 数据套接字数组 */

fd_set readfds, writefds, exceptfds; /* 要被检测的可读、可写、例外的套接字集合 */

int Port; /* 端口号 */

} Sockets;


Sockets Mysock; /* 全局变量 */

SocketsMsg SockMsg;


int InitSocketsStruct(char * servicename) ;

int InitPassiveSock(char * servicename) ;

void CloseMainSock();

int CreateConnection(struct in_addr *sin_addr);

int AcceptConnection(struct in_addr *IPaddr);

int CloseConnection(int Sockno);

int QuerySocketsMsg();

int SendPacket(int Sockno, void *buf, int len);

int RecvPacket(int Sockno, void *buf, int size);

2.5.2 函数源文件

it55.com


/* File Name: tcpsock.c */
/* 本文件给出九个函数的源代码,其中部分地方给出中文注释 */

#include "tcpsock.h"


int InitSocketsStruct(char * servicename)

/* Initialize Sockets structure. If succeed then return 1, else return error code (<0) */

/* 此函数用于只需要主动套接字的客户程序,它用来获取服务信息。服务的定义

在/etc/services文件中 */

{

struct servent *servrec;

struct sockaddr_in serv_addr;


if ((servrec = getservbyname(servicename, "tcp")) == NULL) {

return(-1);

}

bzero((char *)&Mysock, sizeof(Sockets));

Mysock.Port = servrec->s_port; /* Service Port in Network Byte Order */

return(1);

}


int InitPassiveSock(char * servicename)

/* Initialize Passive Socket. If succeed then return 1, else return error code (<0) */

/* 此函数用于需要被动套接字的服务器程序,它除了获取服务信息外,还建立

一个被动套接字。*/

{

int mainsock, flag=1;

struct servent *servrec;

struct sockaddr_in serv_addr;


if ((servrec = getservbyname(servicename, "tcp")) == NULL) {

return(-1);

}

bzero((char *)&Mysock, sizeof(Sockets));

Mysock.Port = servrec->s_port; /* Service Port in Network Byte Order */


if((mainsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {

return(-2);

}


bzero((char *)&serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 任意网络接口 */

serv_addr.sin_port = servrec->s_port;

if (bind(mainsock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {

close(mainsock);

return(-3);

}


if (listen(mainsock, 5) == -1) { /* 将主动套接字变为被动套接字,准备好接收连接 */

close(mainsock);

return(-4);

}


/* Set this socket as a Non-blocking socket. */

if (ioctl(mainsock, FIONBIO, &flag) == -1) {

close(mainsock);

return(-5);

}


Mysock.DaemonSock = mainsock;

FD_SET(mainsock, &Mysock.readfds); /* 申明对主套接字“可读”感兴趣 */

FD_SET(mainsock, &Mysock.exceptfds); /* 申明对主套接字上例外事件感兴趣 */

return(1);

}


void CloseMainSock()

/* 关闭主套接字,并清除对它上面事件的申明。在程序结束前关闭主套接字是一个好习惯 */

{

close(Mysock.DaemonSock);

FD_CLR(Mysock.DaemonSock, &Mysock.readfds);

FD_CLR(Mysock.DaemonSock, &Mysock.exceptfds);

}


int CreateConnection(struct in_addr *sin_addr)

/* Create a Connection to remote host which IP address is in sin_addr.

Param: sin_addr indicates the IP address in Network Byte Order.

if succeed return the socket number which indicates this connection,

else return error code (<0) */

{

struct sockaddr_in server; /* server address */

int tmpsock, flag=1, i;


if ((tmpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0)

return(-1);


server.sin_family = AF_INET;

server.sin_port = Mysock.Port;

server.sin_addr.s_addr = sin_addr->s_addr;


/* Set this socket as a Non-blocking socket. */

if (ioctl(tmpsock, FIONBIO, &flag) == -1) {

close(tmpsock);

return(-2);

}


/* Connect to the server. */

if (connect(tmpsock, (struct sockaddr *)&server, sizeof(server)) < 0) {

if ((errno != EWOULDBLOCK) && (errno != EINPROGRESS)) {

/* 如果错误代码是EWOULDBLOCK和EINPROGRESS,则不用关闭套接字,因为系统将在之后继续为套接字建立连接,连接是否建立成功可用select()函数来检测套接字是否“可写”来确定。*/

close(tmpsock);

return(-3); /* Connect error. */

}

}


FD_SET(tmpsock, &Mysock.readfds);

FD_SET(tmpsock, &Mysock.writefds);

FD_SET(tmpsock, &Mysock.exceptfds);


i = 0;

while (Mysock.Sockets[i] != 0) i++; /* look for a blank sockets position */

if (i >= 64) {

close(tmpsock);

return(-4); /* too many connections */

}


Mysock.Sockets[i] = tmpsock;

Mysock.SockNum++;

return(i);

}


int AcceptConnection(struct in_addr *IPaddr)

/* Accept a connection. If succeed, return the data sockets number, else return -1. */

{

int newsock, len, flag=1, i;

struct sockaddr_in addr;


len = sizeof(addr);

bzero((char *)&addr, len);

if ((newsock = accept(Mysock.DaemonSock, &addr, &len)) == -1)

return(-1); /* Accept error. */


/* Set this socket as a Non-blocking socket. */

ioctl(newsock, FIONBIO, &flag);


FD_SET(newsock, &Mysock.readfds);

FD_SET(newsock, &Mysock.writefds);

FD_SET(newsock, &Mysock.exceptfds);


/* Return IP address in the Parameter. */

IPaddr->s_addr = addr.sin_addr.s_addr;


i = 0;

while (Mysock.Sockets[i] != 0) i++; /* look for a blank sockets position */

if (i >= 64) {

close(newsock);

return(-4); /* too many connections */

}


Mysock.Sockets[i] = newsock;

Mysock.SockNum++;

return(i);

}


int CloseConnection(int Sockno)

/* Close a connection indicated by Sockno. */

{

int retcode;


if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))

return(0);


retcode = close(Mysock.Sockets[Sockno]);

FD_CLR(Mysock.Sockets[Sockno], &Mysock.readfds);

FD_CLR(Mysock.Sockets[Sockno], &Mysock.writefds);

FD_CLR(Mysock.Sockets[Sockno], &Mysock.exceptfds);


Mysock.Sockets[Sockno] = 0;

Mysock.SockNum--;

return(retcode);

}


int QuerySocketsMsg()

/* Query Sockets Message. If succeed return message number, else return -1.

The message information stored in struct SockMsg. */

{

fd_set rfds, wfds, efds;

int retcode, i;

struct timeval TimeOut;


rfds = Mysock.readfds;

wfds = Mysock.writefds;

efds = Mysock.exceptfds;

TimeOut.tv_sec = 0; /* 立即返回,不阻塞。*/

TimeOut.tv_usec = 0;


bzero((char *)&SockMsg, sizeof(SockMsg));

if ((retcode = select(64, &rfds, &wfds, &efds, &TimeOut)) == 0)

return(0);


if (FD_ISSET(Mysock.DaemonSock, &rfds))

SockMsg.AcceptNum = 1; /* some client call server. */


for (i=0; i<64; i++) /* Data in message */

{

if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &rfds)))

SockMsg.ReadQueue[SockMsg.ReadNum++] = i;

}


for (i=0; i<64; i++) /* Data out ready message */

{

if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &wfds)))

SockMsg.WriteQueue[SockMsg.WriteNum++] = i;

}


if (FD_ISSET(Mysock.DaemonSock, &efds))

SockMsg.AcceptNum = -1; /* server socket error. */


for (i=0; i<64; i++) /* Error message */

{

if ((Mysock.Sockets[i] > 0) && (FD_ISSET(Mysock.Sockets[i], &efds)))

SockMsg.ExceptQueue[SockMsg.ExceptNum++] = i;

}

return(retcode);

}


int SendPacket(int Sockno, void *buf, int len)

/* Send a packet. If succeed return the number of send data, else return -1 */

{

int actlen;


if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))

return(0);


if ((actlen = send(Mysock.Sockets[Sockno], buf, len, 0)) < 0)

return(-1);

return(actlen);

}


int RecvPacket(int Sockno, void *buf, int size)

/* Receive a packet. If succeed return the number of receive data, else if the connection

is shutdown by peer then return 0, otherwise return 0-errno */

{

int actlen;

if ((Sockno >= 64) || (Sockno < 0) || (Mysock.Sockets[Sockno] == 0))

return(0);

if ((actlen = recv(Mysock.Sockets[Sockno], buf, size, 0)) < 0)

return(0-errno);

return(actlen); /* actlen是接收的数据长度,如果为零,指示连接被对方关闭。*/

}

2.5.3 简单服务器程序示例 www.it55.com
/* File Name: server.c */

/* 这是一个很简单的重复服务器程序,它初始化好被动套接字后,循环等待接收连接。如果接收到连接,它显示数据套接字序号和客户端的IP地址;如果数据套接字上有数据到来,它接收数据并显示该连接的数据套接字序号和接收到的字符串。*/

#include "tcpsock.h"

main(argc, argv)

int argc;

char **argv;

{

struct in_addr sin_addr;

int retcode, i;

char buf[32];


/* 对于服务器程序,它经常是处于无限循环状态,只有在用户主动kill该进程或系统关机时,它才结束。对于使用kill强行终止的服务器程序,由于主套接字没有关闭,资源没有主动释放,可能会给随后的服务器程序重新启动产生影响。因此,主动关闭主套接字是一个良好的变成习惯。下面的语句使程序在接收到SIGINT、SIGQUIT和SIGTERM等信号时先执行CloseMainSock()函数关闭主套接字,然后再结束程序。因此,在使用kill强行终止服务器进程时,应该先使用kill -2 PID给服务器程序一个消息使其关闭主套接字,然后在用kill -9 PID强行结束该进程。*/

(void) signal(SIGINT, CloseMainSock);

(void) signal(SIGQUIT, CloseMainSock);

(void) signal(SIGTERM, CloseMainSock);


if ((retcode = InitPassiveSock("TestService")) < 0) {

printf("InitPassiveSock: error code = %d/n", retcode);

exit(-1);

}


while (1) {

retcode = QuerySocketsMsg(); /* 查询网络消息 */

if (SockMsg.AcceptNum == 1) { /* 有外来连接等待接收?*/

retcode = AcceptConnection(&sin_addr);

printf("retcode = %d, IP = %s /n", retcode, inet_ntoa(sin_addr.s_addr));

}

else if (SockMsg.AcceptNum == -1) /* 主套接字错误?*/

printf("Daemon Sockets error./n");

for (i=0; i
if ((retcode = RecvPacket(SockMsg.ReadQueue[i], buf, 32)) > 0)

printf("sockno %d Recv string = %s /n", SockMsg.ReadQueue[i], buf);

else /* 返回数据长度为零,指示连接中断,关闭套接字。*/

CloseConnection(SockMsg.ReadQueue[i]);

}

} /* end while */

}

2.5.4 简单客户程序示例 45398 www.it55.com it55学习IT知识,享受IT生活 4dfkjn
/* File Name: client.c */

/* 客户程序在执行时,先初始化数据结构,然后等待用户输入命令。它识别四个命令:

conn(ect): 和服务器建立连接;

send: 给指定连接发送数据;

clos(e): 关闭指定连接;

quit: 退出客户程序。

*/

#include "tcpsock.h"


main(argc, argv)

int argc;

char **argv;

{

char cmd_buf[16];

struct in_addr sin_addr;

int sockno1, retcode;

char *buf = "This is a string for test.";


sin_addr.s_addr = inet_addr("166.111.5.249"); /* 运行服务器程序的主机的IP地址 */


if ((retcode = InitSocketsStruct("TestService")) < 0) { /* 初始化数据结构 */

printf("InitSocketsStruct: error code = %d/n", retcode);

exit(1);

}


while (1) {

printf(">");

gets(cmd_buf);

if (!strncmp(cmd_buf, "conn", 4)) {

retcode = CreateConnection(&sin_addr); /* 建立连接 */

printf("return code: %d/n", retcode);

}

else if(!strncmp(cmd_buf, "send", 4)) {

printf("Sockets Number:");

scanf("%d", &sockno1);

retcode = SendPacket(sockno1, buf, 26); /* 发送数据 */

printf("return code: %d/n", retcode, sizeof(buf));

}

else if (!strncmp(cmd_buf, "close", 4)) {

printf("Sockets Number:");

scanf("%d", &sockno1);

retcode = CloseConnection(sockno1); /* 关闭连接 */

printf("return code: %d/n", retcode);

}

else if (!strncmp(cmd_buf, "quit", 4))

exit(0);

else

putchar('/007');

} /* end while */

}
  其实int中的4代表4个字节,1个字节是8个二进制串,于是实际上int型可以表示的最大整数就是0111 1111 1111 1111 1111 1111 1111 1111共32位,最前面的一位是符号位,计算机中通常用0代表整数,1代表负数,于是这个数转换成10进制就是2^0 + 2^1 + 2^2 + .........2^31 = 2^32 - 1 = 2147483647于是,我又想当然的认为最小的负整数就一定是1111 1111 1111 1111 1111 1111 1111 1111我们知道,负数在计算机中是以补码的形式存储的,而负数的补码= 其绝对值的原码取反 + 1,我们可以反向来推算嘛先减1.
取浮点数的后2位的简单示例代码。
[codes=C]
int temperatur;  // "return" of temperature in degrees Celsius * 100
int temperatur2;
float temp; // temperature in degrees Celsius
float temp2; // temperature in degrees Celsius
temp=temperatur/100.0;
temp2=temperatur2/100.0;
sprintf( tempStr, "%.2f", temp );
sprintf( temp2Str, "%.2f", temp2 );
[/codes]

sprintf.c
[codes=C]
#include <stdio.h>
int main(){

float fstr=21.0056;
char pszstr[16]={0};
sprintf( pszstr, "%.2f", fstr );                                                
printf("pszstr=%s\n",pszstr);

}
[/codes]

在将各种类型的数据构造成字符串时,sprintf 的强大功能很少会让你失望。由于sprintf 跟printf 在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。这也导致sprintf 比printf 有用得多。

sprintf 是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... );
除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:
格式化字符串上。


printf 和sprintf 都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要 的字符串。


格式化数字字符串
sprintf 最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf 在大多数场合可以替代
itoa。

如:
//把整数123 打印成一个字符串保存在s 中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"
当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"
也可以按照16 进制打印:
sprintf(s, "%8x", 4567); //小写16 进制,宽度占8 个位置,右对齐
sprintf(s, "%-8X", 4568); //大写16 进制,宽度占8 个位置,左对齐

这样,一个整数的16 进制字符串就很容易得到,但我们在打印16 进制内容时,通常想要一种左边补0 的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0 就可以了。
sprintf(s, "%08X", 4567); //产生:"000011D7"
上面以”%d”进行的10 进制打印同样也可以使用这种左边补0 的方式。


这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1 的内存16 进制表示形式,在Win32 平台上,一个short 型占2 个字节,所以我们自然希望用4 个16 进制数字来打印它:
short si = -1;
sprintf(s, "%04X", si);
产生“FFFFFFFF”,怎么回事?因为spritnf 是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈时被压进来的到底 是个4 字节的整数还是个2 字节的短整数,所以采取了统一4 字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32 位的整数-1,打印时4 个位置不够了,就把32 位整数-1 的8 位16 进制都打印出来了。

如果你想看si 的本来面目,那么就应该让编译器做0 扩展而不是符号扩展(扩展时二进制左边补0 而不是补符号位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, "%04X", si);


sprintf 和printf 还可以按8 进制打印整数字符串,使用”%o”。注意8 进制和16 进制都不会打
印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16 进制或8 进制表示。


控制浮点数打印格式
浮点数的打印和格式控制是sprintf 的又一大常用功能,浮点数使用格式符”%f”控制,默认保
留小数点后6 位数字,比如:
sprintf(s, "%f", 3.1415926); //产生"3.141593"
但有时我们希望自己控制打印的宽度和小数位数,这时就应该使用:”%m.nf”格式,其中m 表
示打印的宽度,n 表示小数点后的位数。比如:
sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142"
sprintf(s, "%-10.3f", 3.1415626); //产生:"3.142 "
sprintf(s, "%.3f", 3.1415626); //不指定总宽度,产生:"3.142"


注意一个问题,你猜
int i = 100;
sprintf(s, "%.2f", i);
会打出什么东东来?“100.00”?对吗?自己试试就知道了,同时也试试下面这个:
sprintf(s, "%.2f", (double)i);
第一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个”%f”。而函数执行时函数本身则并不知道 当年被压入栈里的是个整数,于是可怜的保存整数i 的那4 个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编 排的结果是否正确。


字符/Ascii 码对照
我们知道,在C/C++语言中,char 也是一种普通的scalable 类型,除了字长之外,它与short,
int,long 这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年该把
这个类型叫做“byte”,然后现在就可以根据实际情况,使用byte 或short 来把char 通过typedef 定义出来,这样更合适些)于是,使用”%d”或者”%x”打印一个字符,便能得出它的10 进制或16 进制的ASCII 码;反过来,使用”%c”打印一个整数,便可以看到它所对应的ASCII 字符。以下程序段把所有可见字符的ASCII 码对照表打印到屏幕上(这里采用printf,注意”#”与”%X”合用时自动为16 进制数增加”0X”前缀):
for(int i = 32; i < 127; i++) {
printf("[ %c ]: %3d 0x%#04X
", i, i, i);
}


连接字符串
sprintf 的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连
接字符串,从而在许多场合可以替代strcat,但sprintf 能够一次连接多个字符串(自然也可以同时
在它们中间插入别的内容,总之非常灵活)。比如:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); //产生:"I love CSDN. "
strcat 只能连接字符串(一段以’’结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以 ’’结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的’’来结尾。如果直接 连接,不管是sprintf 还是strcat 肯定会导致非法内存操作,而strncat 也至少要求第一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串 也一样的。比如:
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:
sprintf(s, "%s%s", a1, a2); //Don't do that!
十有八九要出问题了。是否可以改成:
sprintf(s, "%7s%7s", a1, a2);
也没好到哪儿去,正确的应该是:
sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN"
这可以类比打印浮点数的”%m.nf”,在”%m.ns”中,m 表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n 才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m 没什么大用,还是点号后面的n 用的多。自然,也可以前后都只取部分字符:
sprintf(s, "%.6s%.5s", a1, a2);//产生:"ABCDEFHIJKL"
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的,因为许多 时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在sprintf 的实现中也被考虑到了,sprintf 采用”*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子 可以变成:
sprintf(s, "%.*s%.*s", 7, a1, 7, a2);
或者:
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
sprintf(s, "%-*d", 4, 'A'); //产生"65 "
sprintf(s, "%#0*X", 8, 128); //产生"0X000080","#"产生0X
sprintf(s, "%*.*f", 10, 2, 3.1415926); //产生" 3.14"


打印地址信息
有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32 位的数,你完全可以使用打印无符号整数的”%u”把他们打印出来:
sprintf(s, "%u", &i);
不过通常人们还是喜欢使用16 进制而不是10 进制来显示一个地址:
sprintf(s, "%08X", &i);
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的”%p”:
sprintf(s, "%p", &i);
我觉得它实际上就相当于:
sprintf(s, "%0*x", 2 * sizeof(void *), &i);
利用sprintf 的返回值
较少有人注意printf/sprintf 函数的返回值,但有时它却是有用的,spritnf 返回了本次函数调用
最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf 调用结束以后,你无须再调用一次
strlen 便已经知道了结果字符串的长度。如:
int len = sprintf(s, "%d", i);
对于正整数来说,len 便等于整数i 的10 进制位数。
下面的是个完整的例子,产生10 个[0, 100)之间的随机数,并将他们打印到一个字符数组s 中,
以逗号分隔开。
#include
#include
#include
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, "%d,", rand() % 100);
}
s[offset - 1] = '
';//将最后一个逗号换成换行符。
printf(s);
return 0;
}
设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字
符串时,就可以使用这种方法,从理论上讲,他应该比不断的strcat 效率高,因为strcat 每次调用
都需要先找到最后的那个’’的位置,而在上面给出的例子中,我们每次都利用sprintf 返回值把这
个位置直接记下来了。


使用sprintf 的常见问题
sprintf 是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访
问错误,但好在由sprintf 误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通
常用眼睛再把出错的代码多看几眼就看出来了。


?? 缓冲区溢出
第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问
题,建议变参对应一定要细心,而打印字符串时,尽量使用”%.ns”的形式指定最大字符数。


?? 忘记了第一个参数
低级得不能再低级问题,用printf 用得太惯了。//偶就常犯。:。(


?? 变参对应出问题
通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤
其是对应”*”的那些参数,都提供了吗?不要把一个整数对应一个”%s”,编译器会觉得你
欺她太甚了(编译器是obj 和exe 的妈妈,应该是个女的,:P)。


strftime
sprnitf 还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也
是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为
了在出现问题时可以推卸责任吧。这里举个例子:
time_t t = time(0);
//产生"YYYY-MM-DD hh:mm:ss"格式的字符串。
char s[32];
strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t));
sprintf 在MFC 中也能找到他的知音:CString::Format,strftime 在MFC 中自然也有她的同道:
CTime::Format,这一对由于从面向对象哪里得到了赞助,用以写出的代码更觉优雅。

  

C语言标准库函数::strncmp()

函数原型
int strncmp(char *str1, char *str2, int maxlen);
函数功能 比较字符串s1和s2的前n个字符。 当s1<s2时,返回值<0 当s1=s2时,返回值=0 当s1>s2时,返回值>0声明文件 <string.h>

用法示例 #include <stdio.h> #include <string.h> int main(void)  {      char *buf1 = "aaabbb", *buf2 = "bbbccc", *buf3 = "ccc";      int ptr;      ptr = strncmp(buf2,buf1,3);     if (ptr > 0)        printf("buffer 2 is greater than buffer 1\n");      else        printf("buffer 2 is less than buffer 1\n");     ptr = strncmp(buf2,buf3,3);     if (ptr > 0)        printf("buffer 2 is greater than buffer 3\n");      else        printf("buffer 2 is less than buffer 3\n");      return(0);  }
grep  hp_mailchecksize   /usr/home/xiangdong2/test_entadmin_src/src/entplatform/   -r

以下是grep查找test文本中行首包含某个单词的情况:
grep -i -E '^ANT'  test


对某个目录的grep匹配命令:

grep -rni -E '^ANT' ./
去掉前非0开头的号码:
grep "^[1-9].*" a.txt

#include <stdio.h>
#include <iostream>

#include <string.h>
#include <stdlib.h>
#include <math.h>
//#include "/usr/local/mysql/include/mysql/mysql.h"
#include "/usr/local/mysql/include/mysql.h"
using namespace std;
int main(void)
{
    cout << "Content-type: text/html \n\n";
    char mysqlServer[20] = "172.25.38.70";
    char query[300];
    MYSQL myData;
    MYSQL_RES *res;
    MYSQL_FIELD *fd;
    MYSQL_ROW row;
    int rowCount = 0;
    int colCount = 0;
    int i, j;

    mysql_init( &myData );
    if(!mysql_real_connect(&myData, mysqlServer, "root", "","test3",3306,NULL,0))  
    {
        printf("connect mysql error!\n");
        return 0;
    }
    mysql_query(&myData,"set names utf8");
    sprintf(query,"select * from test3");
    if( mysql_query(&myData, query) != 0 )
    {
        printf("query error!\n");
        return 0;
    }
    else
    {
        res = mysql_store_result( &myData );
        rowCount = (int) mysql_num_rows( res );
        colCount = (int) mysql_num_fields( res );

    printf("mysql result: %d records found\n fields: %d \n", rowCount, colCount);
    for(i = 0; i < rowCount; i++)
    {
      row = mysql_fetch_row( res );
      for( j = 0; j < colCount; j++)
      {
          printf("[ %s ] ", row[j] );
            printf(" \t");
      }
  printf("______ %d\n",i);
  printf(" \n ");
}
}

}

正确的编译命令代码:
g++ c_mysql.cpp -L/usr/local/mysql/lib/  -lmysqlclient -lz -lm -o cout

注意:
如果/tmp/ccTGmMS21.o: In function `main':
/tmp/ccTGmMS21.o(.text+0x11): undefined reference to `mysql_init'
那么参数增加-L/usr/lib/mysql -lmysqlclient  
如果
usr/lib/mysql/libmysqlclient.a(my_compress.o): In function `my_uncompress':
my_compress.o(.text+0xaa): undefined reference to `uncompress'
那么增加-lz参数
规范点: g++ c_mysql.cpp -L/usr/local/mysql/lib/  -lmysqlclient -lz -lm
mail# cd /usr/ports/net/ntop
mail# make install clean
===>  Installing for ntop-3.3_1
===>   ntop-3.3_1 depends on executable: dot - not found
===>    Verifying install for dot in /usr/ports/graphics/graphviz
===>   graphviz-2.14.1_1 depends on executable: wish8.4 - found
===>   graphviz-2.14.1_1 depends on executable: gmake - found
===>   graphviz-2.14.1_1 depends on executable: bison - found
===>   graphviz-2.14.1_1 depends on file: /usr/local/bin/libtool - found
===>   graphviz-2.14.1_1 depends on file: /usr/local/libdata/xorg/libraries - not found
===>    Verifying install for /usr/local/libdata/xorg/libraries in /usr/ports/x11/xorg-libraries
/usr/X11R6 exists, but it is not a symlink. Installation cannot proceed.
This looks like an incompletely removed old version of X.  In the current version, /usr/X11R6 must be a symlink if it exists at all.Please read /usr/ports/UPDATING (entry of 20070519) for the procedure to upgrade X.org related ports.*** Error code 1

Stop in /usr/ports/x11/xorg-libraries.
*** Error code 1

Stop in /usr/ports/graphics/graphviz.
*** Error code 1

Stop in /usr/ports/net/ntop.
*** Error code 1

Stop in /usr/ports/net/ntop.
mail# rm /usr/X11R6/



*********
from the error messages:
usr/X11R6 exists, but it is not a symlink. Installation cannot proceed.
This looks like an incompletely removed old version of X.  In the current version, /usr/X11R6 must be a symlink if it exists at all.Please read /usr/ports/UPDATING (entry of 20070519) for the procedure to upgrade X.org related ports

SO WE SHOULD REMOVE THE sysmlink /usr/X11R6

>rm -r /usr/X11R6
#include <stdio.h>
#include <string>
#include <sstream>

using namespace std;

int main(void)
{
       string a = "312.29";
       string b;
       float f;
       stringstream mm;

       mm << a;
       mm >> f;
       printf("f=[%f]\n", f);

       mm.clear();
       mm << f;
       mm >> b;
       printf("b=[%s]\n", b.c_str());

       return 0;
}


#include <stdio.h>
#include <math.h>
#include <string>
using namespace std;
int main(void)
{
       float f;
       string a = "312.29";
       char c[100];
       string b;

       f = atof(a.c_str());
       printf("f=[%f]\n", f);

       if (f>2008)
               printf("more than 2008\n");
       else
               printf("less than 2008\n");

       sprintf(c, "%f", f);
       printf("c=[%s]\n", c);

       b = c;
       printf("b=[%s]\n", b.c_str());

       return 0;
}
atoi,atol,strtod,strtol,strtoul实现类型转换
所属类别:C/C++
推荐指数:★★★☆
文档人气:427
本周人气:24
发布日期:2007-6-6
atof(将字符串转换成浮点型数)
相关函数
    atoi,atol,strtod,strtol,strtoul
表头文件
    #include <stdlib.h>
定义函数
    double atof(const char *nptr);
函数说明
    atof()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结果返回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分,如123.456或123e-2。
返回值
    返回转换后的浮点型数。
附加说明
    atof()与使用strtod(nptr,(char**)NULL)结果相同。
范例
    /* 将字符串a 与字符串b转换成数字后相加*/
#include<stdlib.h>
main()
{
char *a=”-100.23”;
char *b=”200e-2”;
float c;
c=atof(a)+atof(b);
printf(“c=%.2f ”,c);
}
执行
    c=-98.23
 


   
atoi(将字符串转换成整型数)
相关函数
    atof,atol,atrtod,strtol,strtoul
表头文件
    #include<stdlib.h>
定义函数
    int atoi(const char *nptr);
函数说明
    atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结果返回。
返回值
    返回转换后的整型数。
附加说明
    atoi()与使用strtol(nptr,(char**)NULL,10);结果相同。
范例
    /* 将字符串a 与字符串b转换成数字后相加*/
#include<stdlib.h>
mian()
{
char a[]=”-100”;
char b[]=”456”;
int c;
c=atoi(a)+atoi(b);
printf(c=%d ”,c);
}
执行
    c=356
 


   
atol(将字符串转换成长整型数)
相关函数
    atof,atoi,strtod,strtol,strtoul
表头文件
    #include<stdlib.h>
定义函数
    long atol(const char *nptr);
函数说明
    atol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结果返回。
返回值
    返回转换后的长整型数。
附加说明
    atol()与使用strtol(nptr,(char**)NULL,10);结果相同。
范例
    /*将字符串a与字符串b转换成数字后相加*/
#include<stdlib.h>
main()
{
char a[]=”1000000000”;
char b[]=” 234567890”;
long c;
c=atol(a)+atol(b);
printf(“c=%d ”,c);
}
执行
    c=1234567890
 


   
gcvt(将浮点型数转换为字符串,取四舍五入)
相关函数
    ecvt,fcvt,sprintf
表头文件
    #include<stdlib.h>
定义函数
    char *gcvt(double number,size_t ndigits,char *buf);
函数说明
    gcvt()用来将参数number转换成ASCII码字符串,参数ndigits表示显示的位数。gcvt()与ecvt()和fcvt()不同的地方在于,gcvt()所转换后的字符串包含小数点或正负符号。若转换成功,转换后的字符串会放在参数buf指针所指的空间。
返回值
    返回一字符串指针,此地址即为buf指针。
附加说明
   
范例
    #include<stdlib.h>
main()
{
double a=123.45;
double b=-1234.56;
char *ptr;
int decpt,sign;
gcvt(a,5,ptr);
printf(“a value=%s ”,ptr);
ptr=gcvt(b,6,ptr);
printf(“b value=%s ”,ptr);
}
执行
    a value=123.45
b value=-1234.56
 


   
strtod(将字符串转换成浮点数)
相关函数
    atoi,atol,strtod,strtol,strtoul
表头文件
    #include<stdlib.h>
定义函数
    double strtod(const char *nptr,char **endptr);
函数说明
    strtod()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,到出现非数字或字符串结束时('')才结束转换,并将结果返回。若endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr传回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分。如123.456或123e-2。
返回值
    返回转换后的浮点型数。
附加说明
    参考atof()。
范例
    /*将字符串a,b,c 分别采用10,2,16 进制转换成数字*/
#include<stdlib.h>
mian()
{
char a[]=”1000000000”;
char b[]=”1000000000”;
char c[]=”ffff”;
printf(“a=%d ”,strtod(a,NULL,10));
printf(“b=%d ”,strtod(b,NULL,2));
printf(“c=%d ”,strtod(c,NULL,16));
}
执行
    a=1000000000
b=512
c=65535
 


   
strtol(将字符串转换成长整型数)
相关函数
    atof,atoi,atol,strtod,strtoul
表头文件
    #include<stdlib.h>
定义函数
    long int strtol(const char *nptr,char **endptr,int base);
函数说明
    strtol()会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如 base值为10则采用10进制,若base值为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用 16进制做转换。一开始strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('')结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。
返回值
    返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。
附加说明
    ERANGE指定的转换字符串超出合法范围。
范例
    /* 将字符串a,b,c 分别采用10,2,16进制转换成数字*/
#include<stdlib.h>
main()
{
char a[]=”1000000000”;
char b[]=”1000000000”;
char c[]=”ffff”;
printf(“a=%d ”,strtol(a,NULL,10));
printf(“b=%d ”,strtol(b,NULL,2));
printf(“c=%d ”,strtol(c,NULL,16));
}
执行
    a=1000000000
b=512
c=65535
 


   
strtoul(将字符串转换成无符号长整型数)
相关函数
    atof,atoi,atol,strtod,strtol
表头文件
    #include<stdlib.h>
定义函数
    unsigned long int strtoul(const char *nptr,char **endptr,int base);
函数说明
    strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用16进制做转换。一开始strtoul()会扫描参数nptr字符串,跳过前面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('')结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。
返回值
    返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。
附加说明
    ERANGE指定的转换字符串超出合法范围。
范例
    参考strtol()
 


   
toascii(将整型数转换成合法的ASCII 码字符)
相关函数
    isascii,toupper,tolower
表头文件
    #include<ctype.h>
定义函数
    int toascii(int c)
函数说明
    toascii()会将参数c转换成7位的unsigned char值,第八位则会被清除,此字符即会被转成ASCII码字符。
返回值
    将转换成功的ASCII码字符值返回。
范例
    #include<stdlib.h>
main()
{
int a=217;
char b;
printf(“before toascii () : a value =%d(%c) ”,a,a);
b=toascii(a);
printf(“after toascii() : a value =%d(%c) ”,b,b);
}
执行
    before toascii() : a value =217()
after toascii() : a value =89(Y)
 


   
tolower(将大写字母转换成小写字母)
相关函数
    isalpha,toupper
表头文件
    #include<stdlib.h>
定义函数
    int tolower(int c);
函数说明
    若参数c为大写字母则将该对应的小写字母返回。
返回值
    返回转换后的小写字母,若不须转换则将参数c值返回。
附加说明
   
范例
    /* 将s字符串内的大写字母转换成小写字母*/
#include<ctype.h>
main()
{
char s[]=”aBcDeFgH12345;!#$”;
int i;
printf(“before tolower() : %s ”,s);
for(i=0;I<sizeof(s);i++)
s=tolower(s);
printf(“after tolower() : %s ”,s);
}
执行
    before tolower() : aBcDeFgH12345;!#$
after tolower() : abcdefgh12345;!#$
 


   
toupper(将小写字母转换成大写字母)
相关函数
    isalpha,tolower
表头文件
    #include<ctype.h>
定义函数
    int toupper(int c);
函数说明
    若参数c为小写字母则将该对映的大写字母返回。
返回值
    返回转换后的大写字母,若不须转换则将参数c值返回。
附加说明
   
范例
    /* 将s字符串内的小写字母转换成大写字母*/
#include<ctype.h>
main()
{
char s[]=”aBcDeFgH12345;!#$”;
int i;
printf(“before toupper() : %s ”,s);
for(i=0;I<sizeof(s);i++)
s=toupper(s);
printf(“after toupper() : %s ”,s);
}
执行
    before toupper() : aBcDeFgH12345;!#$
after toupper() : ABCDEFGH12345;!#$


atof(将字符串转换成浮点型数)  
相关函数  atoi,atol,strtod,strtol,strtoul

表头文件  #include <stdlib.h>

定义函数  double atof(const char *nptr);

函数说明  atof()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分,如123.456或123e-2。

返回值  返回转换后的浮点型数。

附加说明  atof()与使用strtod(nptr,(char**)NULL)结果相同。

atoi(将字符串转换成整型数)  
相关函数  atof,atol,atrtod,strtol,strtoul

表头文件  #include<stdlib.h>

定义函数  int atoi(const char *nptr);

函数说明  atoi()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。

返回值  返回转换后的整型数。

附加说明  atoi()与使用strtol(nptr,(char**)NULL,10);结果相同。

atol(将字符串转换成长整型数)  
相关函数  atof,atoi,strtod,strtol,strtoul

表头文件  #include<stdlib.h>

定义函数  long atol(const char *nptr);

函数说明  atol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('\0')才结束转换,并将结果返回。

返回值  返回转换后的长整型数。

附加说明  atol()与使用strtol(nptr,(char**)NULL,10);结果相同。

gcvt(将浮点型数转换为字符串,取四舍五入)  
相关函数  ecvt,fcvt,sprintf

表头文件  #include<stdlib.h>

定义函数  char *gcvt(double number,size_t ndigits,char *buf);

函数说明  gcvt()用来将参数number转换成ASCII码字符串,参数ndigits表示显示的位数。gcvt()与ecvt()和fcvt()不同的地方在于,gcvt()所转换后的字符串包含小数点或正负符号。若转换成功,转换后的字符串会放在参数buf指针所指的空间。

返回值  返回一字符串指针,此地址即为buf指针。

strtod(将字符串转换成浮点数)  
相关函数  atoi,atol,strtod,strtol,strtoul

表头文件  #include<stdlib.h>

定义函数  double strtod(const char *nptr,char **endptr);

函数说明  strtod()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,到出现非数字或字符串结束时('\0')才结束转换,并将结果返回。若endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr传回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分。如123.456或123e-2。

返回值  返回转换后的浮点型数。

strtol(将字符串转换成长整型数)  
相关函数  atof,atoi,atol,strtod,strtoul

表头文件  #include<stdlib.h>

定义函数  long int strtol(const char *nptr,char **endptr,int base);

函数说明  strtol()会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制等。当base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用16进制做转换。一开始strtol()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。

返回值  返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。

附加说明  ERANGE指定的转换字符串超出合法范围。

strtoul(将字符串转换成无符号长整型数)  
相关函数  atof,atoi,atol,strtod,strtol

表头文件  #include<stdlib.h>

定义函数  unsigned long int strtoul(const char *nptr,char **endptr,int base);

函数说明  strtoul()会将参数nptr字符串根据参数base来转换成无符号的长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制,若base值为16则采用16进制数等。当base值为0时则是采用10进制做转换,但遇到如'0x'前置字符则会使用16进制做转换。一开始strtoul()会扫描参数nptr字符串,跳过前面的空格字符串,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时('\0')结束转换,并将结果返回。若参数endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr返回。

返回值  返回转换后的长整型数,否则返回ERANGE并将错误代码存入errno中。

附加说明  ERANGE指定的转换字符串超出合法范围。

toascii(将整型数转换成合法的ASCII 码字符)  
相关函数  isascii,toupper,tolower

表头文件  #include<ctype.h>

定义函数  int toascii(int c)

函数说明  toascii()会将参数c转换成7位的unsigned char值,第八位则会被清除,此字符即会被转成ASCII码字符。

返回值  将转换成功的ASCII码字符值返回。

老版本了,新版本看,phpMyAdmin正确的安装配置:http://www.cnblogs.com/vit4/archive/2012/10/26/2741291.html

注意是不是PHP的session没有打开,打开办法:https://jackxiang.com/post/8379/
————————————————————————————————————————

1、先下载 phpMyAdmin 安装包 ,http://www.phpmyadmin.net

2、解压后一个单独目录中(你可以自定义目录名称)

3、找到 /libraries/config.default.php文件(旧版本是根目录下的config.inc.php文件),用写字板(不要用记事本,这是UTF8编码)进行编辑。

4、查找 $cfg['PmaAbsoluteUri']
修改为你将上传到空间的phpMyAdmin的网址
如:$cfg['PmaAbsoluteUri'] = 'http://bbs.bitscn.com/phpmyadmin/';

5、查找 $cfg['Servers'][$i]['host'] = 'localhost';(通常用默认,也有例外,可以不用修改)

6、查找 $cfg['Servers'][$i]['auth_type'] = 'config'; (本人改为cookie后出现问题,最好别改)
在自己的机子里调试用config;如果在网络上的空间用cookie,这里我们既然在前面已经添加了网址,就修改成cookie ,这里建议使用cookie.

7、查找 $cfg['Servers'][$i]['user'] = 'root'; // MySQL user(用户名,自己机里用root;在网上一般为你的ftp用户名,虚拟主机提供商会告诉你的;一般不要修改)

8、查找 $cfg['Servers'][$i]['password'] = ''; // MySQL password (only needed
自己机里不用设,留空就可以了

9、查找 $cfg['Servers'][$i]['only_db'] = ''; // If set to a db-name, only(你只有一个数据就设置一下;如果你在本机或想架设服务器,那么建议留空)

10、查找 $cfg['DefaultLang'] = 'zh'; (这里是选择语言,zh代表简体中文的意思)

安装完保存OK

登陆时出现:配置文件现在需要绝密的短语密码(blowfish_secret)  

config.default.php文件里面进行设置
$cfg['blowfish_secret'] = 'cookie';
$cfg['Servers'][$i]['auth_type']     = 'cookie';

如果是在配置正确的情况下清空cookie就可以了。

http_post.h
[codes=php]
#ifndef __HTTP_POST__
#define __HTTP_POST__

#define SERVER_ADDR "123.57.252.183"
#define SERVER_PORT 80
#define SERVER_URL  "ai.egg.levoo.com"
#define SERVER_PATH "/Api/upload"

#define HTTP_HEAD   "POST %s HTTP/1.1\r\n"\
                    "Host: %s\r\n"\
                    "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:59.0) Gecko/20100101 Firefox/59.0\r\n"\
                    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"\
                    "Accept-Language: en-US,en;q=0.5\r\n"\
                    "Accept-Encoding: gzip, deflate\r\n"\
                    "Content-Type: multipart/form-data; boundary=%s\r\n"\
                    "Content-Length: %ld\r\n"\
                    "Connection: close\r\n"\
                    "Upgrade-Insecure-Requests: 1\r\n"\
                    "DNT: 1\r\n\r\n"\


#define UPLOAD_REQUEST  "--%s\r\n"\
                        "Content-Disposition: form-data; name=\"image\"; filename=\"%s\"\r\n"\
                        "Content-Type: image/jpeg\r\n\r\n"

unsigned long get_file_size(const char *path);

int http_post_upload_pic(const unsigned char *IP, const unsigned int port,char *URL, const char *filepath,
                                    char *ack_json, int ack_len); //Post方式上传图片

#endif
[/codes]

[codes=php]
#cat snprint.c
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/stat.h>
#include "http_post.h"

unsigned char http_boundary[64]={0};
unsigned char send_request[1024]={0};
unsigned char send_end[1024]={0};
int main(int argc, char *argv[])
{
    long long int timestamp;
    struct timeval tv;
    timestamp = (long long int)tv.tv_sec * 1000 + tv.tv_usec;
    snprintf(http_boundary,64,"---------------------------%lld",timestamp);
    const char *filepath = argv[1];
    unsigned long totalsize = 0;
    unsigned long filesize = -1;
    unsigned long request_len = snprintf(send_request,1024,UPLOAD_REQUEST,http_boundary,filepath); //请求信息
    unsigned long end_len = snprintf(send_end,1024,"\r\n--%s--\r\n",http_boundary); //结束信息
    struct stat statbuff;
    if(stat(filepath, &statbuff) < 0){
        return filesize;
    }else{
        filesize = statbuff.st_size;
    }
    printf("eggpic.jpeg's filesize %ld\n",filesize);
    return 0;
}
[/codes]
#gdb a.out
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-94.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /data/codesdev/http_post/a.out...done.
(gdb) set args eggpic.jpeg
(gdb) b 23
Breakpoint 1 at 0x400645: file snprint.c, line 23.
(gdb) r
Starting program: /data/codesdev/http_post/a.out eggpic.jpeg

Breakpoint 1, main (argc=2, argv=0x7fffffffe798) at snprint.c:23
23          unsigned long end_len = snprintf(send_end,1024,"\r\n--%s--\r\n",http_boundary); //结束信息
(gdb) p argv[1]
$1 = 0x7fffffffea21 "eggpic.jpeg"

(gdb) p filepath
$2 = 0x7fffffffea1b "eggpic.jpeg"

if(stat(filepath, &statbuff) < 0){
(gdb) p send_request
$4 = '-' <repeats 29 times>, "4197109\r\nContent-Disposition: form-data; name=\"image\"; filename=\"eggpic.jpeg\"\r\nContent-Type: image/jpeg\r\n\r\n", '\000' <repeats 887 times>

30          printf("eggpic.jpeg's filesize %ld\n",filesize);
(gdb) p filesize
$3 = 13473
(gdb) n
eggpic.jpeg's filesize 13473
31          return 0;
(gdb) n

snprintf在C语言里字符串上,Http里使用较多,
int snprintf(char *restrict buf, size_t n, const char * restrict  format, ...);
函数说明:最多从源串中拷贝n-1个字符到目标串中,然后再在后面加一个0。所以如果目标串的大小为n
                 的话,将不会溢出。

函数返回值:若成功则返回存入数组的字符数,若编码出错则返回负值。

Result1(推荐的用法)

       #include <stdio.h>
       #include <stdlib.h>
      
       int main()
       {
      
           char str[10];
           snprintf(str,sizeof(str),"0123456789012345678");
           printf("str = %s \n",str);
          return 0;
     }

root@darkstar:/home/zhangl/unixtest/chapter9# ./testsprintf    

str = 012345678


Result2:(不推荐使用)

        #include <stdio.h>
       #include <stdlib.h>
      
       int main()
       {
      
           char str[10];
           snprintf(str,18,"0123456789012345678");
           printf("str = %s \n",str);
          return 0;
    }

root@darkstar:/home/zhangl/unixtest/chapter9# ./testsprintf                    

str = 01234567890123456


snprintf函数返回值的测试:
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
           char str[10];
           int n=0;
    
           n=snprintf(str,sizeof(str),"%s","abc");
              printf("str = %s \n",str);
              printf("n=%d\n",n);
      
              return 0;
   }


Result:

root@darkstar:/home/zhangl/test# ./testsnprintf    
str = abc
n=3
select left(email,locate('@',email)-1),(select name from domain where domain.enterpriseid = email.enterpriseid limit 1) as name from email where id=492699 and left(email,locate('@',email)-1)='chengjr' limit 1
如有困难参考:

http://www.toplee.com/blog/329.html
http://tieba.baidu.com/f?kz=206696384
http://oss.lzu.edu.cn/blog/article.php?tid_45.html


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>  
#include "/usr/local/mysql/include/mysql/mysql.h"
int main(void)
{
char mysqlServer[20] = "10.88.15.114";
char query[300];
MYSQL myData;
MYSQL_RES *res;
MYSQL_FIELD *fd;
MYSQL_ROW row;
int rowCount = 0;
int colCount = 0;
int i, j;

mysql_init( &myData );
if(mysql_real_connect( &myData, mysqlServer, "web", "sinatest", "enterprise",3306,NULL,0))
{
printf("connect mysql error!\n");
return 0;
}

sprintf(query,"select * from domain");
if( mysql_query(&myData, query) != 0 )
{
printf("query error!\n");
return 0;
}
else
{
res = mysql_store_result( &myData );
rowCount = (int) mysql_num_rows( res );
colCount = (int) mysql_num_fields( res );
printf(" result: %d records found\n fields: %d \n", rowCount, colCount);
row = mysql_fetch_row( res );
for(i = 0; i < rowCount; i++)
{
printf(" show: ");
for( j = 0; j < colCount; j++)
{
printf("[ %s ] ", row[j] );
} // end for
printf(" \n ");
} // end for
}

return 0;
}


--------------------------------------------------------------
编译:
gcc -o aaa mysqlconn.c -L /usr/local/mysql/lib/mysql/*.a -lz -lm(混乱哈哈)
注意:
要#include <math.h> 编译的时候要加上-lm.
否则出现:

/usr/local/mysql/lib/mysql/libmysqlclient.a(password.o)(.text+0x308): In function `scramble_323':
: undefined reference to `floor'


连接是这样做的:  

if(mysql_real_connect(conn,host_name,user_name,password,port_num,socket_name,flags)==null)  
  {  
  print_error(conn,"mysql_real_connect()   failed");  
  return(null);  
  }  
  if(db_name   !=null)  
  {  
    if(mysql_select_db(conn,db_name)!=0)\{  
    {  
        print_error(conn,"mysql_select_db()   failed");  
#file a b c makefile
CGI_DIR =bin


all:myfile

myfile:filea.o fileb.o filec.o
        gcc filea.o fileb.o filec.o -o myfile
filea.o:filea.c head.h
        gcc -c filea.c
fileb.o:fileb.c head.h
        gcc -c fileb.c
filec.o:filec.c head.h
        gcc -c filec.c

install:
        @if [ ! -d $(CGI_DIR) ]; then \
        mkdir -p $(CGI_DIR); \
        fi
        cp myfile /usr/home/xiangdong2/c++/make/$(CGI_DIR)




clean:
        rm -f *.o;
        rm -rf myfile
        rm -rf bin

(注意:这儿的要执行的命令要用tab键隔开,否则出现:operation erro)



———————————shell环境下直接make生成可供gdb调试的二进制文件。—————————————
root@192.168.137.128:~/dev_codes_all/arts_debug_book/arts_debug/chapt1# make ins CFLAGS="-Wall -o2 -g"
cc -Wall -o2 -g    ins.c   -o ins
root@192.168.137.128:~/dev_codes_all/arts_debug_book/arts_debug/chapt1# gdb ins
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/dev_codes_all/arts_debug_book/arts_debug/chapt1/ins...done.
(gdb) l
53      void print_results()
54      {
55        int i;
56        for (i = 0; i < num_inputs; i++)
57          printf("%d\n", y[i]);
58      }
59          
60      int main(int argc,char **argv)
61      {
62        get_args(argc,argv);
(gdb)
“我将给他一个无法拒绝的理由……”恐怕世界上在没有哪个演员能够演绎《教父》中,马龙·白兰度的这句对白,“教父”在人们心中所留下难以泯灭的印象是男人、是男人的家庭和男人的爱。
“不要憎恨你的敌人,否则你将做出错误的判断。”
,“让朋友低估你的优点,让敌人高估你的缺点……”
不照顾家人的男人,根本算不上是个男人!”

http://forum.ubuntu.org.cn/about63843-0-asc-0.html



[url=http://www.ithaka.cn/Down/View.asp?id=11][/url]
分页: 256/272 第一页 上页 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 下页 最后页 [ 显示模式: 摘要 | 列表 ]