Chinaunix

标题: Linux网络编程一步一步学-IPv6下网络编程步骤 [打印本页]

作者: zhoulifa    时间: 2007-01-29 13:31
标题: Linux网络编程一步一步学-IPv6下网络编程步骤
大家都知道,随着互联网上主机数量的增多,现有的32位IP地址已经不够用了,所以推出了下一代IP地址IPv6,写网络程序的要稍微改变一下现有的网络程序适应IPv6网络是相当容易的事。
对于我们来说就是IP地址变化了,所以程序里在用到IP地址的地方做相应的改变就可以了。

记住:主要是改变程序里设置IP地址和端口等部分的代码。

服务器端源代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>

  11. #define MAXBUF 1024
  12. /************关于本文档********************************************
  13. *filename: ipv6-server.c
  14. *purpose: 演示最基本的IPv6网络编程步骤,开启服务接收客户端连接并和客户端通信,互相收发消息
  15. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  16. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  17. *date time:2007-01-29 13:06
  18. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  19. * 但请遵循GPL
  20. *Thanks to:Google
  21. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  22. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  23. *********************************************************************/

  24. int main(int argc, char **argv)
  25. {
  26.     int sockfd, new_fd;
  27.     socklen_t len;

  28.     /* struct sockaddr_in my_addr, their_addr; */ // IPv4
  29.     struct sockaddr_in6 my_addr, their_addr; // IPv6

  30.     unsigned int myport, lisnum;
  31.     char buf[MAXBUF + 1];

  32.     if (argv[1])
  33.         myport = atoi(argv[1]);
  34.     else
  35.         myport = 7838;

  36.     if (argv[2])
  37.         lisnum = atoi(argv[2]);
  38.     else
  39.         lisnum = 2;

  40.     /* if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) { */ // IPv4
  41.     if ((sockfd = socket(PF_INET6, SOCK_STREAM, 0)) == -1) { // IPv6
  42.         perror("socket");
  43.         exit(1);
  44.     } else
  45.         printf("socket created\n");

  46.     bzero(&my_addr, sizeof(my_addr));
  47.     /* my_addr.sin_family = PF_INET; */ // IPv4
  48.     my_addr.sin6_family = PF_INET6;    // IPv6
  49.     /* my_addr.sin_port = htons(myport); */ // IPv4
  50.     my_addr.sin6_port = htons(myport);   // IPv6
  51.     if (argv[3])
  52.         /* my_addr.sin_addr.s_addr = inet_addr(argv[3]); */ // IPv4
  53.         inet_pton(AF_INET6, argv[3], &my_addr.sin6_addr);  // IPv6
  54.     else
  55.         /* my_addr.sin_addr.s_addr = INADDR_ANY; */ // IPv4
  56.         my_addr.sin6_addr = in6addr_any;            // IPv6

  57.     /* if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) */ // IPv4
  58.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr_in6))  // IPv6
  59.         == -1) {
  60.         perror("bind");
  61.         exit(1);
  62.     } else
  63.         printf("binded\n");

  64.     if (listen(sockfd, lisnum) == -1) {
  65.         perror("listen");
  66.         exit(1);
  67.     } else
  68.         printf("begin listen\n");

  69.     while (1) {
  70.         len = sizeof(struct sockaddr);
  71.         if ((new_fd =
  72.              accept(sockfd, (struct sockaddr *) &their_addr,
  73.                     &len)) == -1) {
  74.             perror("accept");
  75.             exit(errno);
  76.         } else
  77.             printf("server: got connection from %s, port %d, socket %d\n",
  78.                    /* inet_ntoa(their_addr.sin_addr), */ // IPv4
  79.                    inet_ntop(AF_INET6, &their_addr.sin6_addr, buf, sizeof(buf)), // IPv6
  80.                    /* ntohs(their_addr.sin_port), new_fd); */ // IPv4
  81.                    their_addr.sin6_port, new_fd); // IPv6

  82.         /* 开始处理每个新连接上的数据收发 */
  83.         bzero(buf, MAXBUF + 1);
  84.         strcpy(buf,
  85.                "这是在连接建立成功后向客户端发送的第一个消息\n只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息\n");
  86.         /* 发消息给客户端 */
  87.         len = send(new_fd, buf, strlen(buf), 0);
  88.         if (len < 0) {
  89.             printf
  90.                 ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  91.                  buf, errno, strerror(errno));
  92.         } else
  93.             printf("消息'%s'发送成功,共发送了%d个字节!\n",
  94.                    buf, len);

  95.         bzero(buf, MAXBUF + 1);
  96.         /* 接收客户端的消息 */
  97.         len = recv(new_fd, buf, MAXBUF, 0);
  98.         if (len > 0)
  99.             printf("接收消息成功:'%s',共%d个字节的数据\n",
  100.                    buf, len);
  101.         else
  102.             printf
  103.                 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  104.                  errno, strerror(errno));
  105.         /* 处理每个新连接上的数据收发结束 */
  106.     }

  107.     close(sockfd);
  108.     return 0;
  109. }
复制代码

每行程序后面的 “//IPv4” 表示这行代码是在IPv4网络里用的
而“//IPv6” 表示这行代码是在IPv6网络里用的,比较一下,会很容易看到差别的。

客户端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>

  10. #define MAXBUF 1024
  11. /************关于本文档********************************************
  12. *filename: ipv6-client.c
  13. *purpose: 演示最基本的IPv6网络编程步骤,这是个客户端程序,与服务器互相收发消息
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-29 12:56
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Thanks to:Google
  20. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  21. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  22. *********************************************************************/
  23. int main(int argc, char **argv)
  24. {
  25.     int sockfd, len;
  26.     /* struct sockaddr_in dest; */ // IPv4
  27.     struct sockaddr_in6 dest;      // IPv6
  28.     char buffer[MAXBUF + 1];

  29.     if (argc != 3) {
  30.         printf
  31.             ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  32.              argv[0], argv[0]);
  33.         exit(0);
  34.     }
  35.     /* 创建一个 socket 用于 tcp 通信 */
  36.     /* if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { */ // IPv4
  37.     if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {      // IPv6
  38.         perror("Socket");
  39.         exit(errno);
  40.     }
  41.     printf("socket created\n");

  42.     /* 初始化服务器端(对方)的地址和端口信息 */
  43.     bzero(&dest, sizeof(dest));
  44.     /* dest.sin_family = AF_INET; */  // IPv4
  45.     dest.sin6_family = AF_INET6;     // IPv6
  46.     /* dest.sin_port = htons(atoi(argv[2])); */ // IPv4
  47.     dest.sin6_port = htons(atoi(argv[2]));     // IPv6
  48.     /* if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) { */ // IPv4
  49.     if ( inet_pton(AF_INET6, argv[1], &dest.sin6_addr) < 0 ) {                 // IPv6
  50.         perror(argv[1]);
  51.         exit(errno);
  52.     }
  53.     printf("address created\n");

  54.     /* 连接服务器 */
  55.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  56.         perror("Connect ");
  57.         exit(errno);
  58.     }
  59.     printf("server connected\n");

  60.     /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  61.     bzero(buffer, MAXBUF + 1);
  62.     /* 接收服务器来的消息 */
  63.     len = recv(sockfd, buffer, MAXBUF, 0);
  64.     if (len > 0)
  65.         printf("接收消息成功:'%s',共%d个字节的数据\n",
  66.                buffer, len);
  67.     else
  68.         printf
  69.             ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  70.              errno, strerror(errno));

  71.     bzero(buffer, MAXBUF + 1);
  72.     strcpy(buffer, "这是客户端发给服务器端的消息\n");
  73.     /* 发消息给服务器 */
  74.     len = send(sockfd, buffer, strlen(buffer), 0);
  75.     if (len < 0)
  76.         printf
  77.             ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  78.              buffer, errno, strerror(errno));
  79.     else
  80.         printf("消息'%s'发送成功,共发送了%d个字节!\n",
  81.                buffer, len);

  82.     /* 关闭连接 */
  83.     close(sockfd);
  84.     return 0;
  85. }
复制代码


编译程序用下列命令:
gcc -Wall ipv6-server.c -o ipv6server
gcc -Wall ipv6-client.c -o ipv6client


你自己的主机有IPv6地址吗?很多人会问,输入ifconfig命令看一下吧:
eth0      链路封装:以太网  硬件地址 00:14:2A:6D:5B:A5
          inet 地址:192.168.0.167  广播:192.168.0.255  掩码:255.255.255.0
          inet6 地址: fe80::214:2aff:fe6d:5ba5/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  跃点数:1
          接收数据包:30507 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:26797 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:1000
          接收字节:31461154 (30.0 MiB)  发送字节:4472810 (4.2 MiB)
          中断:185 基本地址:0xe400

lo        链路封装:本地环回
          inet 地址:127.0.0.1  掩码:255.0.0.0
          inet6 地址: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  跃点数:1
          接收数据包:13 错误:0 丢弃:0 过载:0 帧数:0
          发送数据包:13 错误:0 丢弃:0 过载:0 载波:0
          碰撞:0 发送队列长度:0
          接收字节:1178 (1.1 KiB)  发送字节:1178 (1.1 KiB)


看到“inet6 地址:”这两行了吗?后面就是你的IPv6地址
关于IPv6地址类型,可以参考一下 惠普主页上的技术文档资料“IPv6地址类型”

启动服务:
./ipv6server 7838 1
或者加上IP地址启动服务:
./ipv6server 7838 1 fe80::214:2aff:fe6d:5ba5/64

启动客户端测试一下:
./ipv6client ::1/128 7838

./ipv6client fe80::214:2aff:fe6d:5ba5/6 7838

[ 本帖最后由 zhoulifa 于 2007-1-29 13:34 编辑 ]
作者: universes    时间: 2007-01-29 15:07
赞一个先!
本人也是搞IPV6的,不过不是开源,在公司做IPV6产品的开发。编写了公司ND(邻居发现)部分的全部代码。
另外修改了TELNET代码以适应IPV6。
感觉IPV6现在还不很成熟,有兴趣的话可以一起交流、学习!
作者: arenxl    时间: 2007-01-29 15:18
以前关注过,不过还是等以后用的时候再继续研究吧。
作者: zhoulifa    时间: 2007-01-29 17:37
to universes:
高人!
你有没有NDP协议rfc文档? 还是说NDP目前没有标准,只是大家各自按自己的协议在做?
如果有,可否发一份给我? zhoulifa@gmail.com
先谢谢了!
作者: fxyxsl    时间: 2007-01-29 17:49
这个和windows环境下有什么不同?

再请教个问题:windows XP环境下如何获取本机的IP地址(IPv6的地址)?我试了很多方法,2003下可以,XP却始终不行~
作者: converse    时间: 2007-01-29 18:13
LZ你好,我看了你发的几个帖子,感觉不错,建议你整理到一个帖子里面这样以后查找方便也便于将你的想法系列化.我给你加原创精华,谢谢.
作者: yingen2006    时间: 2007-01-29 23:00
mark
作者: 大大狗    时间: 2007-01-29 23:45
强人
作者: zhoulifa    时间: 2007-02-03 21:00
原帖由 converse 于 2007-1-29 18:13 发表
LZ你好,我看了你发的几个帖子,感觉不错,建议你整理到一个帖子里面这样以后查找方便也便于将你的想法系列化.我给你加原创精华,谢谢.


感谢版主!我也想全部发到这里,但不知道怎样才能一次发完全部的,所以只挑了几个有代表性的放在这里。我原本是一系列“ Linux网络编程一步一步学”,共24篇,如下:

• Linux网络编程一步一步学-简单客户端编写

  • Linux网络编程一步一步学-绑定IP和端口

• Linux网络编程一步一步学-循环读取服务器上的数据

  • Linux网络编程一步一步学-设置非阻塞方式

  • Linux网络编程一步一步学-开启网络监听服务

• Linux网络编程一步一步学-接受客户端连接请求

• Linux网络编程一步一步学-向客户端发送消息

  • Linux网络编程一步一步学-客户端和服务器端互相收发消息

  • Linux网络编程一步一步学-UDP编程介绍

  • Linux网络编程一步一步学-UDP方式点对点通讯

• Linux网络编程一步一步学-UDP方式广播通讯

• Linux网络编程一步一步学-网络广播、组播与单播

  • Linux网络编程一步一步学-UDP组播

  • Linux网络编程一步一步学-同步聊天程序

  • Linux网络编程一步一步学-异步通讯聊天程序select

  • Linux网络编程一步一步学-编写一个HTTP协议的目录浏览和文件下载服务器

• Linux网络编程一步一步学-用C自己编写一个telnet服务器

• Linux网络编程一步一步学-网络编程函数说明-来自“永远的UNIX”

  • Linux下上网方法总结

• Linux网络编程一步一步学-利用OpenSSL提供的SSL操作函数进行加密通讯原始例子

  • Linux网络编程一步一步学-IPv6下网络编程步骤

  • Linux网络编程一步一步学-HTTPS客户端程序示例

• OpenSSL体系下使用密钥数字证书等

• Linux网络编程一步一步学-epoll同时处理海量连接的代码  编写一个服务器同时处理上万的网络连接,这对大量用户同时在线的编程相当重要。

• Linux网络编程一步一步学-加密通讯协议SSL研究 这对银行、证券等行业来说相当重要

•  Linux网络编程一步一步学-select详解

还请告诉我如何将此全部发到这里。

[ 本帖最后由 zhoulifa 于 2007-2-3 21:09 编辑 ]
作者: converse    时间: 2007-02-03 22:32
>>还请告诉我如何将此全部发到这里。
汗,你就把帖子一个一个的贴在这个帖子里面就好了,我敢打赌会N热门的~~
作者: zw2002    时间: 2007-02-03 22:51
是啊,支持
作者: linternt    时间: 2007-02-03 23:51
现在有代码都实现IPV4和IPV6通用了,支持精神!
作者: zhoulifa    时间: 2007-02-04 12:23
原帖由 converse 于 2007-2-3 22:32 发表
>>还请告诉我如何将此全部发到这里。
汗,你就把帖子一个一个的贴在这个帖子里面就好了,我敢打赌会N热门的~~


那我就当一回搬运工了。不过我觉得这种方式比较那个,好象与咱们信息化时代不太相符哦
作者: converse    时间: 2007-02-04 12:29
原帖由 zhoulifa 于 2007-2-4 12:23 发表


那我就当一回搬运工了。不过我觉得这种方式比较那个,好象与咱们信息化时代不太相符哦


或者在某个帖子里面一次过把所有帖子的链接都放进来,我说的方案都是为了方便别人查看你的帖子罢了。
作者: zhoulifa    时间: 2007-02-04 12:30
标题: Linux网络编程一步一步学-简单客户端编写

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>

  10. #define MAXBUF 1024
  11. /************关于本文档********************************************
  12. *filename: simple-socket.c
  13. *purpose: 演示最基本的网络编程步骤,这是个客户端程序
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-23 19:41:54
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/
  22. int main(int argc, char **argv)
  23. {
  24.     int sockfd;
  25.     struct sockaddr_in dest;
  26.     char buffer[MAXBUF];

  27.     if (argc != 3) {
  28.         printf
  29.             ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  30.              argv[0], argv[0]);
  31.         exit(0);
  32.     }
  33.     /* 创建一个 socket 用于 tcp 通信 */
  34.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  35.         perror("Socket");
  36.         exit(errno);
  37.     }

  38.     /* 初始化服务器端(对方)的地址和端口信息 */
  39.     bzero(&dest, sizeof(dest));
  40.     dest.sin_family = AF_INET;
  41.     dest.sin_port = htons(atoi(argv[2]));
  42.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  43.         perror(argv[1]);
  44.         exit(errno);
  45.     }

  46.     /* 连接服务器 */
  47.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  48.         perror("Connect ");
  49.         exit(errno);
  50.     }

  51.     /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  52.     bzero(buffer, MAXBUF);
  53.     recv(sockfd, buffer, sizeof(buffer), 0);
  54.     printf("%s", buffer);

  55.     /* 关闭连接 */
  56.     close(sockfd);
  57.     return 0;
  58. }
复制代码

编译此程序使用如下命令:
gcc -Wall simple-socket.c

运行此程序使用如下命令(假设你的主机上开启了ssh服务):
./a.out 127.0.0.1 22


其实socket客户端编程相当简单:
第1步:建立一个socket句柄,用socket()函数;
第2步:设定你要连接的服务器的IP地址和端口等信息;
第3步:与服务器建立连接,用connect函数;
第4步:收发消息,用recv(sockfd,...)/send(sockfd,...)或者read(sockfd,...)/write(sockfd,...)都可以;
第5步:通讯结束后关闭连接,用close()函数即可,当然也有人用shutdown()函数。


[ 本帖最后由 zhoulifa 于 2007-2-4 12:48 编辑 ]
作者: zhoulifa    时间: 2007-02-04 12:50
标题: Linux网络编程一步一步学-绑定IP和端口
为了服务器的安全,有些服务器要求客户端只能以指定的IP地址和特定的端口才能连接上来。这就要求我们客户端连接之前绑定IP地址和端口信息。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>

  10. #define MAXBUF 1024
  11. /************关于本文档********************************************
  12. *filename: simple-bind.c
  13. *purpose: 演示最基本的网络编程步骤,这是个客户端程序以固定 IP 和端口连接服务器
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-23 19:51:54
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/
  22. int main(int argc, char **argv)
  23. {
  24.     int sockfd;
  25.     struct sockaddr_in dest, mine;
  26.     char buffer[MAXBUF];

  27.     if (argc != 5) {
  28.         printf
  29.             ("参数格式错误!正确用法如下:\n\t\t%s 对方IP地址 对方端口 本机IP地址 本机端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来以本机固定的端口从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  30.              argv[0], argv[0]);
  31.         exit(0);
  32.     }
  33.     /* 创建一个 socket 用于 tcp 通信 */
  34.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  35.         perror("Socket");
  36.         exit(errno);
  37.     }

  38.     /* 初始化服务器端(对方)的地址和端口信息 */
  39.     bzero(&dest, sizeof(dest));
  40.     dest.sin_family = AF_INET;
  41.     dest.sin_port = htons(atoi(argv[2]));
  42.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  43.         perror(argv[1]);
  44.         exit(errno);
  45.     }

  46.     /* 初始化自己的地址和端口信息 */
  47.     bzero(&mine, sizeof(mine));
  48.     mine.sin_family = AF_INET;
  49.     mine.sin_port = htons(atoi(argv[4]));
  50.     if (inet_aton(argv[3], (struct in_addr *) &mine.sin_addr.s_addr) == 0) {
  51.         perror(argv[3]);
  52.         exit(errno);
  53.     }

  54.     /* 把自己的 IP 地址信息和端口与 socket 绑定 */
  55.     if (bind(sockfd, (struct sockaddr *) &mine, sizeof(struct sockaddr)) ==
  56.         -1) {
  57.         perror(argv[3]);
  58.         exit(errno);
  59.     }

  60.     /* 以自己特定的端口和IP连接服务器的特定端口 */
  61.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  62.         perror("Connect ");
  63.         exit(errno);
  64.     }

  65.     /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  66.     bzero(buffer, MAXBUF);
  67.     recv(sockfd, buffer, sizeof(buffer), 0);
  68.     printf("%s", buffer);
  69.     sleep(10);
  70.     /* 关闭连接 */
  71.     close(sockfd);
  72.     return 0;
  73. }
复制代码

编译程序用此命令:
gcc -Wall simple-bind.c

运行程序用此命令:
./a.out 127.0.0.1 22 127.0.0.1 3000

同时可以用下列netstat命令查看网络连接状态:
netstat -an|grep 3000

查看到如下信息:
tcp 0 0 127.0.0.1:3000 127.0.0.1:22 ESTABLISHED


与前面一份源代码相比,这里多了设置自己的IP地址和端口信息,所以编程过程变成了:
第1步:建立一个socket句柄,用socket()函数;
第2步:设定你要连接的服务器的IP地址和端口等信息;
第x步:设定你自己的IP地址和端口等信息与socket绑定,用bind()函数; #与前面相比就是多了这一个步骤
第3步:与服务器建立连接,用connect函数;
第4步:收发消息,用recv(sockfd,...)/send(sockfd,...)或者read(sockfd,...)/write(sockfd,...)都可以;
第5步:通讯结束后关闭连接,用close()函数即可,当然也有人用shutdown()函数。

[ 本帖最后由 zhoulifa 于 2007-2-4 12:55 编辑 ]
作者: zhoulifa    时间: 2007-02-04 12:57
标题: Linux网络编程一步一步学-循环读取服务器上的数据

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>

  10. #define MAXBUF 10
  11. /************关于本文档********************************************
  12. *filename: simple-readall.c
  13. *purpose: 演示最基本的网络编程,循环读取服务器上发过来的内容,直到读完为止
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-23 20:16:54
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/
  22. int main(int argc, char **argv)
  23. {
  24.     int sockfd, ret;
  25.     struct sockaddr_in dest, mine;
  26.     char buffer[MAXBUF + 1];

  27.     if (argc != 5) {
  28.         printf
  29.             ("参数格式错误!正确用法如下:\n\t\t%s 对方IP地址 对方端口 本机IP地址 本机端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来以本机固定的端口从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  30.              argv[0], argv[0]);
  31.         exit(0);
  32.     }
  33.     /* 创建一个 socket 用于 tcp 通信 */
  34.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  35.         perror("Socket");
  36.         exit(errno);
  37.     }

  38.     /* 初始化服务器端(对方)的地址和端口信息 */
  39.     bzero(&dest, sizeof(dest));
  40.     dest.sin_family = AF_INET;
  41.     dest.sin_port = htons(atoi(argv[2]));
  42.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  43.         perror(argv[1]);
  44.         exit(errno);
  45.     }

  46.     /* 初始化自己的地址和端口信息 */
  47.     bzero(&mine, sizeof(mine));
  48.     mine.sin_family = AF_INET;
  49.     mine.sin_port = htons(atoi(argv[4]));
  50.     if (inet_aton(argv[3], (struct in_addr *) &mine.sin_addr.s_addr) == 0) {
  51.         perror(argv[3]);
  52.         exit(errno);
  53.     }

  54.     /* 把自己的 IP 地址信息和端口与 socket 绑定 */
  55.     if (bind(sockfd, (struct sockaddr *) &mine, sizeof(struct sockaddr)) ==
  56.         -1) {
  57.         perror(argv[3]);
  58.         exit(errno);
  59.     }

  60.     /* 连接服务器 */
  61.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  62.         perror("Connect ");
  63.         exit(errno);
  64.     }

  65.     /* 接收对方发过来的消息,每次最多接收 MAXBUF 个字节,直到把对方发过来的所有消息接收完毕为止 */
  66.     do {
  67.         bzero(buffer, MAXBUF + 1);
  68.         ret = recv(sockfd, buffer, MAXBUF, 0);
  69.         printf("读到%d个字节,它们是:'%s'\n", ret, buffer);
  70.     } while (ret == MAXBUF);

  71.     /* 关闭连接 */
  72.     close(sockfd);
  73.     return 0;
  74. }
复制代码

编译程序使用如下命令:
gcc -Wall simple-readall.c

运行程序用如下命令:
./a.out 127.0.0.1 22 127.0.0.1 3000

此程序运行结果如下:
读到10个字节,它们是:'SSH-2.0-Op'
读到10个字节,它们是:'enSSH_4.3p'
读到10个字节,它们是:'2 Debian-5'
读到8个字节,它们是:'ubuntu1
'

注意:如果你运行时程序长久没有退出,请把程序的第一行:
#define MAXBUF 10

稍微改一下,比如改成下面的:
#define MAXBUF 9

再编译运行试试。
为什么?请继续看下一篇文章“设置非阻塞方式”。
作者: zhoulifa    时间: 2007-02-04 13:00
标题: Linux网络编程一步一步学-设置非阻塞方式
[code]
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXBUF 10
/************关于本文档********************************************
*filename: simple-nonblock.c
*purpose: 演示最基本的网络编程,循环读取服务器上发过来的内容,直到读完为止
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-23 20:46:54
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/
int main(int argc, char **argv)
{
    int sockfd, ret, rcvtm = 0;
    struct sockaddr_in dest, mine;
    char buffer[MAXBUF + 1];

    if (argc != 5) {
        printf
            ("参数格式错误!正确用法如下:\n\t\t%s 对方IP地址 对方端口 本机IP地址 本机端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来以本机固定的端口从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
             argv[0], argv[0]);
        exit(0);
    }

    /* 创建一个 socket 用于 tcp 通信 */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket");
        exit(errno);
    }

    /* 初始化服务器端(对方)的地址和端口信息 */
    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(atoi(argv[2]));
    if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }

    /* 初始化自己的地址和端口信息 */
    bzero(&mine, sizeof(mine));
    mine.sin_family = AF_INET;
    mine.sin_port = htons(atoi(argv[4]));
    if (inet_aton(argv[3], (struct in_addr *) &mine.sin_addr.s_addr) == 0) {
        perror(argv[3]);
        exit(errno);
    }

    /* 把自己的 IP 地址信息和端口与 socket 绑定 */
    if (bind(sockfd, (struct sockaddr *) &mine, sizeof(struct sockaddr)) ==
        -1) {
        perror(argv[3]);
        exit(errno);
    }

    /* 连接服务器 */
    if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }

    /* 设置 socket 属性为非阻塞方式 */
    if (fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) {
        perror("fcntl");
        exit(errno);
    }

    /* 接收对方发过来的消息,每次最多接收 MAXBUF 个字节,直到把对方发过来的所有消息接收完毕为止 */
    do {
      _retry:
        bzero(buffer, MAXBUF + 1);
        ret = recv(sockfd, buffer, MAXBUF, 0);
        if (ret > 0)
            printf("读到%d个字节,它们是:'%s'\n", ret, buffer);

        if (ret < 0) {
            if (errno == EAGAIN) {
                if (rcvtm)
                    break;
                else {
                    printf("数据还未到达!\n");
                    usleep(100000);
                    goto _retry;
                };
            };
            printf("接收出错了!\n");
            perror("recv");
        }
        rcvtm++;
    } while (ret == MAXBUF);

    /* 关闭连接 */
    close(sockfd);
    return 0;
}
[code]
编译程序用下列命令:
gcc -Wall simple-nonblock.c

运行程序用下列命令:
./a.out 127.0.0.1 21 127.0.0.1 3000

程序运行输出结果如下:
数据还未到达!
读到10个字节,它们是:'220 (vsFTP'
读到10个字节,它们是:'d 2.0.4)
'


问题:

1、非阻塞是什么?
网络通信有阻塞和非阻塞之分,例如对于接收数据的函数recv:在阻塞方式下,没有数据到达时,即接收不到数据时,程序会停在recv函数这里等待数据的到来;而在非阻塞方式下就不会等,如果没有数据可接收就立即返回-1表示接收失败。
2、什么是errno?
errno是Linux系统下保存当前状态的一个公共变量,当前程序运行时进行系统调用如果出错,则会设置errno为某个值以告诉用户出了什么错误。可以用printf("%d %s\n", errno, strerror(errno));得到具体信息。
3、什么是EAGAIN?
man recv
当recv系统调用返回这个值时表示recv读数据时,对方没有发送数据过来。
作者: zhoulifa    时间: 2007-02-04 13:11
标题: Linux网络编程一步一步学-开启网络监听服务

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>

  11. /************关于本文档********************************************
  12. *filename: simple-listen.c
  13. *purpose: 演示最基本的网络编程步骤,开启服务端的监听,等待客户端来连接
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-24 12:31:00
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/

  22. int main(int argc, char **argv)
  23. {
  24.     int sockfd;
  25.     struct sockaddr_in my_addr;
  26.     unsigned int myport, lisnum;

  27.     if (argv[1])
  28.         myport = atoi(argv[1]);
  29.     else
  30.         myport = 7838;

  31.     if (argv[2])
  32.         lisnum = atoi(argv[2]);
  33.     else
  34.         lisnum = 2;

  35.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  36.         perror("socket");
  37.         exit(1);
  38.     } else
  39.         printf("socket created\n");

  40.     bzero(&my_addr, sizeof(my_addr));
  41.     my_addr.sin_family = PF_INET;
  42.     my_addr.sin_port = htons(myport);
  43.     if (argv[3])
  44.         my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  45.     else
  46.         my_addr.sin_addr.s_addr = INADDR_ANY;

  47.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  48.         == -1) {
  49.         perror("bind");
  50.         exit(1);
  51.     } else
  52.         printf("binded\n");

  53.     if (listen(sockfd, lisnum) == -1) {
  54.         perror("listen");
  55.         exit(1);
  56.     } else
  57.         printf("begin listen\n");

  58.     sleep(100);
  59.     close(sockfd);
  60.     return 0;
  61. }
复制代码

编译程序用下列命令:
gcc -Wall simple-listen.c

运行程序用如下命令:
./a.out 7838 2

这将在你自己主机的所有IP地址是等待客户端连接。比如你的网卡lo的IP地址127.0.0.1,又比如你的网卡eth0的IP地址192.168.0.100
如果要指定只在某个地址是开启监听服务,可以用下面的命令:
./a.out 7838 2 127.0.0.1

这样,客户端只能通过127.0.0.1的7838端口连接你的程序。
你可以开启一个终端输入telnet 127.0.0.1 7838来测试是否能连接成功。
同时可以用netstat -an|grep 7838命令来查看网络是否连接正常

其实socket服务器端编程也是相当简单的:
第1步:建立一个socket句柄,用socket()函数;
第2步:设定自己服务器的IP地址和端口等信息,是用于让对方连接的;
第3步:把自己的IP地址和端口等信息与socket绑定,用bind()函数;
第4步:开启监听服务,用listen()函数;    #至此,客户端已经能连接你这个IP地址和端口了
注:但由于我们还没有接受客户端的连接,没有建立用于消息收发的socket句柄,所以客户端还没法一客户端进行消息收发。

作者: zhoulifa    时间: 2007-02-04 13:19
标题: Linux网络编程一步一步学-接受客户端连接请求

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>

  11. /************关于本文档********************************************
  12. *filename: simple-accept.c
  13. *purpose: 演示最基本的网络编程步骤,开启服务端的监听,并接收每个客户端的连接请求
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-24 12:41
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/

  22. int main(int argc, char **argv)
  23. {
  24.     int sockfd, new_fd;
  25.     socklen_t len;
  26.     struct sockaddr_in my_addr, their_addr;
  27.     unsigned int myport, lisnum;

  28.     if (argv[1])
  29.         myport = atoi(argv[1]);
  30.     else
  31.         myport = 7838;

  32.     if (argv[2])
  33.         lisnum = atoi(argv[2]);
  34.     else
  35.         lisnum = 2;

  36.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  37.         perror("socket");
  38.         exit(1);
  39.     } else
  40.         printf("socket created\n");

  41.     bzero(&my_addr, sizeof(my_addr));
  42.     my_addr.sin_family = PF_INET;
  43.     my_addr.sin_port = htons(myport);
  44.     if (argv[3])
  45.         my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  46.     else
  47.         my_addr.sin_addr.s_addr = INADDR_ANY;

  48.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  49.         == -1) {
  50.         perror("bind");
  51.         exit(1);
  52.     } else
  53.         printf("binded\n");

  54.     if (listen(sockfd, lisnum) == -1) {
  55.         perror("listen");
  56.         exit(1);
  57.     } else
  58.         printf("begin listen\n");

  59.     while (1) {
  60.         len = sizeof(struct sockaddr);
  61.         if ((new_fd =
  62.              accept(sockfd, (struct sockaddr *) &their_addr,
  63.                     &len)) == -1) {
  64.             perror("accept");
  65.             exit(errno);
  66.         } else
  67.             printf("server: got connection from %s, port %d, socket %d\n",
  68.                    inet_ntoa(their_addr.sin_addr),
  69.                    ntohs(their_addr.sin_port), new_fd);
  70.     }

  71.     close(sockfd);
  72.     return 0;
  73. }
复制代码

编译程序用下列命令:
gcc -Wall simple-accept.c

运行程序用如下命令:
./a.out 7838 1 127.0.0.1

另外开多几个客户端程序连接上来,
程序输出结果如下:
server: got connection from 127.0.0.1, port 15949, socket 4
server: got connection from 127.0.0.1, port 15950, socket 5
server: got connection from 127.0.0.1, port 15951, socket 6
server: got connection from 127.0.0.1, port 15952, socket 7


与上一篇相比,这里只是增加了accept函数,这个函数会创建一个新的socket句柄,这个句柄才是我们真正可以用来通讯的句柄,即客户端和服务端的消息收发是通过这个accept产生的socket进行的
accpet是一个典型的阻塞函数,即程序会停止在accept函数处直到有客户端连接上来才会返回。

[ 本帖最后由 zhoulifa 于 2007-2-4 13:37 编辑 ]
作者: zhoulifa    时间: 2007-02-04 13:32
标题: Linux网络编程一步一步学-向客户端发送消息

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>

  11. #define MAXBUF 1024
  12. /************关于本文档********************************************
  13. *filename: simple-send.c
  14. *purpose: 演示最基本的网络编程步骤,开启服务接收客户端连接并向客户端发送消息
  15. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  16. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  17. *date time:2007-01-24 13:00
  18. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  19. * 但请遵循GPL
  20. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  21. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  22. *********************************************************************/

  23. int main(int argc, char **argv)
  24. {
  25.     int sockfd, new_fd;
  26.     socklen_t len;
  27.     struct sockaddr_in my_addr, their_addr;
  28.     unsigned int myport, lisnum;
  29.     char buf[MAXBUF + 1];

  30.     if (argv[1])
  31.         myport = atoi(argv[1]);
  32.     else
  33.         myport = 7838;

  34.     if (argv[2])
  35.         lisnum = atoi(argv[2]);
  36.     else
  37.         lisnum = 2;

  38.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  39.         perror("socket");
  40.         exit(1);
  41.     } else
  42.         printf("socket created\n");

  43.     bzero(&my_addr, sizeof(my_addr));
  44.     my_addr.sin_family = PF_INET;
  45.     my_addr.sin_port = htons(myport);
  46.     if (argv[3])
  47.         my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  48.     else
  49.         my_addr.sin_addr.s_addr = INADDR_ANY;

  50.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  51.         == -1) {
  52.         perror("bind");
  53.         exit(1);
  54.     } else
  55.         printf("binded\n");

  56.     if (listen(sockfd, lisnum) == -1) {
  57.         perror("listen");
  58.         exit(1);
  59.     } else
  60.         printf("begin listen\n");

  61.     while (1) {
  62.         len = sizeof(struct sockaddr);
  63.         if ((new_fd =
  64.              accept(sockfd, (struct sockaddr *) &their_addr,
  65.                     &len)) == -1) {
  66.             perror("accept");
  67.             exit(errno);
  68.         } else
  69.             printf("server: got connection from %s, port %d, socket %d\n",
  70.                    inet_ntoa(their_addr.sin_addr),
  71.                    ntohs(their_addr.sin_port), new_fd);

  72.         /* 开始处理每个新连接上的数据收发 */
  73.         bzero(buf, MAXBUF + 1);
  74.         strcpy(buf,
  75.                "这是在连接建立成功后向客户端发送的第一个消息\n只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息\n");
  76.         len = send(new_fd, buf, strlen(buf), 0);
  77.         if (len < 0) {
  78.             printf
  79.                 ("消息'%s'发送失败!错误代码是%d,错误消息是'%s'\n",
  80.                  buf, errno, strerror(errno));
  81.         } else
  82.             printf("消息'%s'发送成功,共发送了%d个字节!\n",
  83.                    buf, len);
  84.         /* 处理每个新连接上的数据收发结束 */
  85.     }

  86.     close(sockfd);
  87.     return 0;
  88. }
复制代码

编译程序用下列命令:
gcc -Wall simple-send.c

运行程序用如下命令:
./a.out 127.0.0.1 7838 1

程序输出结果如下:
socket created
binded
begin listen
server: got connection from 127.0.0.1, port 13097, socket 4
消息'这是在连接建立成功后向客户端发送的第一个消息
只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息
'发送成功,共发送了227个字节!
server: got connection from 127.0.0.1, port 13099, socket 5
消息'这是在连接建立成功后向客户端发送的第一个消息
只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息
'发送成功,共发送了227个字节!

客户端运行如下命令:
telnet 127.0.0.1 7838

客户端得到输出如下:
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
这是在连接建立成功后向客户端发送的第一个消息
只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息
Connection closed by foreign host.

注意:只对accpet接收连接请求产生的新的socket句柄进行读写操作!
作者: zhoulifa    时间: 2007-02-04 13:45
标题: Linux网络编程一步一步学-客户端和服务器端互相收发消息
服务器端源代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>

  11. #define MAXBUF 1024
  12. /************关于本文档********************************************
  13. *filename: simple-accept.c
  14. *purpose: 演示最基本的网络编程步骤,开启服务接收客户端连接并和客户端通信,互相收发消息
  15. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  16. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  17. *date time:2007-01-24 13:26
  18. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  19. * 但请遵循GPL
  20. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  21. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  22. *********************************************************************/

  23. int main(int argc, char **argv)
  24. {
  25.     int sockfd, new_fd;
  26.     socklen_t len;
  27.     struct sockaddr_in my_addr, their_addr;
  28.     unsigned int myport, lisnum;
  29.     char buf[MAXBUF + 1];

  30.     if (argv[1])
  31.       myport = atoi(argv[1]);
  32.     else
  33.       myport = 7838;

  34.     if (argv[2])
  35.       lisnum = atoi(argv[2]);
  36.     else
  37.       lisnum = 2;

  38.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  39.       perror("socket");
  40.       exit(1);
  41.     } else
  42.       printf("socket created\n");

  43.     bzero(&my_addr, sizeof(my_addr));
  44.     my_addr.sin_family = PF_INET;
  45.     my_addr.sin_port = htons(myport);
  46.     if (argv[3])
  47.       my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  48.     else
  49.       my_addr.sin_addr.s_addr = INADDR_ANY;

  50.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  51.       == -1) {
  52.       perror("bind");
  53.       exit(1);
  54.     } else
  55.       printf("binded\n");

  56.     if (listen(sockfd, lisnum) == -1) {
  57.       perror("listen");
  58.       exit(1);
  59.     } else
  60.       printf("begin listen\n");

  61.     while (1) {
  62.       len = sizeof(struct sockaddr);
  63.       if ((new_fd =
  64.            accept(sockfd, (struct sockaddr *) &their_addr,
  65.                   &len)) == -1) {
  66.           perror("accept");
  67.           exit(errno);
  68.       } else
  69.           printf("server: got connection from %s, port %d, socket %d\n",
  70.                  inet_ntoa(their_addr.sin_addr),
  71.                  ntohs(their_addr.sin_port), new_fd);

  72.       /* 开始处理每个新连接上的数据收发 */
  73.       bzero(buf, MAXBUF + 1);
  74.       strcpy(buf,
  75.              "这是在连接建立成功后向客户端发送的第一个消息\n只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息\n");
  76.       /* 发消息给客户端 */
  77.       len = send(new_fd, buf, strlen(buf), 0);
  78.       if (len < 0) {
  79.           printf
  80.               ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  81.                buf, errno, strerror(errno));
  82.       } else
  83.           printf("消息'%s'发送成功,共发送了%d个字节!\n",
  84.                  buf, len);

  85.       bzero(buf, MAXBUF + 1);
  86.       /* 接收客户端的消息 */
  87.       len = recv(new_fd, buf, MAXBUF, 0);
  88.       if (len > 0)
  89.           printf("接收消息成功:'%s',共%d个字节的数据\n",
  90.                  buf, len);
  91.       else
  92.           printf
  93.               ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  94.                errno, strerror(errno));
  95.       /* 处理每个新连接上的数据收发结束 */
  96.     }

  97.     close(sockfd);
  98.     return 0;
  99. }
复制代码

客户端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>

  10. #define MAXBUF 1024
  11. /************关于本文档********************************************
  12. *filename: simple-socket.c
  13. *purpose: 演示最基本的网络编程步骤,这是个客户端程序,与服务器互相收发消息
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-24 13:26
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/
  22. int main(int argc, char **argv)
  23. {
  24.     int sockfd, len;
  25.     struct sockaddr_in dest;
  26.     char buffer[MAXBUF + 1];

  27.     if (argc != 3) {
  28.         printf
  29.             ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  30.              argv[0], argv[0]);
  31.         exit(0);
  32.     }
  33.     /* 创建一个 socket 用于 tcp 通信 */
  34.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  35.         perror("Socket");
  36.         exit(errno);
  37.     }
  38.     printf("socket created\n");

  39.     /* 初始化服务器端(对方)的地址和端口信息 */
  40.     bzero(&dest, sizeof(dest));
  41.     dest.sin_family = AF_INET;
  42.     dest.sin_port = htons(atoi(argv[2]));
  43.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  44.         perror(argv[1]);
  45.         exit(errno);
  46.     }
  47.     printf("address created\n");

  48.     /* 连接服务器 */
  49.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  50.         perror("Connect ");
  51.         exit(errno);
  52.     }
  53.     printf("server connected\n");

  54.     /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  55.     bzero(buffer, MAXBUF + 1);
  56.     /* 接收服务器来的消息 */
  57.     len = recv(sockfd, buffer, MAXBUF, 0);
  58.     if (len > 0)
  59.         printf("接收消息成功:'%s',共%d个字节的数据\n",
  60.                buffer, len);
  61.     else
  62.         printf
  63.             ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  64.              errno, strerror(errno));

  65.     bzero(buffer, MAXBUF + 1);
  66.     strcpy(buffer, "这是客户端发给服务器端的消息\n");
  67.     /* 发消息给服务器 */
  68.     len = send(sockfd, buffer, strlen(buffer), 0);
  69.     if (len < 0)
  70.         printf
  71.             ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  72.              buffer, errno, strerror(errno));
  73.     else
  74.         printf("消息'%s'发送成功,共发送了%d个字节!\n",
  75.                buffer, len);

  76.     /* 关闭连接 */
  77.     close(sockfd);
  78.     return 0;
  79. }
复制代码

编译两个程序用下列命令:
gcc -Wall simple-server.c -o server
gcc -Wall simple-client.c -o client

启动服务端程序用如下命令:
./server 7838 1

启动客户端程序用如下命令:
./client 127.0.0.1 7838

服务端程序运行屏幕输出如下:
socket created
binded
begin listen
server: got connection from 127.0.0.1, port 32537, socket 4
消息'这是在连接建立成功后向客户端发送的第一个消息
只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息
'发送成功,共发送了227个字节!
接收消息成功:'这是客户端发给服务器端的消息
',共43个字节的数据

客户端程序运行屏幕输出如下:
socket created
address created
server connected
接收消息成功:'这是在连接建立成功后向客户端发送的第一个消息
只能向new_fd这个用accept函数新建立的socket发消息,不能向sockfd这个监听socket发送消息,监听socket不能用来接收或发送消息
',共227个字节的数据
消息'这是客户端发给服务器端的消息
'发送成功,共发送了43个字节!

注意:这里的recv函数也是一个典型的阻塞函数,如果对方没有发送数据,程序都会停在recv函数处等待直到超时出错时退出。
所以这个例子是个典型的同步通讯程序,即一端发送数据时另一端一定要接收数据,反之亦然。
所以一端是send、recv、recv、send时,另一端一定是recv、send、send、recv

[ 本帖最后由 zhoulifa 于 2007-2-4 13:47 编辑 ]
作者: zhoulifa    时间: 2007-02-04 13:53
标题: Linux网络编程一步一步学-UDP编程介绍
通常我们在说到网络编程时默认是指TCP编程,即用前面提到的socket函数创建一个socket用于TCP通讯,函数参数我们通常填为 SOCK_STREAM。即socket(PF_INET, SOCK_STREAM, 0),这表示建立一个socket用于流式网络通讯。
通过查看socket的man手册可以看到socket函数的第一个参数的值可以为下面这些值:
Name                              Purpose
PF_UNIX, PF_LOCAL     Local communication
PF_INET                         IPv4 Internet protocols
PF_INET6                       IPv6 Internet protocols
PF_IPX                           IPX - Novell protocols
PF_NETLINK                  Kernel user interface device
PF_X25                           ITU-T X.25 / ISO-8208 protocol
PF_AX25                         Amateur radio AX.25 protocol
PF_ATMPVC                    Access to raw ATM PVCs
PF_APPLETALK             Appletalk
PF_PACKET                    Low level packet interface

第二个参数支持下列几种值:
SOCK_STREAM
Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be sup‐
ported.

SOCK_DGRAM
Supports datagrams (connectionless, unreliable messages of a fixed maximum length).

SOCK_SEQPACKET
Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a con‐
sumer is required to read an entire packet with each read system call.

SOCK_RAW
Provides raw network protocol access.

SOCK_RDM
Provides a reliable datagram layer that does not guarantee ordering.

SOCK_PACKET
Obsolete and should not be used in new programs; see packet(7).

从这里可以看出,SOCK_STREAM这种的特点是面向连接的,即每次收发数据之前必须通过connect建立连接,也是双向的,即任何一方都可以收发数据,协议本身提供了一些保障机制保证它是可靠的、有序的,即每个包按照发送的顺序到达接收方。
而SOCK_DGRAM 这种是User Datagram Protocol协议的网络通讯,它是无连接的,不可靠的,因为通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据。任何一方建立一个 socket以后就可以用sendto发送数据,也可以用recvfrom接收数据。根本不关心对方是否存在,是否发送了数据。它的特点是通讯速度比较快。大家都知道TCP是要经过三次握手的,而UDP没有。
基于上述不同,UDP和TCP编程步骤也有些不同,如下:

/*********************************************************************
*filename: UDP编程介绍
*purpose: 通过比较UDP和TCP编程对二者编程步骤进行总结说明
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-24 20:12:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
*********************************************************************/

TCP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt(); * 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、开启监听,用函数listen();
5、接收客户端上来的连接,用函数accept();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;
8、关闭监听;

TCP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接;

与之对应的UDP编程步骤要简单许多,分别如下:

UDP编程的服务器端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();
4、循环接收数据,用函数recvfrom();
5、关闭网络连接;


UDP编程的客户端一般步骤是:
1、创建一个socket,用函数socket();
2、设置socket属性,用函数setsockopt();* 可选
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选
4、设置对方的IP地址和端口等属性;
5、发送数据,用函数sendto();
6、关闭网络连接;

作者: zhoulifa    时间: 2007-02-04 13:59
标题: Linux网络编程一步一步学-UDP方式点对点通讯
UDP通讯服务器端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <netinet/in.h>
  5. #include <sys/socket.h>
  6. #include <errno.h>
  7. #include <stdlib.h>
  8. #include <arpa/inet.h>

  9. /*********************************************************************
  10. *filename: simple-udpserver.c
  11. *purpose: 基本编程步骤说明,演示了UDP编程的服务器端编程步骤
  12. *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  13. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  14. *date time:2007-01-24 20:22:00
  15. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  16. * 但请遵循GPL
  17. *Thanks to: Google.com
  18. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  19. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  20. *********************************************************************/

  21. int main(int argc, char **argv)
  22. {
  23.     struct sockaddr_in s_addr;
  24.     struct sockaddr_in c_addr;
  25.     int sock;
  26.     socklen_t addr_len;
  27.     int len;
  28.     char buff[128];

  29.     /* 创建 socket , 关键在于这个 SOCK_DGRAM */
  30.     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
  31.       perror("socket");
  32.       exit(errno);
  33.     } else
  34.       printf("create socket.\n\r");

  35.     memset(&s_addr, 0, sizeof(struct sockaddr_in));
  36.     /* 设置地址和端口信息 */
  37.     s_addr.sin_family = AF_INET;
  38.     if (argv[2])
  39.       s_addr.sin_port = htons(atoi(argv[2]));
  40.     else
  41.       s_addr.sin_port = htons(7838);
  42.     if (argv[1])
  43.       s_addr.sin_addr.s_addr = inet_addr(argv[1]);
  44.     else
  45.       s_addr.sin_addr.s_addr = INADDR_ANY;

  46.     /* 绑定地址和端口信息 */
  47.     if ((bind(sock, (struct sockaddr *) &s_addr, sizeof(s_addr))) == -1) {
  48.       perror("bind");
  49.       exit(errno);
  50.     } else
  51.       printf("bind address to socket.\n\r");

  52.     /* 循环接收数据 */
  53.     addr_len = sizeof(c_addr);
  54.     while (1) {
  55.       len = recvfrom(sock, buff, sizeof(buff) - 1, 0,
  56.                      (struct sockaddr *) &c_addr, &addr_len);
  57.       if (len < 0) {
  58.           perror("recvfrom");
  59.           exit(errno);
  60.       }

  61.       buff[len] = '\0';
  62.       printf("收到来自%s:%d的消息:%s\n\r",
  63.              inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port), buff);
  64.     }
  65.     return 0;
  66. }
复制代码

客户端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <netinet/in.h>
  5. #include <sys/socket.h>
  6. #include <errno.h>
  7. #include <stdlib.h>
  8. #include <arpa/inet.h>

  9. /*********************************************************************
  10. *filename: simple-udpclient.c
  11. *purpose: 基本编程步骤说明,演示了UDP编程的客户端编程步骤
  12. *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  13. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  14. *date time:2007-01-24 21:22:00
  15. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  16. * 但请遵循GPL
  17. *Thanks to: Google.com
  18. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  19. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  20. *********************************************************************/
  21. int main(int argc, char **argv)
  22. {
  23.     struct sockaddr_in s_addr;
  24.     int sock;
  25.     int addr_len;
  26.     int len;
  27.     char buff[128];

  28.     /* 创建 socket , 关键在于这个 SOCK_DGRAM */
  29.     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
  30.         perror("socket");
  31.         exit(errno);
  32.     } else
  33.         printf("create socket.\n\r");

  34.     /* 设置对方地址和端口信息 */
  35.     s_addr.sin_family = AF_INET;
  36.     if (argv[2])
  37.         s_addr.sin_port = htons(atoi(argv[2]));
  38.     else
  39.         s_addr.sin_port = htons(7838);
  40.     if (argv[1])
  41.         s_addr.sin_addr.s_addr = inet_addr(argv[1]);
  42.     else {
  43.         printf("消息必须有一个接收者!\n");
  44.         exit(0);
  45.     }

  46.     /* 发送UDP消息 */
  47.     addr_len = sizeof(s_addr);
  48.     strcpy(buff, "hello i'm here");
  49.     len = sendto(sock, buff, strlen(buff), 0,
  50.                  (struct sockaddr *) &s_addr, addr_len);
  51.     if (len < 0) {
  52.         printf("\n\rsend error.\n\r");
  53.         return 3;
  54.     }

  55.     printf("send success.\n\r");
  56.     return 0;
  57. }
复制代码

编译程序用下列命令:
gcc -Wall simple-udpserver.c -o server
gcc -Wall simple-udpclient.c -o client

运行程序用下列命令:
./server 127.0.0.1 7838
./client 127.0.0.1 7838

作者: zhoulifa    时间: 2007-02-04 14:02
标题: Linux网络编程一步一步学-UDP方式广播通讯
和前一篇文章<Linux网络编程一步一步学-UDP方式点对点通讯>一样,只是在客户端源代码里加一行设置socket属性为广播方式即可。
需要加的一句是:
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));

源代码变成下面的:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <netinet/in.h>
  5. #include <sys/socket.h>
  6. #include <errno.h>
  7. #include <stdlib.h>
  8. #include <arpa/inet.h>

  9. /*********************************************************************
  10. *filename: broadc-udpclient.c
  11. *purpose: 基本编程步骤说明,演示了UDP编程的广播客户端编程步骤
  12. *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  13. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  14. *date time:2007-01-24 21:30:00
  15. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  16. * 但请遵循GPL
  17. *Thanks to: Google.com
  18. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  19. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  20. *********************************************************************/
  21. int main(int argc, char **argv)
  22. {
  23.     struct sockaddr_in s_addr;
  24.     int sock;
  25.     int addr_len;
  26.     int len;
  27.     char buff[128];
  28.     int yes;

  29.     /* 创建 socket */
  30.     if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
  31.         perror("socket");
  32.         exit(errno);
  33.     } else
  34.         printf("create socket.\n\r");

  35.     /* 设置通讯方式对广播,即本程序发送的一个消息,网络上所有主机均可以收到 */
  36.     yes = 1;
  37.     setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes));
  38.     /* 唯一变化就是这一点了 */

  39.     /* 设置对方地址和端口信息 */
  40.     s_addr.sin_family = AF_INET;
  41.     if (argv[2])
  42.         s_addr.sin_port = htons(atoi(argv[2]));
  43.     else
  44.         s_addr.sin_port = htons(7838);
  45.     if (argv[1])
  46.         s_addr.sin_addr.s_addr = inet_addr(argv[1]);
  47.     else {
  48.         printf("消息必须有一个接收者!\n");
  49.         exit(0);
  50.     }

  51.     /* 发送UDP消息 */
  52.     addr_len = sizeof(s_addr);
  53.     strcpy(buff, "hello i'm here");
  54.     len = sendto(sock, buff, strlen(buff), 0,
  55.                  (struct sockaddr *) &s_addr, addr_len);
  56.     if (len < 0) {
  57.         printf("\n\rsend error.\n\r");
  58.         return 3;
  59.     }

  60.     printf("send success.\n\r");
  61.     return 0;
  62. }
复制代码

编译这个程序用下列命令:
gcc -Wall broadc-udpclient.c -o client

运行程序用下列命令:
./client 192.168.0.255 7838

就会往192.168.0网络内所有主机发消息。
其它主机如果运行了服务端:
./server 自己的IP地址 7838
则都会收到上述客户端发的消息了。
作者: zhoulifa    时间: 2007-02-04 14:09
标题: Linux网络编程一步一步学-网络广播、组播与单播
这里以下图所示的网络为基础来说明网络通讯的各种方式:


什么是广播?
以前面的文章<Linux网络编程一步一步学-UDP方式广播通讯>为例:就是用下列命令在上图所示的主机192.168.100.xa上运行客户端程序:
./client 192.168.100.255 7838
则上图所示网络上的所有主机,只要其IP地址192.168.100.*与网络掩码(比如255.255.255.0)运算得到的子网(比如 192.168.100.0)与192.168.100.xa主机所在的子网是一样的,都会在自己的7838端口收到192.168.xa主机发出来的 UDP消息。消息会被复制并发到每个主机的网卡上去,网卡收到消息后提交给操作系统去处理,操作系统发现有程序在7838端口接收UDP数据则把消息转给相应的程序去处理,如果没有程序接收来自7838端口的UDP消息,则操作系统丢弃该消息。

/*********************************************************************
*filename: Linux网络编程一步一步学-网络广播、组播与单播
*purpose: 说明网络广播、组播与单播
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-25 13:10:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/
广播的缺点:不管主机是否有程序接收广播消息,广播消息一定会被网卡收到并提交给操作系统去处理,所以会造成网络上流量增大,对不接收广播消息的主机造成一定的负担。

什么是单播?
以前面的文章<Linux网络编程一步一步学-客户端和服务器端互相收发消息>为例:就是用下列命令在上图所示的主机192.168.100.xa上运行客户端程序:
./client 192.168.100.xf 7838
则消息只会从192.168.100.xa主机发到192.168.100.xf主机上,192.168.100.xf主机的网卡收到消息后转给操作系统去处理,操作系统再把此消息转给相应程序去处理,如果没有程序处理就丢弃该包。

TCP方式和UDP方式都可以实现单播。也是大多数情况下网络通讯所采取的方式。

什么是组播?
以后面的文章<Linux网络编程一步一步学-UDP组播>为例:就是用下列命令在上图所示的主机192.168.100.xa上运行客户端程序:
./mcastclient 230.1.1.1 7838
则消息只会从192.168.100.xa主机发到加入了组230.1.1.1的主机的7838端口。象广播一样,组播消息一样会被复制发到网络所有主机的网卡上,但只有宣布加入230.1.1.1这个组的主机的网卡才会把数据提交给操作系统去处理。如果没有加入组,则网卡直接将数据丢弃。
要想接收组播消息的主机必须运行命令加入组,如下方式:
./mcastserver 230.1.1.1 7838

注意:按照RFC规定,组播地址范围是D类IP地址,即224.0.0.1-239.255.255.255。
组播IP地址不能用我们平时所有的C类IP地址。
作者: zhoulifa    时间: 2007-02-04 14:17
标题: Linux网络编程一步一步学-UDP组播
组播客户端代码如下:

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <arpa/inet.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>

  7. #define BUFLEN 255
  8. /*********************************************************************
  9. *filename: mcastclient.c
  10. *purpose: 演示组播编程的基本步骤,其实这就是一个基本的UDP客户端程序
  11. *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  12. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  13. *date time:2007-01-25 13:10:00
  14. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  15. * 但请遵循GPL
  16. *Thanks to: Google.com
  17. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  18. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  19. *********************************************************************/
  20. int main(int argc, char **argv)
  21. {
  22.     struct sockaddr_in peeraddr, myaddr;

  23.     int sockfd;
  24.     char recmsg[BUFLEN + 1];
  25.     unsigned int socklen;

  26. /* 创建 socket 用于UDP通讯 */
  27.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  28.     if (sockfd < 0) {
  29.       printf("socket creating error\n");
  30.       exit(1);
  31.     }
  32.     socklen = sizeof(struct sockaddr_in);

  33.     /* 设置对方的端口和IP信息 */
  34.     memset(&peeraddr, 0, socklen);
  35.     peeraddr.sin_family = AF_INET;
  36.     if (argv[2])
  37.       peeraddr.sin_port = htons(atoi(argv[2]));
  38.     else
  39.       peeraddr.sin_port = htons(7838);
  40.     if (argv[1]) {
  41.         /* 注意这里设置的对方地址是指组播地址224.0.0.0-239.255.255.255,而不是对方的实际IP地址 */
  42.       if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0) {
  43.           printf("wrong group address!\n");
  44.           exit(0);
  45.       }
  46.     } else {
  47.       printf("no group address!\n");
  48.       exit(0);
  49.     }

  50.     /* 设置自己的端口和IP信息 */
  51.     memset(&myaddr, 0, socklen);
  52.     myaddr.sin_family = AF_INET;
  53.     if (argv[4])
  54.       myaddr.sin_port = htons(atoi(argv[4]));
  55.     else
  56.       myaddr.sin_port = htons(23456);

  57.     if (argv[3]) {
  58.       if (inet_pton(AF_INET, argv[3], &myaddr.sin_addr) <= 0) {
  59.           printf("self ip address error!\n");
  60.           exit(0);
  61.       }
  62.     } else
  63.       myaddr.sin_addr.s_addr = INADDR_ANY;

  64.     /* 绑定自己的端口和IP信息到socket上 */
  65.     if (bind
  66.       (sockfd, (struct sockaddr *) &myaddr,
  67.        sizeof(struct sockaddr_in)) == -1) {
  68.       printf("Bind error\n");
  69.       exit(0);
  70.     }

  71.     /* 循环接受用户输入的消息发送组播消息 */
  72.     for (;;) {
  73.         /* 接受用户输入 */
  74.       bzero(recmsg, BUFLEN + 1);
  75.       if (fgets(recmsg, BUFLEN, stdin) == (char *) EOF)
  76.           exit(0);
  77.         /* 发送消息 */
  78.       if (sendto
  79.           (sockfd, recmsg, strlen(recmsg), 0,
  80.            (struct sockaddr *) &peeraddr,
  81.            sizeof(struct sockaddr_in)) < 0) {
  82.           printf("sendto error!\n");
  83.           exit(3);
  84.       }
  85.       printf("'%s' send ok\n", recmsg);
  86.     }
  87. }
复制代码

组播服务器端程序源代码为:

  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <arpa/inet.h>
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <netdb.h>
  8. #include <errno.h>

  9. #define BUFLEN 255
  10. /*********************************************************************
  11. *filename: mcastserver.c
  12. *purpose: 演示组播编程的基本步骤,组播服务器端,关键在于加入组
  13. *tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  14. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  15. *date time:2007-01-25 13:20:00
  16. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  17. * 但请遵循GPL
  18. *Thanks to: Google.com
  19. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  20. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  21. *********************************************************************/
  22. int main(int argc, char **argv)
  23. {
  24.     struct sockaddr_in peeraddr;
  25.     struct in_addr ia;
  26.     int sockfd;
  27.     char recmsg[BUFLEN + 1];
  28.     unsigned int socklen, n;
  29.     struct hostent *group;
  30.     struct ip_mreq mreq;

  31.     /* 创建 socket 用于UDP通讯 */
  32.     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  33.     if (sockfd < 0) {
  34.         printf("socket creating err in udptalk\n");
  35.         exit(1);
  36.     }

  37.     /* 设置要加入组播的地址 */
  38.     bzero(&mreq, sizeof(struct ip_mreq));
  39.     if (argv[1]) {
  40.         if ((group = gethostbyname(argv[1])) == (struct hostent *) 0) {
  41.             perror("gethostbyname");
  42.             exit(errno);
  43.         }
  44.     } else {
  45.         printf
  46.             ("you should give me a group address, 224.0.0.0-239.255.255.255\n");
  47.         exit(errno);
  48.     }

  49.     bcopy((void *) group->h_addr, (void *) &ia, group->h_length);
  50.     /* 设置组地址 */
  51.     bcopy(&ia, &mreq.imr_multiaddr.s_addr, sizeof(struct in_addr));

  52.     /* 设置发送组播消息的源主机的地址信息 */
  53.     mreq.imr_interface.s_addr = htonl(INADDR_ANY);

  54.     /* 把本机加入组播地址,即本机网卡作为组播成员,只有加入组才能收到组播消息 */
  55.     if (setsockopt
  56.         (sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
  57.          sizeof(struct ip_mreq)) == -1) {
  58.         perror("setsockopt");
  59.         exit(-1);
  60.     }

  61.     socklen = sizeof(struct sockaddr_in);
  62.     memset(&peeraddr, 0, socklen);
  63.     peeraddr.sin_family = AF_INET;
  64.     if (argv[2])
  65.         peeraddr.sin_port = htons(atoi(argv[2]));
  66.     else
  67.         peeraddr.sin_port = htons(7838);
  68.     if (argv[1]) {
  69.         if (inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0) {
  70.             printf("Wrong dest IP address!\n");
  71.             exit(0);
  72.         }
  73.     } else {
  74.         printf("no group address given, 224.0.0.0-239.255.255.255\n");
  75.         exit(errno);
  76.     }

  77.     /* 绑定自己的端口和IP信息到socket上 */
  78.     if (bind
  79.         (sockfd, (struct sockaddr *) &peeraddr,
  80.          sizeof(struct sockaddr_in)) == -1) {
  81.         printf("Bind error\n");
  82.         exit(0);
  83.     }

  84.     /* 循环接收网络上来的组播消息 */
  85.     for (;;) {
  86.         bzero(recmsg, BUFLEN + 1);
  87.         n = recvfrom(sockfd, recmsg, BUFLEN, 0,
  88.                      (struct sockaddr *) &peeraddr, &socklen);
  89.         if (n < 0) {
  90.             printf("recvfrom err in udptalk!\n");
  91.             exit(4);
  92.         } else {
  93.             /* 成功接收到数据报 */
  94.             recmsg[n] = 0;
  95.             printf("peer:%s", recmsg);
  96.         }
  97.     }
  98. }
复制代码

编译程序用下列命令:
gcc -Wall mcastclient.c -o mcastclient
gcc -Wall mcastserver.c -o mcastserver

运行程序用如下命令:
./mcastserver 230.1.1.1 7838

客户端程序运行命令为:
./mcastclient 230.1.1.1 7838 192.168.100.1 12345

注意组播地址的不同!
不是实际的IP地址,我们的IP地址是C类IP,组播用的D类IP,而且是虚拟出来的,未绑定在任何实际网卡设备上。
作者: zhoulifa    时间: 2007-02-04 14:33
标题: Linux网络编程一步一步学-同步聊天程序
服务器端源代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>

  11. #define MAXBUF 1024
  12. /************关于本文档********************************************
  13. *filename: sync-server.c
  14. *purpose: 演示网络同步通讯,这是服务器端程序
  15. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  16. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  17. *date time:2007-01-25 20:26
  18. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  19. * 但请遵循GPL
  20. *Thanks to: Google.com
  21. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  22. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  23. *********************************************************************/

  24. int main(int argc, char **argv)
  25. {
  26.     int sockfd, new_fd;
  27.     socklen_t len;
  28.     struct sockaddr_in my_addr, their_addr;
  29.     unsigned int myport, lisnum;
  30.     char buf[MAXBUF + 1];

  31.     if (argv[1])
  32.       myport = atoi(argv[1]);
  33.     else
  34.       myport = 7838;

  35.     if (argv[2])
  36.       lisnum = atoi(argv[2]);
  37.     else
  38.       lisnum = 2;

  39.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  40.       perror("socket");
  41.       exit(1);
  42.     }

  43.     bzero(&my_addr, sizeof(my_addr));
  44.     my_addr.sin_family = PF_INET;
  45.     my_addr.sin_port = htons(myport);
  46.     if (argv[3])
  47.       my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  48.     else
  49.       my_addr.sin_addr.s_addr = INADDR_ANY;

  50.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  51.       == -1) {
  52.       perror("bind");
  53.       exit(1);
  54.     }

  55.     if (listen(sockfd, lisnum) == -1) {
  56.       perror("listen");
  57.       exit(1);
  58.     }

  59.     while (1) {
  60.       printf
  61.           ("\n----等待新的连接到来开始新一轮聊天……\n");
  62.       len = sizeof(struct sockaddr);
  63.       if ((new_fd =
  64.            accept(sockfd, (struct sockaddr *) &their_addr,
  65.                   &len)) == -1) {
  66.           perror("accept");
  67.           exit(errno);
  68.       } else
  69.           printf("server: got connection from %s, port %d, socket %d\n",
  70.                  inet_ntoa(their_addr.sin_addr),
  71.                  ntohs(their_addr.sin_port), new_fd);

  72.       /* 开始处理每个新连接上的数据收发 */
  73.       while (1) {
  74.           bzero(buf, MAXBUF + 1);
  75.           printf("请输入要发送给对方的消息:");
  76.           fgets(buf, MAXBUF, stdin);
  77.           if (!strncasecmp(buf, "quit", 4)) {
  78.               printf("自己请求终止聊天!\n");
  79.               break;
  80.           }
  81.           len = send(new_fd, buf, strlen(buf) - 1, 0);
  82.           if (len > 0)
  83.               printf
  84.                   ("消息:%s\t发送成功,共发送了%d个字节!\n",
  85.                    buf, len);
  86.           else {
  87.               printf
  88.                   ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  89.                    buf, errno, strerror(errno));
  90.               break;
  91.           }
  92.           bzero(buf, MAXBUF + 1);
  93.           /* 接收客户端的消息 */
  94.           len = recv(new_fd, buf, MAXBUF, 0);
  95.           if (len > 0)
  96.               printf
  97.                   ("接收消息成功:'%s',共%d个字节的数据\n",
  98.                    buf, len);
  99.           else {
  100.               if (len < 0)
  101.                   printf
  102.                       ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  103.                        errno, strerror(errno));
  104.               else
  105.                   printf("对方退出了,聊天终止\n");
  106.               break;
  107.           }
  108.       }
  109.       close(new_fd);
  110.       /* 处理每个新连接上的数据收发结束 */
  111.     }

  112.     close(sockfd);
  113.     return 0;
  114. }
复制代码

客户端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>

  10. #define MAXBUF 1024
  11. /************关于本文档********************************************
  12. // *filename: sync-client.c
  13. *purpose: 演示网络同步通讯,这是客户端程序
  14. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  15. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  16. *date time:2007-01-25 20:32
  17. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  18. * 但请遵循GPL
  19. *Thanks to: Google.com
  20. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  21. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  22. *********************************************************************/
  23. int main(int argc, char **argv)
  24. {
  25.     int sockfd, len;
  26.     struct sockaddr_in dest;
  27.     char buffer[MAXBUF + 1];

  28.     if (argc != 3) {
  29.         printf
  30.             ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  31.              argv[0], argv[0]);
  32.         exit(0);
  33.     }
  34.     /* 创建一个 socket 用于 tcp 通信 */
  35.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  36.         perror("Socket");
  37.         exit(errno);
  38.     }
  39.     printf("socket created\n");

  40.     /* 初始化服务器端(对方)的地址和端口信息 */
  41.     bzero(&dest, sizeof(dest));
  42.     dest.sin_family = AF_INET;
  43.     dest.sin_port = htons(atoi(argv[2]));
  44.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  45.         perror(argv[1]);
  46.         exit(errno);
  47.     }
  48.     printf("address created\n");

  49.     /* 连接服务器 */
  50.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  51.         perror("Connect ");
  52.         exit(errno);
  53.     }
  54.     printf("server connected\n");

  55.     while (1) {
  56.         /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  57.         bzero(buffer, MAXBUF + 1);
  58.         /* 接收服务器来的消息 */
  59.         len = recv(sockfd, buffer, MAXBUF, 0);
  60.         if (len > 0)
  61.             printf("接收消息成功:'%s',共%d个字节的数据\n",
  62.                    buffer, len);
  63.         else {
  64.             if (len < 0)
  65.                 printf
  66.                     ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  67.                      errno, strerror(errno));
  68.             else
  69.                 printf("对方退出了,聊天终止!\n");
  70.             break;
  71.         }
  72.         bzero(buffer, MAXBUF + 1);
  73.         printf("请输入要发送给对方的消息:");
  74.         fgets(buffer, MAXBUF, stdin);
  75.         if (!strncasecmp(buffer, "quit", 4)) {
  76.             printf("自己请求终止聊天!\n");
  77.             break;
  78.         }
  79.         /* 发消息给服务器 */
  80.         len = send(sockfd, buffer, strlen(buffer) - 1, 0);
  81.         if (len < 0) {
  82.             printf
  83.                 ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  84.                  buffer, errno, strerror(errno));
  85.             break;
  86.         } else
  87.             printf
  88.                 ("消息:%s\t发送成功,共发送了%d个字节!\n",
  89.                  buffer, len);
  90.     }
  91.     /* 关闭连接 */
  92.     close(sockfd);
  93.     return 0;
  94. }
复制代码

编译程序用下列命令:
gcc -Wall sync-server.c -o server
gcc -Wall sync-client.c -o client

分别运行这两个程序:
./server 7838 1
./client 127.0.0.1 7838

同步聊天过程如下:
1、服务端程序开启
2、客户端程序开启(即客户端连接服务器)
3、服务器端用户输入消息并发送给客户端
4、客户端收到消息,客户端用户输入消息并发送给服务器端
5、服务器端收到消息,服务器端用户输入消息并发送给客户端
6、任何一方退出当前聊天即终止一次聊天过程
7、服务器端继续等待下一个人连接上来开始新的聊天过程

同步通讯,要求任何一方都必须按照顺序操作,一收一发或一发一收
作者: zhoulifa    时间: 2007-02-04 14:40
标题: Linux网络编程一步一步学-异步通讯聊天程序select
什么是异步通讯?
就是通讯任意一方可以任意发送消息,有消息来到时会收到系统提示去接收消息。

这里要用到select函数。使用步骤如下:
1、设置一个集合变量,用来存放所有要判断的句柄(file descriptors:即我们建立的每个socket、用open打开的每个文件等)
2、把需要判断的句柄加入到集合里
3、设置判断时间
4、开始等待,即select
5、如果在设定的时间内有任何句柄状态变化了就马上返回,并把句柄设置到集合里

服务器端源代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>
  11. #include <sys/time.h>
  12. #include <sys/types.h>

  13. #define MAXBUF 1024
  14. /************关于本文档********************************************
  15. *filename: async-server.c
  16. *purpose: 演示网络异步通讯,这是服务器端程序
  17. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  18. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  19. *date time:2007-01-25 21:22
  20. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  21. * 但请遵循GPL
  22. *Thanks to: Google.com
  23. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  24. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  25. *********************************************************************/

  26. int main(int argc, char **argv)
  27. {
  28.     int sockfd, new_fd;
  29.     socklen_t len;
  30.     struct sockaddr_in my_addr, their_addr;
  31.     unsigned int myport, lisnum;
  32.     char buf[MAXBUF + 1];
  33.     fd_set rfds;
  34.     struct timeval tv;
  35.     int retval, maxfd = -1;

  36.     if (argv[1])
  37.         myport = atoi(argv[1]);
  38.     else
  39.         myport = 7838;

  40.     if (argv[2])
  41.         lisnum = atoi(argv[2]);
  42.     else
  43.         lisnum = 2;

  44.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  45.         perror("socket");
  46.         exit(1);
  47.     }

  48.     bzero(&my_addr, sizeof(my_addr));
  49.     my_addr.sin_family = PF_INET;
  50.     my_addr.sin_port = htons(myport);
  51.     if (argv[3])
  52.         my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  53.     else
  54.         my_addr.sin_addr.s_addr = INADDR_ANY;

  55.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  56.         == -1) {
  57.         perror("bind");
  58.         exit(1);
  59.     }

  60.     if (listen(sockfd, lisnum) == -1) {
  61.         perror("listen");
  62.         exit(1);
  63.     }

  64.     while (1) {
  65.         printf
  66.             ("\n----等待新的连接到来开始新一轮聊天……\n");
  67.         len = sizeof(struct sockaddr);
  68.         if ((new_fd =
  69.              accept(sockfd, (struct sockaddr *) &their_addr,
  70.                     &len)) == -1) {
  71.             perror("accept");
  72.             exit(errno);
  73.         } else
  74.             printf("server: got connection from %s, port %d, socket %d\n",
  75.                    inet_ntoa(their_addr.sin_addr),
  76.                    ntohs(their_addr.sin_port), new_fd);

  77.         /* 开始处理每个新连接上的数据收发 */
  78.         printf
  79.             ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
  80.         while (1) {
  81.             /* 把集合清空 */
  82.             FD_ZERO(&rfds);
  83.             /* 把标准输入句柄0加入到集合中 */
  84.             FD_SET(0, &rfds);
  85.             maxfd = 0;
  86.             /* 把当前连接句柄new_fd加入到集合中 */
  87.             FD_SET(new_fd, &rfds);
  88.             if (new_fd > maxfd)
  89.                 maxfd = new_fd;
  90.             /* 设置最大等待时间 */
  91.             tv.tv_sec = 1;
  92.             tv.tv_usec = 0;
  93.             /* 开始等待 */
  94.             retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
  95.             if (retval == -1) {
  96.                 printf("将退出,select出错! %s", strerror(errno));
  97.                 break;
  98.             } else if (retval == 0) {
  99.                 /* printf
  100.                    ("没有任何消息到来,用户也没有按键,继续等待……\n"); */
  101.                 continue;
  102.             } else {
  103.                 if (FD_ISSET(0, &rfds)) {
  104.                     /* 用户按键了,则读取用户输入的内容发送出去 */
  105.                     bzero(buf, MAXBUF + 1);
  106.                     fgets(buf, MAXBUF, stdin);
  107.                     if (!strncasecmp(buf, "quit", 4)) {
  108.                         printf("自己请求终止聊天!\n");
  109.                         break;
  110.                     }
  111.                     len = send(new_fd, buf, strlen(buf) - 1, 0);
  112.                     if (len > 0)
  113.                         printf
  114.                             ("消息:%s\t发送成功,共发送了%d个字节!\n",
  115.                              buf, len);
  116.                     else {
  117.                         printf
  118.                             ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  119.                              buf, errno, strerror(errno));
  120.                         break;
  121.                     }
  122.                 }
  123.                 if (FD_ISSET(new_fd, &rfds)) {
  124.                     /* 当前连接的socket上有消息到来则接收对方发过来的消息并显示 */
  125.                     bzero(buf, MAXBUF + 1);
  126.                     /* 接收客户端的消息 */
  127.                     len = recv(new_fd, buf, MAXBUF, 0);
  128.                     if (len > 0)
  129.                         printf
  130.                             ("接收消息成功:'%s',共%d个字节的数据\n",
  131.                              buf, len);
  132.                     else {
  133.                         if (len < 0)
  134.                             printf
  135.                                 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  136.                                  errno, strerror(errno));
  137.                         else
  138.                             printf("对方退出了,聊天终止\n");
  139.                         break;
  140.                     }
  141.                 }
  142.             }
  143.         }
  144.         close(new_fd);
  145.         /* 处理每个新连接上的数据收发结束 */
  146.         printf("还要和其它连接聊天吗?(no->退出)");
  147.         fflush(stdout);
  148.         bzero(buf, MAXBUF + 1);
  149.         fgets(buf, MAXBUF, stdin);
  150.         if (!strncasecmp(buf, "no", 2)) {
  151.             printf("终止聊天!\n");
  152.             break;
  153.         }
  154.     }

  155.     close(sockfd);
  156.     return 0;
  157. }
复制代码

客户端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>
  10. #include <sys/time.h>
  11. #include <sys/types.h>

  12. #define MAXBUF 1024
  13. /************关于本文档********************************************
  14. // *filename: ssync-client.c
  15. *purpose: 演示网络异步通讯,这是客户端程序
  16. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  17. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  18. *date time:2007-01-25 21:32
  19. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  20. * 但请遵循GPL
  21. *Thanks to: Google.com
  22. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  23. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  24. *********************************************************************/
  25. int main(int argc, char **argv)
  26. {
  27.     int sockfd, len;
  28.     struct sockaddr_in dest;
  29.     char buffer[MAXBUF + 1];
  30.     fd_set rfds;
  31.     struct timeval tv;
  32.     int retval, maxfd = -1;

  33.     if (argc != 3) {
  34.         printf
  35.             ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  36.              argv[0], argv[0]);
  37.         exit(0);
  38.     }
  39.     /* 创建一个 socket 用于 tcp 通信 */
  40.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  41.         perror("Socket");
  42.         exit(errno);
  43.     }

  44.     /* 初始化服务器端(对方)的地址和端口信息 */
  45.     bzero(&dest, sizeof(dest));
  46.     dest.sin_family = AF_INET;
  47.     dest.sin_port = htons(atoi(argv[2]));
  48.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  49.         perror(argv[1]);
  50.         exit(errno);
  51.     }

  52.     /* 连接服务器 */
  53.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  54.         perror("Connect ");
  55.         exit(errno);
  56.     }

  57.     printf
  58.         ("\n准备就绪,可以开始聊天了……直接输入消息回车即可发信息给对方\n");
  59.     while (1) {
  60.         /* 把集合清空 */
  61.         FD_ZERO(&rfds);
  62.         /* 把标准输入句柄0加入到集合中 */
  63.         FD_SET(0, &rfds);
  64.         maxfd = 0;
  65.         /* 把当前连接句柄sockfd加入到集合中 */
  66.         FD_SET(sockfd, &rfds);
  67.         if (sockfd > maxfd)
  68.             maxfd = sockfd;
  69.         /* 设置最大等待时间 */
  70.         tv.tv_sec = 1;
  71.         tv.tv_usec = 0;
  72.         /* 开始等待 */
  73.         retval = select(maxfd + 1, &rfds, NULL, NULL, &tv);
  74.         if (retval == -1) {
  75.             printf("将退出,select出错! %s", strerror(errno));
  76.             break;
  77.         } else if (retval == 0) {
  78.             /* printf
  79.                ("没有任何消息到来,用户也没有按键,继续等待……\n"); */
  80.             continue;
  81.         } else {
  82.             if (FD_ISSET(sockfd, &rfds)) {
  83.                 /* 连接的socket上有消息到来则接收对方发过来的消息并显示 */
  84.                 bzero(buffer, MAXBUF + 1);
  85.                 /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  86.                 len = recv(sockfd, buffer, MAXBUF, 0);
  87.                 if (len > 0)
  88.                     printf
  89.                         ("接收消息成功:'%s',共%d个字节的数据\n",
  90.                          buffer, len);
  91.                 else {
  92.                     if (len < 0)
  93.                         printf
  94.                             ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  95.                              errno, strerror(errno));
  96.                     else
  97.                         printf("对方退出了,聊天终止!\n");
  98.                     break;
  99.                 }
  100.             }
  101.             if (FD_ISSET(0, &rfds)) {
  102.                 /* 用户按键了,则读取用户输入的内容发送出去 */
  103.                 bzero(buffer, MAXBUF + 1);
  104.                 fgets(buffer, MAXBUF, stdin);
  105.                 if (!strncasecmp(buffer, "quit", 4)) {
  106.                     printf("自己请求终止聊天!\n");
  107.                     break;
  108.                 }
  109.                 /* 发消息给服务器 */
  110.                 len = send(sockfd, buffer, strlen(buffer) - 1, 0);
  111.                 if (len < 0) {
  112.                     printf
  113.                         ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  114.                          buffer, errno, strerror(errno));
  115.                     break;
  116.                 } else
  117.                     printf
  118.                         ("消息:%s\t发送成功,共发送了%d个字节!\n",
  119.                          buffer, len);
  120.             }
  121.         }
  122.     }
  123.     /* 关闭连接 */
  124.     close(sockfd);
  125.     return 0;
  126. }
复制代码

编译用如下命令:
gcc -Wall async-server.c -o server
gcc -Wall async-client.c -o client

运行用如下命令:
./server 7838 1
./client 127.0.0.1 7838

作者: zhoulifa    时间: 2007-02-04 14:51
标题: Linux网络编程一步一步学-编写一个HTTP协议的目录浏览和文件下载服务器
服务器源代码如下:

  1. #include <stdarg.h>
  2. #include <errno.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. #include <time.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <dirent.h>
  11. #include <errno.h>
  12. #include <netinet/in.h>
  13. #include <sys/socket.h>
  14. #include <resolv.h>
  15. #include <arpa/inet.h>
  16. #include <stdlib.h>
  17. #include <signal.h>
  18. #include <getopt.h>

  19. #define DEFAULTIP "127.0.0.1"
  20. #define DEFAULTPORT "80"
  21. #define DEFAULTBACK "10"
  22. #define DEFAULTDIR "/home"
  23. #define DEFAULTLOG "/tmp/das-server.log"

  24. void prterrmsg(char *msg);
  25. #define prterrmsg(msg)        { perror(msg); abort(); }
  26. void wrterrmsg(char *msg);
  27. #define wrterrmsg(msg)        { fputs(msg, logfp); fputs(strerror(errno), logfp);fflush(logfp); abort(); }

  28. void prtinfomsg(char *msg);
  29. #define prtinfomsg(msg)        { fputs(msg, stdout);  }
  30. void wrtinfomsg(char *msg);
  31. #define wrtinfomsg(msg)        {  fputs(msg, logfp); fflush(logfp);}

  32. #define MAXBUF        1024

  33. char buffer[MAXBUF + 1];
  34. char *host = 0;
  35. char *port = 0;
  36. char *back = 0;
  37. char *dirroot = 0;
  38. char *logdir = 0;
  39. unsigned char daemon_y_n = 0;
  40. FILE *logfp;

  41. #define MAXPATH        150

  42. /*----------------------------------------
  43. *--- dir_up - 查找dirpath所指目录的上一级目录
  44. *----------------------------------------
  45. */
  46. char *dir_up(char *dirpath)
  47. {
  48.     static char Path[MAXPATH];
  49.     int len;

  50.     strcpy(Path, dirpath);
  51.     len = strlen(Path);
  52.     if (len > 1 && Path[len - 1] == '/')
  53.         len--;
  54.     while (Path[len - 1] != '/' && len > 1)
  55.         len--;
  56.     Path[len] = 0;
  57.     return Path;
  58. }

  59. /*------------------------------------------------------
  60. *--- AllocateMemory - 分配空间并把d所指的内容复制
  61. *------------------------------------------------------
  62. */
  63. void AllocateMemory(char **s, int l, char *d)
  64. {
  65.     *s = malloc(l + 1);
  66.     bzero(*s, l + 1);
  67.     memcpy(*s, d, l);
  68. }
  69. /************关于本文档********************************************
  70. *filename: das-server.c
  71. *purpose: 这是在Linux下用C语言写的目录访问服务器,支持目录浏览和文件下载
  72. *wrote by: zhoulifa([email]zhoulifa@163.com[/email]) 周立发([url]http://zhoulifa.bokee.com)[/url]
  73. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  74. *date time:2007-01-26 19:32
  75. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  76. * 但请遵循GPL
  77. *Thanks to: Google.com
  78. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  79. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  80. *********************************************************************/
  81. /*------------------------------------------------------
  82. *--- GiveResponse - 把Path所指的内容发送到client_sock去
  83. *-------------------如果Path是一个目录,则列出目录内容
  84. *-------------------如果Path是一个文件,则下载文件
  85. *------------------------------------------------------
  86. */
  87. void GiveResponse(FILE * client_sock, char *Path)
  88. {
  89.     struct dirent *dirent;
  90.     struct stat info;
  91.     char Filename[MAXPATH];
  92.     DIR *dir;
  93.     int fd, len, ret;
  94.     char *p, *realPath, *realFilename, *nport;

  95.     /* 获得实际工作目录或文件 */
  96.     len = strlen(dirroot) + strlen(Path) + 1;
  97.     realPath = malloc(len + 1);
  98.     bzero(realPath, len + 1);
  99.     sprintf(realPath, "%s/%s", dirroot, Path);

  100.     /* 获得实际工作端口 */
  101.     len = strlen(port) + 1;
  102.     nport = malloc(len + 1);
  103.     bzero(nport, len + 1);
  104.     sprintf(nport, ":%s", port);

  105.     /* 获得实际工作目录或文件的信息以判断是文件还是目录 */
  106.     if (stat(realPath, &info)) {
  107.         fprintf(client_sock,
  108.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: close\r\n\r\n<html><head><title>%d - %s</title></head>"
  109.                 "<body><font size=+4>Linux 下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
  110.                 "<table border cols=3 width=\"100%%\">", errno,
  111.                 strerror(errno));
  112.         fprintf(client_sock,
  113.                 "</table><font color=\"CC0000\" size=+2>请向管理员咨询为何出现如下错误提示:\n%s %s</font></body></html>",
  114.                 Path, strerror(errno));
  115.         goto out;
  116.     }
  117.     /* 处理浏览文件请求,即下载文件 */
  118.     if (S_ISREG(info.st_mode)) {
  119.         fd = open(realPath, O_RDONLY);
  120.         len = lseek(fd, 0, SEEK_END);
  121.         p = (char *) malloc(len + 1);
  122.         bzero(p, len + 1);
  123.         lseek(fd, 0, SEEK_SET);
  124.         ret = read(fd, p, len);
  125.         close(fd);
  126.         fprintf(client_sock,
  127.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: keep-alive\r\nContent-type: application/*\r\nContent-Length:%d\r\n\r\n",
  128.                 len);
  129.         fwrite(p, len, 1, client_sock);
  130.         free(p);
  131.     } else if (S_ISDIR(info.st_mode)) {
  132.         /* 处理浏览目录请求 */
  133.         dir = opendir(realPath);
  134.         fprintf(client_sock,
  135.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: close\r\n\r\n<html><head><title>%s</title></head>"
  136.                 "<body><font size=+4>Linux 下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
  137.                 "<table border cols=3 width=\"100%%\">", Path);
  138.         fprintf(client_sock,
  139.                 "<caption><font size=+3>目录 %s</font></caption>\n",
  140.                 Path);
  141.         fprintf(client_sock,
  142.                 "<tr><td>名称</td><td>大小</td><td>修改时间</td></tr>\n");
  143.         if (dir == 0) {
  144.             fprintf(client_sock,
  145.                     "</table><font color=\"CC0000\" size=+2>%s</font></body></html>",
  146.                     strerror(errno));
  147.             return;
  148.         }
  149.         /* 读取目录里的所有内容 */
  150.         while ((dirent = readdir(dir)) != 0) {
  151.             if (strcmp(Path, "/") == 0)
  152.                 sprintf(Filename, "/%s", dirent->d_name);
  153.             else
  154.                 sprintf(Filename, "%s/%s", Path, dirent->d_name);
  155.             fprintf(client_sock, "<tr>");
  156.             len = strlen(dirroot) + strlen(Filename) + 1;
  157.             realFilename = malloc(len + 1);
  158.             bzero(realFilename, len + 1);
  159.             sprintf(realFilename, "%s/%s", dirroot, Filename);
  160.             if (stat(realFilename, &info) == 0) {
  161.                 if (strcmp(dirent->d_name, "..") == 0)
  162.                     fprintf(client_sock,
  163.                             "<td><a href=\"http://%s%s%s\">(parent)</a></td>",
  164.                             host, atoi(port) == 80 ? "" : nport,
  165.                             dir_up(Path));
  166.                 else
  167.                     fprintf(client_sock,
  168.                             "<td><a href=\"http://%s%s%s\">%s</a></td>",
  169.                             host, atoi(port) == 80 ? "" : nport, Filename,
  170.                             dirent->d_name);
  171.                 if (S_ISDIR(info.st_mode))
  172.                     fprintf(client_sock, "<td>目录</td>");
  173.                 else if (S_ISREG(info.st_mode))
  174.                     fprintf(client_sock, "<td>%d</td>", info.st_size);
  175.                 else if (S_ISLNK(info.st_mode))
  176.                     fprintf(client_sock, "<td>链接</td>");
  177.                 else if (S_ISCHR(info.st_mode))
  178.                     fprintf(client_sock, "<td>字符设备</td>");
  179.                 else if (S_ISBLK(info.st_mode))
  180.                     fprintf(client_sock, "<td>块设备</td>");
  181.                 else if (S_ISFIFO(info.st_mode))
  182.                     fprintf(client_sock, "<td>FIFO</td>");
  183.                 else if (S_ISSOCK(info.st_mode))
  184.                     fprintf(client_sock, "<td>Socket</td>");
  185.                 else
  186.                     fprintf(client_sock, "<td>(未知)</td>");
  187.                 fprintf(client_sock, "<td>%s</td>", ctime(&info.st_ctime));
  188.             }
  189.             fprintf(client_sock, "</tr>\n");
  190.             free(realFilename);
  191.         }
  192.         fprintf(client_sock, "</table></center></body></html>");
  193.     } else {
  194.         /* 既非常规文件又非目录,禁止访问 */
  195.         fprintf(client_sock,
  196.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: close\r\n\r\n<html><head><title>permission denied</title></head>"
  197.                 "<body><font size=+4>Linux 下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
  198.                 "<table border cols=3 width=\"100%%\">");
  199.         fprintf(client_sock,
  200.                 "</table><font color=\"CC0000\" size=+2>你访问的资源'%s'被禁止访问,请联系管理员解决!</font></body>< /html>",
  201.                 Path);
  202.     }
  203.   out:
  204.     free(realPath);
  205.     free(nport);
  206. }

  207. /*------------------------------------------------------
  208. *--- getoption - 分析取出程序的参数
  209. *------------------------------------------------------
  210. */
  211. void getoption(int argc, char **argv)
  212. {
  213.     int c, len;
  214.     char *p = 0;

  215.     opterr = 0;
  216.     while (1) {
  217.         int option_index = 0;
  218.         static struct option long_options[] = {
  219.             {"host", 1, 0, 0},
  220.             {"port", 1, 0, 0},
  221.             {"back", 1, 0, 0},
  222.             {"dir", 1, 0, 0},
  223.             {"log", 1, 0, 0},
  224.             {"daemon", 0, 0, 0},
  225.             {0, 0, 0, 0}
  226.         };
  227.         /* 本程序支持如一些参数:
  228.          * --host IP地址 或者 -H IP地址
  229.          * --port 端口 或者 -P 端口
  230.          * --back 监听数量 或者 -B 监听数量
  231.          * --dir 网站根目录 或者 -D 网站根目录
  232.          * --log 日志存放路径 或者 -L 日志存放路径
  233.          * --daemon 使程序进入后台运行模式
  234.          */
  235.         c = getopt_long(argc, argv, "H:P:B:D:L",
  236.                         long_options, &option_index);
  237.         if (c == -1 || c == '?')
  238.             break;

  239.         if(optarg)        len = strlen(optarg);
  240.         else        len = 0;

  241.         if ((!c && !(strcasecmp(long_options[option_index].name, "host")))
  242.             || c == 'H')
  243.             p = host = malloc(len + 1);
  244.         else if ((!c
  245.                   &&
  246.                   !(strcasecmp(long_options[option_index].name, "port")))
  247.                  || c == 'P')
  248.             p = port = malloc(len + 1);
  249.         else if ((!c
  250.                   &&
  251.                   !(strcasecmp(long_options[option_index].name, "back")))
  252.                  || c == 'B')
  253.             p = back = malloc(len + 1);
  254.         else if ((!c
  255.                   && !(strcasecmp(long_options[option_index].name, "dir")))
  256.                  || c == 'D')
  257.             p = dirroot = malloc(len + 1);
  258.         else if ((!c
  259.                   && !(strcasecmp(long_options[option_index].name, "log")))
  260.                  || c == 'L')
  261.             p = logdir = malloc(len + 1);
  262.         else if ((!c
  263.                   &&
  264.                   !(strcasecmp
  265.                     (long_options[option_index].name, "daemon")))) {
  266.             daemon_y_n = 1;
  267.             continue;
  268.         }
  269.         else
  270.             break;
  271.         bzero(p, len + 1);
  272.         memcpy(p, optarg, len);
  273.     }
  274. }

  275. int main(int argc, char **argv)
  276. {
  277.     struct sockaddr_in addr;
  278.     int sock_fd, addrlen;

  279.     /* 获得程序工作的参数,如 IP 、端口、监听数、网页根目录、目录存放位置等 */
  280.     getoption(argc, argv);

  281.     if (!host) {
  282.         addrlen = strlen(DEFAULTIP);
  283.         AllocateMemory(&host, addrlen, DEFAULTIP);
  284.     }
  285.     if (!port) {
  286.         addrlen = strlen(DEFAULTPORT);
  287.         AllocateMemory(&port, addrlen, DEFAULTPORT);
  288.     }
  289.     if (!back) {
  290.         addrlen = strlen(DEFAULTBACK);
  291.         AllocateMemory(&back, addrlen, DEFAULTBACK);
  292.     }
  293.     if (!dirroot) {
  294.         addrlen = strlen(DEFAULTDIR);
  295.         AllocateMemory(&dirroot, addrlen, DEFAULTDIR);
  296.     }
  297.     if (!logdir) {
  298.         addrlen = strlen(DEFAULTLOG);
  299.         AllocateMemory(&logdir, addrlen, DEFAULTLOG);
  300.     }

  301.     printf
  302.         ("host=%s port=%s back=%s dirroot=%s logdir=%s %s是后台工作模式(进程ID:%d)\n",
  303.          host, port, back, dirroot, logdir, daemon_y_n?"":"不", getpid());

  304.     /* fork() 两次处于后台工作模式下 */
  305.     if (daemon_y_n) {
  306.         if (fork())
  307.             exit(0);
  308.         if (fork())
  309.             exit(0);
  310.         close(0), close(1), close(2);
  311.         logfp = fopen(logdir, "a+");
  312.         if (!logfp)
  313.             exit(0);
  314.     }

  315.     /* 处理子进程退出以免产生僵尸进程 */
  316.     signal(SIGCHLD, SIG_IGN);

  317.     /* 创建 socket */
  318.     if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
  319.         if (!daemon_y_n) {
  320.             prterrmsg("socket()");
  321.         } else {
  322.             wrterrmsg("socket()");
  323.         }
  324.     }

  325.     /* 设置端口快速重用 */
  326.     addrlen = 1;
  327.     setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen,
  328.                sizeof(addrlen));

  329.     addr.sin_family = AF_INET;
  330.     addr.sin_port = htons(atoi(port));
  331.     addr.sin_addr.s_addr = inet_addr(host);
  332.     addrlen = sizeof(struct sockaddr_in);
  333.     /* 绑定地址、端口等信息 */
  334.     if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0) {
  335.         if (!daemon_y_n) {
  336.             prterrmsg("bind()");
  337.         } else {
  338.             wrterrmsg("bind()");
  339.         }
  340.     }

  341.     /* 开启临听 */
  342.     if (listen(sock_fd, atoi(back)) < 0) {
  343.         if (!daemon_y_n) {
  344.             prterrmsg("listen()");
  345.         } else {
  346.             wrterrmsg("listen()");
  347.         }
  348.     }
  349.     while (1) {
  350.         int len;
  351.         int new_fd;
  352.         addrlen = sizeof(struct sockaddr_in);
  353.         /* 接受新连接请求 */
  354.         new_fd = accept(sock_fd, (struct sockaddr *) &addr, &addrlen);
  355.         if (new_fd < 0) {
  356.             if (!daemon_y_n) {
  357.                 prterrmsg("accept()");
  358.             } else {
  359.                 wrterrmsg("accept()");
  360.             }
  361.             break;
  362.         }
  363.         bzero(buffer, MAXBUF + 1);
  364.         sprintf(buffer, "连接来自于: %s:%d\n",
  365.                 inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  366.         if (!daemon_y_n) {
  367.             prtinfomsg(buffer);
  368.         } else {
  369.             wrtinfomsg(buffer);
  370.         }
  371.         /* 产生一个子进程去处理请求,当前进程继续等待新的连接到来 */
  372.         if (!fork()) {
  373.             bzero(buffer, MAXBUF + 1);
  374.             if ((len = recv(new_fd, buffer, MAXBUF, 0)) > 0) {
  375.                 FILE *ClientFP = fdopen(new_fd, "w");
  376.                 if (ClientFP == NULL) {
  377.                     if (!daemon_y_n) {
  378.                         prterrmsg("fdopen()");
  379.                     } else {
  380.                         prterrmsg("fdopen()");
  381.                     }
  382.                 } else {
  383.                     char Req[MAXPATH + 1] = "";
  384.                     sscanf(buffer, "GET %s HTTP", Req);
  385.                     bzero(buffer, MAXBUF + 1);
  386.                     sprintf(buffer, "请求取文件: \"%s\"\n", Req);
  387.                     if (!daemon_y_n) {
  388.                         prtinfomsg(buffer);
  389.                     } else {
  390.                         wrtinfomsg(buffer);
  391.                     }
  392.                     /* 处理用户请求 */
  393.                     GiveResponse(ClientFP, Req);
  394.                     fclose(ClientFP);
  395.                 }
  396.             }
  397.             exit(0);
  398.         }
  399.         close(new_fd);
  400.     }
  401.     close(sock_fd);
  402.     return 0;
  403. }
复制代码

编译程序用下列命令:
gcc -Wall das-server.c -o das-server

注:das即 Dictory Access Server

以root用户启动服务程序用下列命令:
./das-server

或以普通用户启动服务程序用下列命令:
./das-server --port 7838


./das-server -P 7838

注:只有root用户才有权限启动1024以下的端口,所以如果想用默认的80端口就得用root来运行。

如果要想让程序在后台自动运行,即处理精灵模式下工作,在命令后面加上--daemon参数即可。

打开一个网络浏览器输入服务地址开始浏览,如下图:

下载文件如下图:

注:请不要下载较大的文件,比如文件大小超过10M的,因为程序是一次分配内存,会占用系统内存较大导致系统死掉!

[ 本帖最后由 zhoulifa 于 2007-2-4 14:52 编辑 ]
作者: zhoulifa    时间: 2007-02-04 14:56
标题: Linux网络编程一步一步学-用C自己编写一个telnet服务器
服务器源代码如下:

  1. #include <stdarg.h>
  2. #include <errno.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. #include <time.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <dirent.h>
  11. #include <errno.h>
  12. #include <netinet/in.h>
  13. #include <sys/socket.h>
  14. #include <resolv.h>
  15. #include <arpa/inet.h>
  16. #include <stdlib.h>
  17. #include <signal.h>
  18. #include <getopt.h>

  19. #define DEFAULTIP "127.0.0.1"
  20. #define DEFAULTPORT "23"
  21. #define DEFAULTBACK "10"
  22. #define DEFAULTDIR "/tmp"
  23. #define DEFAULTLOG "/tmp/telnet-server.log"

  24. void prterrmsg(char *msg);
  25. #define prterrmsg(msg)        { perror(msg); abort(); }
  26. void wrterrmsg(char *msg);
  27. #define wrterrmsg(msg)        { fputs(msg, logfp); fputs(strerror(errno), logfp);fflush(logfp); abort(); }

  28. void prtinfomsg(char *msg);
  29. #define prtinfomsg(msg)        { fputs(msg, stdout);  }
  30. void wrtinfomsg(char *msg);
  31. #define wrtinfomsg(msg)        {  fputs(msg, logfp); fflush(logfp);}

  32. #define MAXBUF        1024

  33. char buffer[MAXBUF + 1];
  34. char *host = 0;
  35. char *port = 0;
  36. char *back = 0;
  37. char *dirroot = 0;
  38. char *logdir = 0;
  39. unsigned char daemon_y_n = 0;
  40. FILE *logfp;

  41. #define MAXPATH        150

  42. /*------------------------------------------------------
  43. *--- AllocateMemory - 分配空间并把d所指的内容复制
  44. *------------------------------------------------------
  45. */
  46. void AllocateMemory(char **s, int l, char *d)
  47. {
  48.     *s = malloc(l + 1);
  49.     bzero(*s, l + 1);
  50.     memcpy(*s, d, l);
  51. }
  52. /************关于本文档*************************************************************
  53. *filename: telnet-server.c
  54. *purpose: 这是在Linux下用C语言写的telnet服务器,没有用户名和密码,直接以开启服务者的身份登录系统
  55. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  56. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  57. *date time:2007-01-27 17:02
  58. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  59. * 但请遵循GPL
  60. *Thanks to: Google.com
  61. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  62. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  63. **********************************************************************************/

  64. /*------------------------------------------------------
  65. *--- getoption - 分析取出程序的参数
  66. *------------------------------------------------------
  67. */
  68. void getoption(int argc, char **argv)
  69. {
  70.     int c, len;
  71.     char *p = 0;

  72.     opterr = 0;
  73.     while (1) {
  74.         int option_index = 0;
  75.         static struct option long_options[] = {
  76.             {"host", 1, 0, 0},
  77.             {"port", 1, 0, 0},
  78.             {"back", 1, 0, 0},
  79.             {"dir", 1, 0, 0},
  80.             {"log", 1, 0, 0},
  81.             {"daemon", 0, 0, 0},
  82.             {0, 0, 0, 0}
  83.         };
  84.         /* 本程序支持如一些参数:
  85.          * --host IP地址 或者 -H IP地址
  86.          * --port 端口 或者 -P 端口
  87.          * --back 监听数量 或者 -B 监听数量
  88.          * --dir 服务默认目录 或者 -D 服务默认目录
  89.          * --log 日志存放路径 或者 -L 日志存放路径
  90.          * --daemon 使程序进入后台运行模式
  91.          */
  92.         c = getopt_long(argc, argv, "H:P:B:D:L",
  93.                         long_options, &option_index);
  94.         if (c == -1 || c == '?')
  95.             break;

  96.         if(optarg)        len = strlen(optarg);
  97.         else        len = 0;

  98.         if ((!c && !(strcasecmp(long_options[option_index].name, "host")))
  99.             || c == 'H')
  100.             p = host = malloc(len + 1);
  101.         else if ((!c
  102.                   &&
  103.                   !(strcasecmp(long_options[option_index].name, "port")))
  104.                  || c == 'P')
  105.             p = port = malloc(len + 1);
  106.         else if ((!c
  107.                   &&
  108.                   !(strcasecmp(long_options[option_index].name, "back")))
  109.                  || c == 'B')
  110.             p = back = malloc(len + 1);
  111.         else if ((!c
  112.                   && !(strcasecmp(long_options[option_index].name, "dir")))
  113.                  || c == 'D')
  114.             p = dirroot = malloc(len + 1);
  115.         else if ((!c
  116.                   && !(strcasecmp(long_options[option_index].name, "log")))
  117.                  || c == 'L')
  118.             p = logdir = malloc(len + 1);
  119.         else if ((!c
  120.                   &&
  121.                   !(strcasecmp
  122.                     (long_options[option_index].name, "daemon")))) {
  123.             daemon_y_n = 1;
  124.             continue;
  125.         }
  126.         else
  127.             break;
  128.         bzero(p, len + 1);
  129.         memcpy(p, optarg, len);
  130.     }
  131. }

  132. int main(int argc, char **argv)
  133. {
  134.     struct sockaddr_in addr;
  135.     int sock_fd, addrlen;

  136.     /* 获得程序工作的参数,如 IP 、端口、监听数、网页根目录、目录存放位置等 */
  137.     getoption(argc, argv);

  138.     if (!host) {
  139.         addrlen = strlen(DEFAULTIP);
  140.         AllocateMemory(&host, addrlen, DEFAULTIP);
  141.     }
  142.     if (!port) {
  143.         addrlen = strlen(DEFAULTPORT);
  144.         AllocateMemory(&port, addrlen, DEFAULTPORT);
  145.     }
  146.     if (!back) {
  147.         addrlen = strlen(DEFAULTBACK);
  148.         AllocateMemory(&back, addrlen, DEFAULTBACK);
  149.     }
  150.     if (!dirroot) {
  151.         addrlen = strlen(DEFAULTDIR);
  152.         AllocateMemory(&dirroot, addrlen, DEFAULTDIR);
  153.     }
  154.     if (!logdir) {
  155.         addrlen = strlen(DEFAULTLOG);
  156.         AllocateMemory(&logdir, addrlen, DEFAULTLOG);
  157.     }

  158.     printf
  159.         ("host=%s port=%s back=%s dirroot=%s logdir=%s %s是后台工作模式(进程ID:%d)\n",
  160.          host, port, back, dirroot, logdir, daemon_y_n?"":"不", getpid());

  161.     /* fork() 两次处于后台工作模式下 */
  162.     if (daemon_y_n) {
  163.         if (fork())
  164.             exit(0);
  165.         if (fork())
  166.             exit(0);
  167.         close(0), close(1), close(2);
  168.         logfp = fopen(logdir, "a+");
  169.         if (!logfp)
  170.             exit(0);
  171.     }

  172.     /* 处理子进程退出以免产生僵尸进程 */
  173.     signal(SIGCHLD, SIG_IGN);

  174.     /* 创建 socket */
  175.     if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
  176.         if (!daemon_y_n) {
  177.             prterrmsg("socket()");
  178.         } else {
  179.             wrterrmsg("socket()");
  180.         }
  181.     }

  182.     /* 设置端口快速重用 */
  183.     addrlen = 1;
  184.     setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen,
  185.                sizeof(addrlen));

  186.     addr.sin_family = AF_INET;
  187.     addr.sin_port = htons(atoi(port));
  188.     addr.sin_addr.s_addr = inet_addr(host);
  189.     addrlen = sizeof(struct sockaddr_in);
  190.     /* 绑定地址、端口等信息 */
  191.     if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0) {
  192.         if (!daemon_y_n) {
  193.             prterrmsg("bind()");
  194.         } else {
  195.             wrterrmsg("bind()");
  196.         }
  197.     }

  198.     /* 开启临听 */
  199.     if (listen(sock_fd, atoi(back)) < 0) {
  200.         if (!daemon_y_n) {
  201.             prterrmsg("listen()");
  202.         } else {
  203.             wrterrmsg("listen()");
  204.         }
  205.     }
  206.     while (1) {
  207.         int new_fd;
  208.         addrlen = sizeof(struct sockaddr_in);
  209.         /* 接受新连接请求 */
  210.         new_fd = accept(sock_fd, (struct sockaddr *) &addr, &addrlen);
  211.         if (new_fd < 0) {
  212.             if (!daemon_y_n) {
  213.                 prterrmsg("accept()");
  214.             } else {
  215.                 wrterrmsg("accept()");
  216.             }
  217.             break;
  218.         }
  219.         bzero(buffer, MAXBUF + 1);
  220.         sprintf(buffer, "连接来自于: %s:%d\n",
  221.                 inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  222.         if (!daemon_y_n) {
  223.             prtinfomsg(buffer);
  224.         } else {
  225.             wrtinfomsg(buffer);
  226.         }
  227.         /* 产生一个子进程去处理请求,当前进程继续等待新的连接到来 */
  228.         if (!fork()) {
  229.             /* 把socket连接作为标准输入、输出、出错句柄来用 */
  230.             dup2(new_fd, 0);
  231.             dup2(new_fd, 1);
  232.             dup2(new_fd, 2);
  233.             /* 切换到指定目录工作 */
  234.             chdir(dirroot);
  235.             /* 交互式执行shell */
  236.             execl("/bin/bash", "-l", "--login", "-i", "-r", "-s", (char *)NULL);
  237.         }
  238.         close(new_fd);
  239.     }
  240.     close(sock_fd);
  241.     return 0;
  242. }
复制代码

用下列命令编译程序:
gcc -Wall telnet-server -o telnetd

启动telnet服务:
./telnetd --daemon #以root用户身份在23端口(即telnet默认端口服务)


./telnetd -P 7838 #以非root用户身份

然后开启一个新终端,telnet连接自己的服务器试试,如:
telnet 127.0.0.1


telnet 127.0.0.1 7838

不需要输入用户名和密码,直接以启动telnet服务的用户的身份登录系统了。
输入系统命令体验一下吧!
作者: zhoulifa    时间: 2007-02-04 15:04
标题: Linux网络编程一步一步学-网络编程函数说明-来自“永远的UNIX”
www.fanqiang.com(永远的UNIX)网站上也有一系统文章,比较详细地介绍了网络编程的各函数,大家可以去那边看看,我就不复制过来了。

那边系列文章的目录和链接如下:
/************关于本文档*************************************************************
*filename: Linux网络编程一步一步学-网络编程函数说明-来自“永远的UNIX”
*purpose: 详细说明Linux下网络编程各函数的具体用法,当然最好的是各位自己查看在线手册man
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-27 19:22
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
**********************************************************************************/
Linux网络编程--1. Linux网络知识介绍
1.1 客户端程序和服务端程序
1.2 常用的命令
1.3 TCP/UDP介绍
Linux网络编程--2. 初等网络函数介绍(TCP)
2.1 socket
2.2 bind
2.3 listen
2.4 accept
2.5 connect
2.6 实例
2.7 总结
Linux网络编程--3. 服务器和客户机的信息函数
3.1 字节转换函数
3.2 IP和域名的转换
3.3 字符串的IP和32位的IP转换
3.4 服务信息函数
3.5 一个例子
Linux网络编程--4. 完整的读写函数
4.1 写函数write
4.2 读函数read
4.3 数据的传递
Linux网络编程--5. 用户数据报发送
5.1 两个常用的函数
5.2 一个实例
Linux网络编程--6. 高级套接字函数
6.1 recv和send
6.2 recvfrom和sendto
6.3 recvmsg和sendmsg
6.4 套接字的关闭
6.5 shutdown
Linux网络编程--7. TCP/IP协议
7.1 网络传输分层
7.2 IP协议
7.3 ICMP协议
7.4 UDP协议
7.5 TCP
7.6 TCP连接的建立
Linux网络编程--8. 套接字选项
8.1 getsockopt和setsockopt
8.2 ioctl
Linux网络编程--9. 服务器模型
9.1 循环服务器:UDP服务器
9.2 循环服务器:TCP服务器
9.3 并发服务器:TCP服务器
9.4 并发服务器:多路复用I/O
9.5 并发服务器:UDP服务器
9.6 一个并发TCP服务器实例
Linux网络编程--10. 原始套接字 --11. 后记
10. 原始套接字
10.1 原始套接字的创建
10.2 一个原始套接字的实例
10.3 总结
11. 后记


学习任何知识都不能光看不练。必须动手练习,对于这些函数,自己写个小程序测试一下其用法就会很明了了。
作者: zhoulifa    时间: 2007-02-04 15:13
标题: Linux下上网方法总结
目前大家用到的上网方法无非如下几种:电话拨号、ADSL、局域网共享上网。

下面分别说明在Linux下各种上网方法
1、电话拨号网络设置
1)打开一个命令终端,运行配置命令wvdialconf,此命令将设置拨号上网的各参数,保存在文件/etc/wvdial.conf里,文件内容如下:
[Dialer Defaults]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = Analog Modem
Baud = 115200
New PPPD = yes
Modem = /dev/modem
ISDN = 0
; Phone =
; Password =
; Username =

2)手动编辑此文件,比如用vi命令,vi /etc/wvdial.conf
把此文件的最后几行前面的注释符号去掉,后面的值改成电话拨号的几个参数(这三个参数可以打电信的客户号码问),如下:
Phone = 拨号上网的电话号码
Password = 上网的用户名
Username = 上网的密码

至此,电话拨号上网已经设置完成
3)运行命令来开始拨号,命令如下:
pon wvdial

连接成功后就可以开心上网了。

2、ADSL上网设置
运行一个ADSL配置命令pppoeconf。
按照界面提示一步步设置即可
程序将自动检测系统可用的网卡,如下图:

一般应该只出现eth0, eth1之类的,因为我装了VMware虚拟机,所以有vmnet1、vmnet8之类的虚拟网卡出现 。

当然这里要选“是”以便继续设置。

系统自动检测ADSL设备。

允许程序设置一些默认参数,当然你也可以选择“否”,按照你自己的特殊参数来设置。但目前国内的ADSL可能没什么差别,所以选默认值足够了。

这里输入ADSL的帐号。

这里输入ADSL帐号对应的密码。下面的都选“是”应该可以了。



如果你只是偶尔用一下ADSL上网,那么这里可以选择“否”,如果每次都是用ADSL上网并且不限时上网(即包月上网那种),就选择“是”将会开机自动启动ADSL网络。

这里说明了以后手工启动ADSL网络的方法,看到了没?输入命令pon dsl-provider即可启动上网,poff就可以断开网络连接了。

配置完成后在/etc/ppp/目录下生成ppp相关的文件,当然/etc/ppp/peers/dsl-provider文件里记录了所有参数,你可以自己修改。
同时在/etc/init.d目录里也生成 了几个文件:
/etc/init.d/pppd-dns
/etc/init.d/ppp
/etc/init.d/pppstatus-clean
/etc/init.d/pppstatus

所以你也可以用/etc/init.d/ppp start命令来启动网络。和pon dsl-provider一样。
网络启动了就可以开心上网了。
/************关于本文档********************************************
*filename: Linux下上网方法总结
*purpose: 说明了Linux下几种上网方法的设置:电话拨号、ADSL、局域网共享上网
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-28 18:02
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/

3、局域网共享上网设置
一般来说局域网里一定有一个作为路由的设备连接着互联网,其它局域网电脑只要设置好IP地址、网关、子网掩码和DNS就可以上风了。

最简单的方法就是设置网卡为DHCP动态获取方式,设置方法如下:
如果是Debian/Ubuntu系统,则修改/etc/network/interfaces文件,把里面关于eth0(这是第一块网卡,相应地,eth1代表第二块网卡)的改成如下即可:
auto eth0
iface eth0 inet dhcp

如果是Red Hat等其它的,好象是要修改/etc/sysconfig/network-scripts/ifcfg-eth0文件(太久不用这个版本了,所以记不太清楚),修改内容如下:
DEVICE=eth0
BOOTPROTO=dhcp
onBOOT=yes

另一种方法就是按照管理员的要求,每台电脑分配一个固定的IP地址,同样是要修改上面提到的文件,比如/etc/network/interfaces,文件里关于eth0的内容要改成下面的:
auto eth0
iface eth0 inet static
address 192.168.100.30
gateway 192.168.100.1
netmask 255.255.255.0

/etc/sysconfig/network-scripts/ifcfg-eth0要修改成如下方式:
DEVICE=eth0
BOOTPROTO=none
onBOOT=yes
IPADDR=192.168.100.30
NETMASK=255.255.255.0
GATEWAY=192.168.100.1

所有配置方法都有相关图形界面可以用的,比如下图:

但我是写程序的,所以比较喜欢用命令去配置或者自己动手修改配置文件。因为系统所有东西除非是写在操作系统里,否则都是在文件里,由操作系统来读取并执行的。
作者: zhoulifa    时间: 2007-02-04 15:14
标题: Linux网络编程一步一步学-利用OpenSSL提供的SSL操作函数进行加密通讯原始例子
首先,大家知道SSL这一目前“事实上的Internet加密标准” 吧?一般的网站是没有用到SSL的,所以如果你用TCPDUMP就可以很容易地看到别人上网的帐号、密码之类的,当然,现在有些已经改用安全通讯方式进行验证了,比如google的邮件服务gmail,而象银行、证券等行业,从一开始就要求用加密通讯,你在哪个银行网站上输入帐号和密码后点击提交不是通过加密方式提交的呢?事实上,SSL也正是在银行这些行业的需求下才产生的。
现在大家经常上网会上到一些https://开头的网站,那就是把SSL标准应用到HTTP上从而变成了HTTPS。另外大家可能都不用telnet这个明文传输工具来进行远程登录了,都改用ssh了,ssh正是SSL的一个实现,用来进行远程加密通讯的。

其次,要在我们现有的TCP程序上加上SSL,你得安装开发包libssl-dev,这个包的描述是这样的:
Package: libssl-dev
Priority: optional
Section: libdevel
Installed-Size: 5552
Maintainer: Debian OpenSSL Team
Architecture: i386
Source: openssl
Version: 0.9.8a-7ubuntu0.3
Depends: libssl0.9.8 (= 0.9.8a-7ubuntu0.3), zlib1g-dev
Conflicts: ssleay (<< 0.9.2b), libssl08-dev, libssl09-dev, libssl095a-dev, libssl096-dev
Filename: pool/main/o/openssl/libssl-dev_0.9.8a-7ubuntu0.3_i386.deb
Size: 2023440
MD5sum: 3c4052d07abe7d7984a774ca815ba4cf
SHA1: 29145b66372613e78c37d9ce0de6a7d1cfc7bac0
SHA256: 9e86aa1174a45e4f61e5afcb56d485ea60f90e31b0ecaf2bf31f426f7eb8c6eb
Description: SSL development libraries, header files and documentation
libssl and libcrypt development libraries, header files and manpages
.
It is part of the OpenSSL implementation of SSL.
Bugs: mailto:ubuntu-users@lists.ubuntu.com
Origin: Ubuntu

Package: libssl-dev
Priority: optional
Section: libdevel
Installed-Size: 5548
Maintainer: Debian OpenSSL Team
Architecture: i386
Source: openssl
Version: 0.9.8a-7build1
Depends: libssl0.9.8 (= 0.9.8a-7build1), zlib1g-dev
Conflicts: ssleay (<< 0.9.2b), libssl08-dev, libssl09-dev, libssl095a-dev, libssl096-dev
Filename: pool/main/o/openssl/libssl-dev_0.9.8a-7build1_i386.deb
Size: 2022142
MD5sum: c9b989aebbae4f6f5dbde67207858023
Description: SSL development libraries, header files and documentation
libssl and libcrypt development libraries, header files and manpages
.
It is part of the OpenSSL implementation of SSL.
Bugs: mailto:ubuntu-users@lists.ubuntu.com
Origin: Ubuntu

也就是说这个libssl-dev包是库函数、头文件以及相关编程说明文档的集合。

安装完成之后在/usr/share/doc/libssl-dev/demos目录下有一些编程示例。你可以参照里面的文档自己来写加密通讯程序。

/************关于本文档********************************************
*filename: Linux网络编程一步一步学-利用OpenSSL提供的SSL操作函数进行加密通讯原始例子
*purpose: 说明了如果在Linux下利用OpenSSL库函数进行SSL加密通讯程序开发
*tidied by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-01-28 19:00
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to: Google.com
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/

比如/usr/share/doc/libssl-dev/demos/bio目录下提供的一个服务器端例子,代码如下:

  1. /* NOCW */
  2. /* demos/bio/saccept.c */

  3. /* A minimal program to server an SSL connection.
  4. * It uses blocking.
  5. * saccept host:port
  6. * host is the interface IP to use.  If any interface, use *:port
  7. * The default it *:4433
  8. *
  9. * cc -I../../include saccept.c -L../.. -lssl -lcrypto
  10. */

  11. #include <stdio.h>
  12. #include <signal.h>
  13. #include <openssl/err.h>
  14. #include <openssl/ssl.h>

  15. #define CERT_FILE    "server.pem"

  16. BIO *in=NULL;

  17. void close_up()
  18.     {
  19.     if (in != NULL)
  20.         BIO_free(in);
  21.     }

  22. int main(argc,argv)
  23. int argc;
  24. char *argv[];
  25.     {
  26.     char *port=NULL;
  27.     BIO *ssl_bio,*tmp;
  28.     SSL_CTX *ctx;
  29.     SSL *ssl;
  30.     char buf[512];
  31.     int ret=1,i;

  32.         if (argc <= 1)
  33.         port="*:4433";
  34.     else
  35.         port=argv[1];

  36.     signal(SIGINT,close_up);

  37.     SSL_load_error_strings();

  38. #ifdef WATT32
  39.     dbug_init();
  40.     sock_init();
  41. #endif

  42.     /* Add ciphers and message digests */
  43.     OpenSSL_add_ssl_algorithms();

  44.     ctx=SSL_CTX_new(SSLv23_server_method());
  45.     if (!SSL_CTX_use_certificate_file(ctx,CERT_FILE,SSL_FILETYPE_PEM))
  46.         goto err;
  47.     if (!SSL_CTX_use_PrivateKey_file(ctx,CERT_FILE,SSL_FILETYPE_PEM))
  48.         goto err;
  49.     if (!SSL_CTX_check_private_key(ctx))
  50.         goto err;

  51.     /* Setup server side SSL bio */
  52.     ssl=SSL_new(ctx);
  53.     ssl_bio=BIO_new_ssl(ctx,0);

  54.     if ((in=BIO_new_accept(port)) == NULL) goto err;

  55.     /* This means that when a new connection is acceptede on 'in',
  56.      * The ssl_bio will be 'dupilcated' and have the new socket
  57.      * BIO push into it.  Basically it means the SSL BIO will be
  58.      * automatically setup */
  59.     BIO_set_accept_bios(in,ssl_bio);

  60. again:
  61.     /* The first call will setup the accept socket, and the second
  62.      * will get a socket.  In this loop, the first actual accept
  63.      * will occur in the BIO_read() function. */

  64.     if (BIO_do_accept(in) <= 0) goto err;

  65.     for (;;)
  66.         {
  67.         i=BIO_read(in,buf,512);
  68.         if (i == 0)
  69.             {
  70.             /* If we have finished, remove the underlying
  71.              * BIO stack so the next time we call any function
  72.              * for this BIO, it will attempt to do an
  73.              * accept */
  74.             printf("Done\n");
  75.             tmp=BIO_pop(in);
  76.             BIO_free_all(tmp);
  77.             goto again;
  78.             }
  79.         if (i < 0) goto err;
  80.         fwrite(buf,1,i,stdout);
  81.         fflush(stdout);
  82.         }

  83.     ret=0;
  84. err:
  85.     if (ret)
  86.         {
  87.         ERR_print_errors_fp(stderr);
  88.         }
  89.     if (in != NULL) BIO_free(in);
  90.     exit(ret);
  91.     return(!ret);
  92.     }
复制代码

对应的一个客户端例子代码如下:

  1. /* NOCW */
  2. /* demos/bio/sconnect.c */

  3. /* A minimal program to do SSL to a passed host and port.
  4. * It is actually using non-blocking IO but in a very simple manner
  5. * sconnect host:port - it does a 'GET / HTTP/1.0'
  6. *
  7. * cc -I../../include sconnect.c -L../.. -lssl -lcrypto
  8. */
  9. #include <stdio.h>
  10. #include <stdlib.h>
  11. #include <unistd.h>
  12. #include <openssl/err.h>
  13. #include <openssl/ssl.h>

  14. extern int errno;

  15. int main(argc,argv)
  16. int argc;
  17. char *argv[];
  18.     {
  19.     char *host;
  20.     BIO *out;
  21.     char buf[1024*10],*p;
  22.     SSL_CTX *ssl_ctx=NULL;
  23.     SSL *ssl;
  24.     BIO *ssl_bio;
  25.     int i,len,off,ret=1;

  26.     if (argc <= 1)
  27.         host="localhost:4433";
  28.     else
  29.         host=argv[1];

  30. #ifdef WATT32
  31.     dbug_init();
  32.     sock_init();
  33. #endif

  34.     /* Lets get nice error messages */
  35.     SSL_load_error_strings();

  36.     /* Setup all the global SSL stuff */
  37.     OpenSSL_add_ssl_algorithms();
  38.     ssl_ctx=SSL_CTX_new(SSLv23_client_method());

  39.     /* Lets make a SSL structure */
  40.     ssl=SSL_new(ssl_ctx);
  41.     SSL_set_connect_state(ssl);

  42.     /* Use it inside an SSL BIO */
  43.     ssl_bio=BIO_new(BIO_f_ssl());
  44.     BIO_set_ssl(ssl_bio,ssl,BIO_CLOSE);

  45.     /* Lets use a connect BIO under the SSL BIO */
  46.     out=BIO_new(BIO_s_connect());
  47.     BIO_set_conn_hostname(out,host);
  48.     BIO_set_nbio(out,1);
  49.     out=BIO_push(ssl_bio,out);

  50.     p="GET / HTTP/1.0\r\n\r\n";
  51.     len=strlen(p);

  52.     off=0;
  53.     for (;;)
  54.         {
  55.         i=BIO_write(out,&(p[off]),len);
  56.         if (i <= 0)
  57.             {
  58.             if (BIO_should_retry(out))
  59.                 {
  60.                 fprintf(stderr,"write DELAY\n");
  61.                 sleep(1);
  62.                 continue;
  63.                 }
  64.             else
  65.                 {
  66.                 goto err;
  67.                 }
  68.             }
  69.         off+=i;
  70.         len-=i;
  71.         if (len <= 0) break;
  72.         }

  73.     for (;;)
  74.         {
  75.         i=BIO_read(out,buf,sizeof(buf));
  76.         if (i == 0) break;
  77.         if (i < 0)
  78.             {
  79.             if (BIO_should_retry(out))
  80.                 {
  81.                 fprintf(stderr,"read DELAY\n");
  82.                 sleep(1);
  83.                 continue;
  84.                 }
  85.             goto err;
  86.             }
  87.         fwrite(buf,1,i,stdout);
  88.         }

  89.     ret=1;

  90.     if (0)
  91.         {
  92. err:
  93.         if (ERR_peek_error() == 0) /* system call error */
  94.             {
  95.             fprintf(stderr,"errno=%d ",errno);
  96.             perror("error");
  97.             }
  98.         else
  99.             ERR_print_errors_fp(stderr);
  100.         }
  101.     BIO_free_all(out);
  102.     if (ssl_ctx != NULL) SSL_CTX_free(ssl_ctx);
  103.     exit(!ret);
  104.     return(ret);
  105.     }
复制代码

编译程序里象一般的gcc命令一样,但需要链接ssl库,比如
gcc -Wall saccept.c -o sslserver -lssl
gcc -Wall sconnect.c -o sslclient -l ssl


【作者: 周立发】【访问统计:24】【2007年01月28日 星期日 18:47】【注册】【打印】
Trackback

你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=6074014
回复
发布人:
作者: zhoulifa    时间: 2007-02-04 15:17
标题: Linux网络编程一步一步学-IPv6下网络编程步骤
就是本贴第一篇了:
Linux网络编程一步一步学-IPv6下网络编程步骤
作者: zhoulifa    时间: 2007-02-04 15:27
标题: Linux网络编程一步一步学-HTTPS客户端程序示例
源代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <sys/types.h>
  5. #include <sys/socket.h>
  6. #include <errno.h>
  7. #include <unistd.h>
  8. #include <netinet/in.h>
  9. #include <limits.h>
  10. #include <netdb.h>
  11. #include <arpa/inet.h>
  12. #include <ctype.h>
  13. #include <openssl/crypto.h>
  14. #include <openssl/ssl.h>
  15. #include <openssl/err.h>
  16. #include <openssl/rand.h>

  17. #define DEBUG 1

  18. /********************************************
  19. 功能:搜索字符串右边起的第一个匹配字符
  20. ********************************************/
  21. char *Rstrchr(char *s, char x)
  22. {
  23.     int i = strlen(s);
  24.     if (!(*s))
  25.         return 0;
  26.     while (s[i - 1])
  27.         if (strchr(s + (i - 1), x))
  28.             return (s + (i - 1));
  29.         else
  30.             i--;
  31.     return 0;
  32. }

  33. /**************************************************************
  34. 功能:从字符串src中分析出网站地址和端口,并得到用户要下载的文件
  35. ***************************************************************/
  36. void GetHost(char *src, char *web, char *file, int *port)
  37. {
  38.     char *pA;
  39.     char *pB;
  40.     memset(web, 0, sizeof(web));
  41.     memset(file, 0, sizeof(file));
  42.     *port = 0;
  43.     if (!(*src))
  44.         return;
  45.     pA = src;
  46.     if (!strncmp(pA, "http://", strlen("http://")))
  47.         pA = src + strlen("http://");
  48.     else if (!strncmp(pA, "https://", strlen("https://")))
  49.         pA = src + strlen("https://");
  50.     pB = strchr(pA, '/');
  51.     if (pB) {
  52.         memcpy(web, pA, strlen(pA) - strlen(pB));
  53.         if (pB + 1) {
  54.             memcpy(file, pB + 1, strlen(pB) - 1);
  55.             file[strlen(pB) - 1] = 0;
  56.         }
  57.     } else
  58.         memcpy(web, pA, strlen(pA));
  59.     if (pB)
  60.         web[strlen(pA) - strlen(pB)] = 0;
  61.     else
  62.         web[strlen(pA)] = 0;
  63.     pA = strchr(web, ':');
  64.     if (pA)
  65.         *port = atoi(pA + 1);
  66.     else
  67.         *port = 443;
  68. }

  69. /************关于本文档********************************************
  70. *filename: https-client.c
  71. *purpose: 演示HTTPS客户端编程方法
  72. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  73. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  74. *date time:2007-01-30 20:06
  75. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  76. * 但请遵循GPL
  77. *Thanks to:Google
  78. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  79. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  80. *********************************************************************/

  81. int main(int argc, char *argv[])
  82. {
  83.     int sockfd, ret;
  84.     char buffer[1024];
  85.     struct sockaddr_in server_addr;
  86.     struct hostent *host;
  87.     int portnumber, nbytes;
  88.     char host_addr[256];
  89.     char host_file[1024];
  90.     char local_file[256];
  91.     FILE *fp;
  92.     char request[1024];
  93.     int send, totalsend;
  94.     int i;
  95.     char *pt;
  96.     SSL *ssl;
  97.     SSL_CTX *ctx;

  98.     if (argc != 2) {
  99.         if (DEBUG)
  100.             fprintf(stderr, "Usage:%s webpage-address\a\n", argv[0]);
  101.         exit(1);
  102.     }
  103.     if (DEBUG)
  104.         printf("parameter.1 is: %s\n", argv[1]);

  105.     GetHost(argv[1], host_addr, host_file, &portnumber);        /*分析网址、端口、文件名等 */
  106.     if (DEBUG)
  107.         printf("webhost:%s\n", host_addr);
  108.     if (DEBUG)
  109.         printf("hostfile:%s\n", host_file);
  110.     if (DEBUG)
  111.         printf("portnumber:%d\n\n", portnumber);

  112.     if ((host = gethostbyname(host_addr)) == NULL) {        /*取得主机IP地址 */
  113.         if (DEBUG)
  114.             fprintf(stderr, "Gethostname error, %s\n", strerror(errno));
  115.         exit(1);
  116.     }

  117.     /* 客户程序开始建立 sockfd描述符 */
  118.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {        /*建立SOCKET连接 */
  119.         if (DEBUG)
  120.             fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
  121.         exit(1);
  122.     }

  123.     /* 客户程序填充服务端的资料 */
  124.     bzero(&server_addr, sizeof(server_addr));
  125.     server_addr.sin_family = AF_INET;
  126.     server_addr.sin_port = htons(portnumber);
  127.     server_addr.sin_addr = *((struct in_addr *) host->h_addr);

  128.     /* 客户程序发起连接请求 */
  129.     if (connect(sockfd, (struct sockaddr *) (&server_addr), sizeof(struct sockaddr)) == -1) {        /*连接网站 */
  130.         if (DEBUG)
  131.             fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
  132.         exit(1);
  133.     }

  134.     /* SSL初始化 */
  135.     SSL_library_init();
  136.     SSL_load_error_strings();
  137.     ctx = SSL_CTX_new(SSLv23_client_method());
  138.     if (ctx == NULL) {
  139.         ERR_print_errors_fp(stderr);
  140.         exit(1);
  141.     }

  142.     ssl = SSL_new(ctx);
  143.     if (ssl == NULL) {
  144.         ERR_print_errors_fp(stderr);
  145.         exit(1);
  146.     }

  147.     /* 把socket和SSL关联 */
  148.     ret = SSL_set_fd(ssl, sockfd);
  149.     if (ret == 0) {
  150.         ERR_print_errors_fp(stderr);
  151.         exit(1);
  152.     }

  153.     RAND_poll();
  154.     while (RAND_status() == 0) {
  155.         unsigned short rand_ret = rand() % 65536;
  156.         RAND_seed(&rand_ret, sizeof(rand_ret));
  157.     }

  158.     ret = SSL_connect(ssl);
  159.     if (ret != 1) {
  160.         ERR_print_errors_fp(stderr);
  161.         exit(1);
  162.     }

  163.     sprintf(request, "GET /%s HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-cn\r\n\
  164. User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n\
  165. Host: %s:%d\r\nConnection: Close\r\n\r\n", host_file, host_addr,
  166.             portnumber);
  167.     if (DEBUG)
  168.         printf("%s", request);        /*准备request,将要发送给主机 */

  169.     /*取得真实的文件名 */
  170.     if (host_file && *host_file)
  171.         pt = Rstrchr(host_file, '/');
  172.     else
  173.         pt = 0;

  174.     memset(local_file, 0, sizeof(local_file));
  175.     if (pt && *pt) {
  176.         if ((pt + 1) && *(pt + 1))
  177.             strcpy(local_file, pt + 1);
  178.         else
  179.             memcpy(local_file, host_file, strlen(host_file) - 1);
  180.     } else if (host_file && *host_file)
  181.         strcpy(local_file, host_file);
  182.     else
  183.         strcpy(local_file, "index.html");
  184.     if (DEBUG)
  185.         printf("local filename to write:%s\n\n", local_file);

  186.     /*发送https请求request */
  187.     send = 0;
  188.     totalsend = 0;
  189.     nbytes = strlen(request);
  190.     while (totalsend < nbytes) {
  191.         send = SSL_write(ssl, request + totalsend, nbytes - totalsend);
  192.         if (send == -1) {
  193.             if (DEBUG)
  194.                 ERR_print_errors_fp(stderr);
  195.             exit(0);
  196.         }
  197.         totalsend += send;
  198.         if (DEBUG)
  199.             printf("%d bytes send OK!\n", totalsend);
  200.     }

  201.     fp = fopen(local_file, "a");
  202.     if (!fp) {
  203.         if (DEBUG)
  204.             printf("create file error! %s\n", strerror(errno));
  205.         return 0;
  206.     }
  207.     if (DEBUG)
  208.         printf("\nThe following is the response header:\n");
  209.     i = 0;
  210.     /* 连接成功了,接收https响应,response */
  211.     while ((nbytes = SSL_read(ssl, buffer, 1)) == 1) {
  212.         if (i < 4) {
  213.             if (buffer[0] == '\r' || buffer[0] == '\n')
  214.                 i++;
  215.             else
  216.                 i = 0;
  217.             if (DEBUG)
  218.                 printf("%c", buffer[0]);        /*把https头信息打印在屏幕上 */
  219.         } else {
  220.             fwrite(buffer, 1, 1, fp);        /*将https主体信息写入文件 */
  221.             i++;
  222.             if (i % 1024 == 0)
  223.                 fflush(fp);        /*每1K时存盘一次 */
  224.         }
  225.     }
  226.     fclose(fp);
  227.     /* 结束通讯 */
  228.     ret = SSL_shutdown(ssl);
  229.     if (ret != 1) {
  230.         ERR_print_errors_fp(stderr);
  231.         exit(1);
  232.     }
  233.     close(sockfd);
  234.     SSL_free(ssl);
  235.     SSL_CTX_free(ctx);
  236.     ERR_free_strings();
  237.     exit(0);
  238. }
复制代码

编译此程序用下列命令:
gcc -Wall https-client.c -lssl -o httpsclient

运行此程序来取得HTTPS服务器上的页面,比如:
./httpsclient https://127.0.0.1/test.html

关键之处在于建立socket之后的SSL相关初始化以及中间的recv/send用SSL_read和SSL_write代替,最后记得释放SSL资源即可。
可以对比之前的文章来发现异同:
HTTP协议的C语言编程实现实例
作者: zhoulifa    时间: 2007-02-04 15:29
标题: OpenSSL体系下使用密钥数字证书等
首先得安装OpenSSL软件包openssl,安装了这个软件包之后,我们可以做这些事情:
  o  Creation of RSA, DH and DSA Key Parameters # 创建密钥 key
  o  Creation of X.509 Certificates, CSRs and CRLs # 创建证书
  o  Calculation of Message Digests                #
  o  Encryption and Decryption with Ciphers # 加密、解密
  o  SSL/TLS Client and Server Tests        # SSL 服务器端/客户端测试
  o  Handling of S/MIME signed or encrypted Mail  # 处理签名或加密了的邮件

1、生成RSA密钥的方法
openssl genrsa -des3 -out privkey.pem 2048

这个命令会生成一个2048位的密钥,同时有一个des3方法加密的密码,如果你不想要每次都输入密码,可以改成:
openssl genrsa -out privkey.pem 2048

建议用2048位密钥,少于此可能会不安全或很快将不安全。

2、生成一个证书请求
openssl req -new -key privkey.pem -out cert.csr

这个命令将会生成一个证书请求,当然,用到了前面生成的密钥privkey.pem文件
这里将生成一个新的文件cert.csr,即一个证书请求文件,你可以拿着这个文件去数字证书颁发机构(即CA)申请一个数字证书。CA会给你一个新的文件cacert.pem,那才是你的数字证书。

如果是自己做测试,那么证书的申请机构和颁发机构都是自己。就可以用下面这个命令来生成证书:
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

这个命令将用上面生成的密钥privkey.pem生成一个数字证书cacert.pem

3、使用数字证书和密钥
有了privkey.pem和cacert.pem文件后就可以在自己的程序中使用了,比如做一个加密通讯的服务器
作者: zhoulifa    时间: 2007-02-04 15:32
标题: Linux网络编程一步一步学-epoll同时处理海量连接的代码
如果你是做大型项目的,比如同时要求上万人在线的某种程序,比如在线游戏、在线聊天,那怕是一个最简单的支持上万人同时在线的WEB服务器。用原来的方法都比较困难实现的。
下面这段代码就可以支持9997个连接同时在线,当然你可以修改宏定义以支持更多的连接:
#define MAXEPOLLSIZE 10000

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>
  11. #include <openssl/ssl.h>
  12. #include <openssl/err.h>
  13. #include <fcntl.h>
  14. #include <sys/epoll.h>
  15. #include <sys/time.h>
  16. #include <sys/resource.h>


  17. #define MAXBUF 1024
  18. #define MAXEPOLLSIZE 10000

  19. /*
  20. setnonblocking - 设置句柄为非阻塞方式
  21. */
  22. int setnonblocking(int sockfd)
  23. {
  24.     if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
  25.         return -1;
  26.     }
  27.     return 0;
  28. }

  29. /*
  30. handle_message - 处理每个 socket 上的消息收发
  31. */
  32. int handle_message(int new_fd)
  33. {
  34.     char buf[MAXBUF + 1];
  35.     int len;
  36.     /* 开始处理每个新连接上的数据收发 */
  37.     bzero(buf, MAXBUF + 1);
  38.     /* 接收客户端的消息 */
  39.     len = recv(new_fd, buf, MAXBUF, 0);
  40.     if (len > 0)
  41.         printf
  42.             ("%d接收消息成功:'%s',共%d个字节的数据\n",
  43.              new_fd, buf, len);
  44.     else {
  45.         if (len < 0)
  46.             printf
  47.                 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  48.                  errno, strerror(errno));
  49.         close(new_fd);
  50.         return -1;
  51.     }
  52.     /* 处理每个新连接上的数据收发结束 */
  53.     return len;
  54. }
  55. /************关于本文档********************************************
  56. *filename: epoll-server.c
  57. *purpose: 演示epoll处理海量socket连接的方法
  58. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  59. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  60. *date time:2007-01-31 21:00
  61. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  62. * 但请遵循GPL
  63. *Thanks to:Google
  64. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  65. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  66. *********************************************************************/
  67. int main(int argc, char **argv)
  68. {
  69.     int listener, new_fd, kdpfd, nfds, n, ret, curfds;
  70.     socklen_t len;
  71.     struct sockaddr_in my_addr, their_addr;
  72.     unsigned int myport, lisnum;
  73.     struct epoll_event ev;
  74.     struct epoll_event events[MAXEPOLLSIZE];
  75.     struct rlimit rt;

  76.     if (argv[1])
  77.         myport = atoi(argv[1]);
  78.     else
  79.         myport = 7838;

  80.     if (argv[2])
  81.         lisnum = atoi(argv[2]);
  82.     else
  83.         lisnum = 2;

  84.     /* 设置每个进程允许打开的最大文件数 */
  85.     rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
  86.     if (setrlimit(RLIMIT_NOFILE, &rt) == -1) {
  87.         perror("setrlimit");
  88.         exit(1);
  89.     }
  90.     else printf("设置系统资源参数成功!\n");

  91.     /* 开启 socket 监听 */
  92.     if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  93.         perror("socket");
  94.         exit(1);
  95.     } else
  96.         printf("socket 创建成功!\n");

  97.     setnonblocking(listener);

  98.     bzero(&my_addr, sizeof(my_addr));
  99.     my_addr.sin_family = PF_INET;
  100.     my_addr.sin_port = htons(myport);
  101.     if (argv[3])
  102.         my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  103.     else
  104.         my_addr.sin_addr.s_addr = INADDR_ANY;

  105.     if (bind
  106.         (listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  107.         == -1) {
  108.         perror("bind");
  109.         exit(1);
  110.     } else
  111.         printf("IP 地址和端口绑定成功\n");

  112.     if (listen(listener, lisnum) == -1) {
  113.         perror("listen");
  114.         exit(1);
  115.     } else
  116.         printf("开启服务成功!\n");

  117.     /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
  118.     kdpfd = epoll_create(MAXEPOLLSIZE);
  119.     len = sizeof(struct sockaddr_in);
  120.     ev.events = EPOLLIN | EPOLLET;
  121.     ev.data.fd = listener;
  122.     if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0) {
  123.         fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);
  124.         return -1;
  125.     } else
  126.         printf("监听 socket 加入 epoll 成功!\n");
  127.     curfds = 1;
  128.     while (1) {
  129.         /* 等待有事件发生 */
  130.         nfds = epoll_wait(kdpfd, events, curfds, -1);
  131.         if (nfds == -1) {
  132.             perror("epoll_wait");
  133.             break;
  134.         }
  135.         /* 处理所有事件 */
  136.         for (n = 0; n < nfds; ++n) {
  137.             if (events[n].data.fd == listener) {
  138.                 new_fd = accept(listener, (struct sockaddr *) &their_addr,
  139.                                 &len);
  140.                 if (new_fd < 0) {
  141.                     perror("accept");
  142.                     continue;
  143.                 } else
  144.                     printf("有连接来自于: %d:%d, 分配的 socket 为:%d\n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);

  145.                 setnonblocking(new_fd);
  146.                 ev.events = EPOLLIN | EPOLLET;
  147.                 ev.data.fd = new_fd;
  148.                 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0) {
  149.                     fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s\n",
  150.                             new_fd, strerror(errno));
  151.                     return -1;
  152.                 }
  153.                 curfds++;
  154.             } else {
  155.                 ret = handle_message(events[n].data.fd);
  156.                 if (ret < 1 && errno != 11) {
  157.                     epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,
  158.                               &ev);
  159.                     curfds--;
  160.                 }
  161.             }
  162.         }
  163.     }
  164.     close(listener);
  165.     return 0;
  166. }
复制代码

编译此程序用命令:
gcc -Wall epoll-server.c -o server

运行此程序需要具有管理员权限!
sudo ./server 7838 1

通过测试这一个服务器可能同时处理10000 -3 = 9997 个连接!

如果这是一个在线服务系统,那么它可以支持9997人同时在线,比如游戏、聊天等。
作者: zhoulifa    时间: 2007-02-04 15:43
标题: Linux网络编程一步一步学-加密通讯协议SSL研究
服务器端源代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>
  11. #include <openssl/ssl.h>
  12. #include <openssl/err.h>

  13. #define MAXBUF 1024
  14. /************关于本文档********************************************
  15. *filename: ssl-server.c
  16. *purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是服务器端例子
  17. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  18. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  19. *date time:2007-02-02 19:40
  20. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  21. * 但请遵循GPL
  22. *Thanks to:Google
  23. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  24. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  25. *********************************************************************/
  26. int main(int argc, char **argv)
  27. {
  28.     int sockfd, new_fd;
  29.     socklen_t len;
  30.     struct sockaddr_in my_addr, their_addr;
  31.     unsigned int myport, lisnum;
  32.     char buf[MAXBUF + 1];
  33.     SSL_CTX *ctx;

  34.     if (argv[1])
  35.         myport = atoi(argv[1]);
  36.     else
  37.         myport = 7838;

  38.     if (argv[2])
  39.         lisnum = atoi(argv[2]);
  40.     else
  41.         lisnum = 2;

  42.     /* SSL 库初始化 */
  43.     SSL_library_init();
  44.     /* 载入所有 SSL 算法 */
  45.     OpenSSL_add_all_algorithms();
  46.     /* 载入所有 SSL 错误消息 */
  47.     SSL_load_error_strings();
  48.     /* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
  49.     ctx = SSL_CTX_new(SSLv23_server_method());
  50.     /* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
  51.     if (ctx == NULL) {
  52.         ERR_print_errors_fp(stdout);
  53.         exit(1);
  54.     }
  55.     /* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
  56.     if (SSL_CTX_use_certificate_file(ctx, argv[4], SSL_FILETYPE_PEM) <= 0) {
  57.         ERR_print_errors_fp(stdout);
  58.         exit(1);
  59.     }
  60.     /* 载入用户私钥 */
  61.     if (SSL_CTX_use_PrivateKey_file(ctx, argv[5], SSL_FILETYPE_PEM) <= 0) {
  62.         ERR_print_errors_fp(stdout);
  63.         exit(1);
  64.     }
  65.     /* 检查用户私钥是否正确 */
  66.     if (!SSL_CTX_check_private_key(ctx)) {
  67.         ERR_print_errors_fp(stdout);
  68.         exit(1);
  69.     }

  70.     /* 开启一个 socket 监听 */
  71.     if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  72.         perror("socket");
  73.         exit(1);
  74.     } else
  75.         printf("socket created\n");

  76.     bzero(&my_addr, sizeof(my_addr));
  77.     my_addr.sin_family = PF_INET;
  78.     my_addr.sin_port = htons(myport);
  79.     if (argv[3])
  80.         my_addr.sin_addr.s_addr = inet_addr(argv[3]);
  81.     else
  82.         my_addr.sin_addr.s_addr = INADDR_ANY;

  83.     if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  84.         == -1) {
  85.         perror("bind");
  86.         exit(1);
  87.     } else
  88.         printf("binded\n");

  89.     if (listen(sockfd, lisnum) == -1) {
  90.         perror("listen");
  91.         exit(1);
  92.     } else
  93.         printf("begin listen\n");

  94.     while (1) {
  95.         SSL *ssl;
  96.         len = sizeof(struct sockaddr);
  97.         /* 等待客户端连上来 */
  98.         if ((new_fd =
  99.              accept(sockfd, (struct sockaddr *) &their_addr,
  100.                     &len)) == -1) {
  101.             perror("accept");
  102.             exit(errno);
  103.         } else
  104.             printf("server: got connection from %s, port %d, socket %d\n",
  105.                    inet_ntoa(their_addr.sin_addr),
  106.                    ntohs(their_addr.sin_port), new_fd);

  107.         /* 基于 ctx 产生一个新的 SSL */
  108.         ssl = SSL_new(ctx);
  109.         /* 将连接用户的 socket 加入到 SSL */
  110.         SSL_set_fd(ssl, new_fd);
  111.         /* 建立 SSL 连接 */
  112.         if (SSL_accept(ssl) == -1) {
  113.             perror("accept");
  114.             close(new_fd);
  115.             break;
  116.         }

  117.         /* 开始处理每个新连接上的数据收发 */
  118.         bzero(buf, MAXBUF + 1);
  119.         strcpy(buf, "server->client");
  120.         /* 发消息给客户端 */
  121.         len = SSL_write(ssl, buf, strlen(buf));

  122.         if (len <= 0) {
  123.             printf
  124.                 ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  125.                  buf, errno, strerror(errno));
  126.             goto finish;
  127.         } else
  128.             printf("消息'%s'发送成功,共发送了%d个字节!\n",
  129.                    buf, len);

  130.         bzero(buf, MAXBUF + 1);
  131.         /* 接收客户端的消息 */
  132.         len = SSL_read(ssl, buf, MAXBUF);
  133.         if (len > 0)
  134.             printf("接收消息成功:'%s',共%d个字节的数据\n",
  135.                    buf, len);
  136.         else
  137.             printf
  138.                 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  139.                  errno, strerror(errno));
  140.         /* 处理每个新连接上的数据收发结束 */
  141.       finish:
  142.         /* 关闭 SSL 连接 */
  143.         SSL_shutdown(ssl);
  144.         /* 释放 SSL */
  145.         SSL_free(ssl);
  146.         /* 关闭 socket */
  147.         close(new_fd);
  148.     }

  149.     /* 关闭监听的 socket */
  150.     close(sockfd);
  151.     /* 释放 CTX */
  152.     SSL_CTX_free(ctx);
  153.     return 0;
  154. }
复制代码

客户端源代码如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>
  10. #include <openssl/ssl.h>
  11. #include <openssl/err.h>

  12. #define MAXBUF 1024

  13. void ShowCerts(SSL * ssl)
  14. {
  15.     X509 *cert;
  16.     char *line;

  17.     cert = SSL_get_peer_certificate(ssl);
  18.     if (cert != NULL) {
  19.         printf("数字证书信息:\n");
  20.         line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
  21.         printf("证书: %s\n", line);
  22.         free(line);
  23.         line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
  24.         printf("颁发者: %s\n", line);
  25.         free(line);
  26.         X509_free(cert);
  27.     } else
  28.         printf("无证书信息!\n");
  29. }
  30. /************关于本文档********************************************
  31. *filename: ssl-client.c
  32. *purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是客户端例子
  33. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  34. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  35. *date time:2007-02-02 20:10
  36. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  37. * 但请遵循GPL
  38. *Thanks to:Google
  39. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  40. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  41. *********************************************************************/
  42. int main(int argc, char **argv)
  43. {
  44.     int sockfd, len;
  45.     struct sockaddr_in dest;
  46.     char buffer[MAXBUF + 1];
  47.     SSL_CTX *ctx;
  48.     SSL *ssl;

  49.     if (argc != 3) {
  50.         printf
  51.             ("参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  52.              argv[0], argv[0]);
  53.         exit(0);
  54.     }

  55.     /* SSL 库初始化,参看 ssl-server.c 代码 */
  56.     SSL_library_init();
  57.     OpenSSL_add_all_algorithms();
  58.     SSL_load_error_strings();
  59.     ctx = SSL_CTX_new(SSLv23_client_method());
  60.     if (ctx == NULL) {
  61.         ERR_print_errors_fp(stdout);
  62.         exit(1);
  63.     }

  64.     /* 创建一个 socket 用于 tcp 通信 */
  65.     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  66.         perror("Socket");
  67.         exit(errno);
  68.     }
  69.     printf("socket created\n");

  70.     /* 初始化服务器端(对方)的地址和端口信息 */
  71.     bzero(&dest, sizeof(dest));
  72.     dest.sin_family = AF_INET;
  73.     dest.sin_port = htons(atoi(argv[2]));
  74.     if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
  75.         perror(argv[1]);
  76.         exit(errno);
  77.     }
  78.     printf("address created\n");

  79.     /* 连接服务器 */
  80.     if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
  81.         perror("Connect ");
  82.         exit(errno);
  83.     }
  84.     printf("server connected\n");

  85.     /* 基于 ctx 产生一个新的 SSL */
  86.     ssl = SSL_new(ctx);
  87.     SSL_set_fd(ssl, sockfd);
  88.     /* 建立 SSL 连接 */
  89.     if (SSL_connect(ssl) == -1)
  90.         ERR_print_errors_fp(stderr);
  91.     else {
  92.         printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
  93.         ShowCerts(ssl);
  94.     }

  95.     /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
  96.     bzero(buffer, MAXBUF + 1);
  97.     /* 接收服务器来的消息 */
  98.     len = SSL_read(ssl, buffer, MAXBUF);
  99.     if (len > 0)
  100.         printf("接收消息成功:'%s',共%d个字节的数据\n",
  101.                buffer, len);
  102.     else {
  103.         printf
  104.             ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
  105.              errno, strerror(errno));
  106.         goto finish;
  107.     }
  108.     bzero(buffer, MAXBUF + 1);
  109.     strcpy(buffer, "from client->server");
  110.     /* 发消息给服务器 */
  111.     len = SSL_write(ssl, buffer, strlen(buffer));
  112.     if (len < 0)
  113.         printf
  114.             ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  115.              buffer, errno, strerror(errno));
  116.     else
  117.         printf("消息'%s'发送成功,共发送了%d个字节!\n",
  118.                buffer, len);

  119.   finish:
  120.     /* 关闭连接 */
  121.     SSL_shutdown(ssl);
  122.     SSL_free(ssl);
  123.     close(sockfd);
  124.     SSL_CTX_free(ctx);
  125.     return 0;
  126. }
复制代码

编译程序用下列命令:
gcc -Wall ssl-client.c -o client
gcc -Wall ssl-server.c -o server

运行程序用如下命令:
./server 7838 1 cacert.pem privkey.pem
./client 127.0.0.1 7838

用下面这两个命令产生上述cacert.pem和privkey.pem文件:
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

具体请参考 “OpenSSL体系下使用密钥数字证书等”

如果想对SSL有更深入的了解,请学习计算机安全相关的内容,尤其是非对称加密技术。
如果想对SSL库的源代码有深入学习,请去 www.openssl.org 下载源码来阅读。

或者阅读“SSL连接建立过程分析”其目录和链接如下:

SSL连接建立过程分析(1)
1. 应用程序接口
1.1 SSL初始化
1.2 建立SSL新连接
1.3 SSL通信
1.4 SSL释放
2. SSL实现分析
2.1 SSL_load_error_strings
2.2 SSLeay_add_ssl_algorithms()
2.3 SSL23_server_method()
2.4 SSL23_client_method()
2.5 SSL_CTX_new ()
SSL连接建立过程分析(2)
2.6 SSL_CTX_set_default_passwd_cb[_userdata]()
2.7 SSL_CTX_use_certificate_file()
2.8 SSL_CTX_use_PrivateKey_file()
2.9 SSL_CTX_check_private_key()
2.10 SSL_new
2.11 SSL_set_fd
SSL连接建立过程分析(3)
2.12 SSL_accept
SSL连接建立过程分析(4)
2.13 SSL_connect
SSL连接建立过程分析(5)
2.14 SSL_read
SSL连接建立过程分析(6)
2.15 SSL_write
作者: zhoulifa    时间: 2007-02-04 15:53
标题: Linux网络编程一步一步学-select详解
select系统调用是用来让我们的程序监视多个文件句柄(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。

文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:
int socket(int domain, int type, int protocol);

当然,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr,0就是stdin,1就是stdout,2就是stderr。
比如下面这两段代码都是从标准输入读入9个字节字符:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. int main(int argc, char ** argv)
  5. {
  6.         char buf[10] = "";
  7.         read(0, buf, 9); /* 从标准输入 0 读入字符 */
  8.         fprintf(stdout, "%s\n", buf); /* 向标准输出 stdout 写字符 */
  9.         return 0;
  10. }
复制代码

/* **上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. int main(int argc, char ** argv)
  5. {
  6.         char buf[10] = "";
  7.         fread(buf, 9, 1, stdin); /* 从标准输入 stdin 读入字符 */
  8.         write(1, buf, strlen(buf));
  9.         return 0;
  10. }
复制代码

继续上面说的select,就是用来监视某个或某些句柄的状态变化的。select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:
struct timeval {
             long    tv_sec;         /* seconds */
             long    tv_usec;        /* microseconds */
         };

第2、3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:
fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */
struct timeval tv; /* 申明一个时间变量来保存时间 */
int ret; /* 保存返回值 */
FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
if(ret < 0) perror("select");/* 这说明select函数出错 */
else if(ret == 0) printf("超时\n"); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
    printf("ret=%d\n", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
        /* 读取socket句柄里的数据 */
        recv(...);
    }
}

注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1。比如我们创建了3个句柄:
/************关于本文档********************************************
*filename: Linux网络编程一步一步学-select详解
*purpose: 详细说明select的用法
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-02-03 19:40
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to:Google
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/
int sa, sb, sc;
sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);

FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);

在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:
int maxfd = 0;
if(sa > maxfd) maxfd = sa;
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;

然后调用select函数:
ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:
FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
if(ret < 0) perror("select");/* 出错 */
else if(ret == 0) printf("超时\n"); /* 在我们设定的时间tv内,用户没有按键盘 */
else { /* 用户有按键盘,要读取用户的输入 */
    scanf("%s", buf);
}

详细编程请参见:
Linux网络编程一步一步学-异步通讯聊天程序select
作者: marion    时间: 2007-02-05 01:21
佩服佩服!!请问Lifa兄,想学C语言,应该如何实施?谢谢!
作者: converse    时间: 2007-02-05 12:46
感谢楼主的辛苦贡献,本帖已经收录到C版置顶的版贴中,以后会有更多的看到此贴并且从中获益,再次感谢楼主.
C版版贴见:
http://bbs.chinaunix.net/viewthr ... &extra=page%3D1

收录在其中的网络编程部分.
作者: langue    时间: 2007-02-05 12:52
--

为了表彰 zhoulifa 会员对本版做出的贡献,本帖将在 C/C++ 版置顶一个月,同时帖子将被永久收录进版帖。

--
作者: gaosen2463    时间: 2007-02-05 14:45
真的好强啊
作者: zhoulifa    时间: 2007-02-05 14:54
原帖由 marion 于 2007-2-5 01:21 发表
佩服佩服!!请问Lifa兄,想学C语言,应该如何实施?谢谢!


为了回答你这个问题,我今天专门写了这篇文章:
我是这样学习Linux下C语言编程的-完全认识GNU C 语言库glibc
准备写一系列“我是这样学习Linux下C语言编程的”,你先看看,如果觉得还行、值得推荐给大家,那我再把这一系列文章搬到这里来。
作者: 1jjk    时间: 2007-02-05 15:02
原帖由 zhoulifa 于 2007-2-5 14:54 发表


为了回答你这个问题,我今天专门写了这篇文章:
我是这样学习Linux下C语言编程的-完全认识GNU C 语言库glibc
准备写一系列“我是这样学习Linux下C语言编 ...


发哥学习的精神很好啊
不过应该比这个再基础些,估计他的意思是Linux下最最基础的那种
作者: marshal97    时间: 2007-02-05 15:59
PF
强贴留名
作者: dzt_tomdu    时间: 2007-02-06 13:08
谢谢,正想用这方面的资料那。十分感谢
作者: arthasg    时间: 2007-02-22 22:40
楼主大哥,你在程序中多次使用inet_aton函数,似乎这个函数已经被inet_pton取代了呢.
作者: zgqmm    时间: 2007-02-25 09:42
强贴,正学习ing
作者: zhoulifa    时间: 2007-02-26 08:51
原帖由 arthasg 于 2007-2-22 22:40 发表
楼主大哥,你在程序中多次使用inet_aton函数,似乎这个函数已经被inet_pton取代了呢.

inet_pton是比inet_aton之类的函数要方便一些,但用inet_aton没什么不好,目前还是有不少在用的
作者: 醒目    时间: 2007-02-26 17:22
遇到高人了,收藏,慢慢看
作者: converse    时间: 2007-02-27 22:27
今天晚上花时间把大部分看过了,感觉对于初学者而言是非常不错的教程,作者手把手的一步一步给出网络编程的步骤,每一个教程都用相关的例子,赞!
作者: akei48    时间: 2007-02-28 00:58
看贴留名.
作者: akei48    时间: 2007-02-28 14:37
自己发出去的广播是不是自己捕捉不到啊?
作者: 源方    时间: 2007-03-08 10:43
做为附件传上来吧
作者: iCharlene    时间: 2007-04-08 21:53
我一直在期待附件~~~
作者: cofish    时间: 2007-04-11 15:26
已经全部收录。
谢谢
作者: aobai    时间: 2007-04-23 13:17
mark
作者: xhl    时间: 2007-04-23 14:16
原帖由 zhoulifa 于 2007-2-26 08:51 发表

inet_pton是比inet_aton之类的函数要方便一些,但用inet_aton没什么不好,目前还是有不少在用的


inet_aton 这个方法是非线程安全的,不可重入。

如果你用这个方法实现自己的threadsafe-lib的话, 肯定会出问题.
不过普通的应用, inet_aton似乎比inet_pton更方便些。
作者: wrennywang    时间: 2007-06-08 17:28
学习ing,发现1个小问题
在好几段代码中有
if (argv[1])
        myport = atoi(argv[1]);
    else
        myport = 7838;

    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 2;
if (argv[3])
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;

按说,argv[0]应该是程序运行路径名
argv[1]是程序名后第一个参数,对应的 应该是 ip.....
argv[2]是程序名后第二个参数..................................
作者: chary8088    时间: 2008-11-17 17:29
MARK,这个帖子太好了
作者: alaulong    时间: 2008-12-05 13:27
不错,学习下网络编程.
作者: c/unix    时间: 2008-12-05 14:40
提示: 作者被禁止或删除 内容自动屏蔽
作者: 无花有果    时间: 2011-01-07 12:19
本来是寻找ssl的编程的,没有想到可以有参考性的那么多。不过作者貌似很久不上论坛了……

顶起!
作者: iCymbidium    时间: 2011-01-07 12:57
牛人如此多,我要多喝牛奶,也变牛人!
作者: jiangyingji    时间: 2011-01-09 19:46
学习。。。
作者: eliry    时间: 2011-01-10 10:49
楼主真有前瞻眼光,我现在打算在产品的代码里面加上IPv6支持,参阅下楼主文档,益处很多。谢谢!
作者: bzhk924    时间: 2012-05-21 19:43
if ( inet_pton(AF_INET6, argv[1], &dest.sin6_addr) < 0 ) {                 // IPv6

        perror(argv[1]);

        exit(errno);

    }
严谨来说,小于等于0




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2