发信人: topot() 
整理人: wenbobo(2002-12-06 23:14:19), 站内信件
 | 
 
 
  
  
  
   isbase技术指南(二)
 
 用C语言编写Socket程序---入门篇
 
 本文的目的在于为初学者提供一个快速的入门指导,用来迅速熟悉用C语言来编写 Internet网络应用程序。本文假设读者已经具备了C语言的基本知识和语法,并且 读者有使用Uinx/Linux的经验。尽管Uinx/Linux的Socket编程与在Windows下的有 一些不同的地方,但是在此我并不想展开。另外,本文所有的程序都在Red Hat  5.2下编译通过,并且在glibc 2.0.7和libc 5.3.12两种环境下都没有问题。现在 就开始我们的教程吧:)。
 
 对一个程序员而言,sockets和底层的文件描述符非常类似(可以在sockets里使 用read()和write()函数),尽管建立一个socket比打开,读取和写入一个文件更 为麻烦,但这是由于网络连接比单纯的本地硬盘的读写复杂的多所造成的。
 
 通常,sockets用来实现客户机/服务器对。服务器的任务是监听某个特定的端口 ,当接收到客户端的服务请求时完成相应的服务;客户机的任务是请求服务器完 成事先设定好的服务。
 
 作为入门级的文章,我们在这里不会使用所有的socket类型和功能,但是我们会 向读者提供足够的信息。现在,就让我们开始吧。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 建立一个socket:socket()
 
 你所要学的socket编程的第一件事就是用socket()建立一个socket:
 
 - -------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int socket(int af, int type, int protocol)
 
 - ------
 
 'int af'代表地址族或者称为socket所代表的域,通常有两个选项:
     AF_UNIX - 只在单机上使用。
     AF_INET - 可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。
  
 'int type'代表你所使用的连接类型,通常也有两种情况:
     SOCK_STREAM - 用来建立面向连接的sockets,可以进行可靠无误的的数据传 输
     SOCK_DGRAM - 用来建立没有连接的sockets,不能保证数据传输的可靠性。 
 
 在本文中,我们着重使用AF_INET地址族和SOCK_STREAM连接类型。
 
 'int protocol'通常设定为0。这样的目的是使系统选择默认的由协议族和连接类 型所确定的协议。
 
 这个函数的返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设 定了相应的errno。
 
 - ------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int sockfd /* soon to be socket file descriptor */
 
 sockfd = socket(AF_INET, SOCK_STREAM, 0)
 /* error checking here */
 
 - ------
 
 如果执行成功,我们就拥有了一个socket的文件句柄,通过这个句柄就可以访问 Internet了。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 名字绑定socket: bind()
 
 下一步要完成的就是名字绑定工作了:
 
 - ------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int bind(int sockfd, struct sockaddr *name, int namelen)
 
 - ------
 
 在这个函数里,sockfd是从socket()调用得到的文件描述句柄。name是一个指向 sockaddr类型结构的一个指针。如果地址族被设定为AF_UNIX,这个类型的定义是 如下所示:
 
 - ------
 
 struct sockaddr {
 u_short sa_family;
 char sa_data[14];
 };
 
 - ------
 
 在这个结构种,name.sa_family应当被设定为AF_UNIX。name.sa_data应当包含最 长为14个字节的文件名,这个文件名用来分配给socket。namelen给出了文件名的 具体长度。
 
 - ------
 
 #include <sys/types.h>
 #include <sys.socket.h>
 
 struct sockaddr name;
 int sockfd;
 
 name.sa_family = AF_UNIX;
 strcpy(name.sa_data, "/tmp/whatever");
 
 sockfd = socket(AF_UNIX, SOCK_STREAM, 0)
 /* error checking code here */
 
 bind(sockfd, &name, strlen(name.sa_data) + sizeof(name.sa_family)
 /* error checking code here */
 
 - ------
 
 如果调用成功,则返回值为0,如果调用失败返回值为-1,并设定相应的错误代码 errno。
 
 现在,让我们在使用另一种结构,它是在使用AF_INET地址族的时候使用的。
 
 - -----
 
 struct sockaddr_in {
 short int sin_family; /* Address family */
 unsigned short int sin_port; /* Port number */
 struct in_addr sin_addr; /* Internet address */
 unsigned char sin_zero[8]; /* Same size as struct sockaddr */
 };
 
 - ------
 
 看起来它比前一个大多了,但要掌握它并不十分困难。
 
 - ------
 
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <errno.h>
 
 int sockfd, port = 23;
 struct sockaddr_in my_addr;
 
 if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
 {
 printf("Socket Error, %d\n", errno);
 exit(1);
 }
 
 my_addr.sin_family = AF_INET; /* host byte order */
 my_addr.sin_port = htons(port); /* see man htons for more information  */
 my_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* get our address */
 bzero(&(my_addr.sin_zero), 8); /* zero out the rest of the space */
 
 if((bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))  == -1)
 {
 printf("Bind Error, %d\n", errno);
 close(sockfd);
 exit(1);
 }
 
 - ------
 
 现在,如果没有问题的话,我们建立的socket就有一个名字了!相反,如果不成 功,它会设定相应的错误代码,并使程序退出。这里需要说明的是,如果你的计 算机不想和别人的计算机连接,那么完全没有必要使用bind()。对于端口的绑定 ,在服务器而言是不合适的,它只应该在客户机上实现。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 远程连接: connect()
 
 这是和别的计算机连接所必须的一步。
 
 - ------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 
 
 - ------
 
 sockfd是我们建立的文件描述句柄,serv_addr是一个sockaddr结构,包含目的的 地址和端口号,addrlen 被设定为sockaddr结构的大小。
 
 - ------
 
 #include <string.h> 
 #include <sys/types.h> 
 #include <sys/socket.h> 
 
 #define DEST_IP "132.241.5.10"
 #define DEST_PORT 23
 
 main()
 {
 int sockfd;
 struct sockaddr_in dest_addr; /* will hold the destination addr */
 
 sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! * /
 
 dest_addr.sin_family = AF_INET; /* host byte order */
 dest_addr.sin_port = htons(DEST_PORT); /* short, network byte order */ 
 dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
 bzero(&(dest_addr.sin_zero), 8); /* zero the rest of the struct */
 
 connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr) );
 /* error checking code here */
 /* more code 
 .
 .
 .
 */
 }
 
 - ------
 
 同样,connect()在调用返回后,如果返回值为0则表明成功,如果是1则说明有错 误,并且同时设定了相应的错误代码。由于我们现在不关心具体和那个端口连接 ,所以在上面的例程里我们没有调用了bind()函数。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 监听: listen()
 
 当我们需要建立一个服务器的时候,我们需要有一种手段来监听输入的请求,而 listen()函数正是提供这个功能。
 
 - ------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int listen(int sockfd, int backlog);
 
 - ------
 
 参数backlog是指一次可以监听多少个连接
 
 它的调用返回结果和上述的几个函数是一样的,这里就不多说了。
 值得一提的是,在这里,我们需要建立一个绑定,用来接收特定端口的服务请求 。
 
 - ------
 
 socket(); /* to create out socket file descriptor */
 bind(); /* to give our socket a name */
 listen(); /* listen for connection */
 
 - ------
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 接受连接: accept()
 
 好了,现在开始实质性的工作了。当有人试图从我们打开的端口登陆进来时我们 应该响应他,这个时候就要用到accept()函数了。
 
 - ------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int accept(int sockfd, void *addr, int *addrlen);
 
 - ------
 
 函数调用所需的参数都是我们所熟悉的:)
 
 - ------
 
 #include <string.h> 
 #include <sys/types.h> 
 #include <sys/socket.h> 
 
 #define MYPORT 1500 /* the port users will be connecting to */
 #define BACKLOG 5 /* how many pending connections queue will hold */
 
 main()
 {
 int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
  struct sockaddr_in my_addr; /* my address information */
 struct sockaddr_in their_addr; /* connector's address information */
 int sin_size;
 
 sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! * /
 
 my_addr.sin_family = AF_INET; /* host byte order */
 my_addr.sin_port = htons(MYPORT); /* short, network byte order */
 my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
 bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
 
 /* did you remember your error checking? */
 bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
 
 listen(sockfd, BACKLOG);
 
 sin_size = sizeof(struct sockaddr_in);
 new_fd = accept(sockfd, &their_addr, &sin_size);
 
 - ------
 
 这里我们要注意的是:我们用new_fd来完成所有的接收和发送的操作。如果在只 有一个连接的情况下你可以关闭原来的sockfd用来防止更多的输入请求。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 输入和输入的完成: send() and recv()
 
 在我们完成了上述的工作后,最后一步就是传输数据了:)。在这里,我们通过se nd()和recv()来实现。
 
 - ------
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 int send(int sockfd, const void *msg, int len, int flags);
 int recv(int sockfd, void *buf, int len, unsigned int flags);
 
 - ------
 send():
 sockfd - socket file descriptor
 msg - message to send
 len - size of message to send
 flags - read 'man send' for more info, set it to 0 for now :)
 
 recv():
 sockfd - socket file descriptor
 buf - data to receive
 len - size of buf
 flags - same as flags in send()
 
 send() 例程:
 - ------
 
 char *msg = "Hey there people";
 int len, send_msg;
 
 /* code to create(), bind(), listen() and accept() */ 
 
 len = strlen(msg);
 bytes_sent = send(sockfd, msg, len, 0);
 
 - ------
 recv() 例程:
 - ------
 
 char *buf;
 int len, recv_msg;
 
 /* code to create(), bind(), listen() and accept() */
 
 len = strlen(buf);
 recv_msg = recv(sockfd, buf, len, 0);
 
 - ------
 
 如果你使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实 现数据传输。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 最后一步: close() and shutdown()
 
 当传输结束时,应当关闭连接。
 
 - ------
 
 #include <stdio.h>
 
 /* all you code */
 
 close(sockfd);
 
 - ------
 
 更保险的方法是用shutdown()来关闭连接。
 
 - ------
 
 int shutdown(int sockfd, int how)
 
 - ------
 
 参数how的选择:
 1 - 不允许接收更多的数据
 2 - 不允许发送更多的数据
 3 - 不允许接收和发送更多的数据(和close()一样)
 
 一切就是这么简单:)
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 你是谁: getpeerbyname()
 
 可能你还想知道是谁正在和你连接,那么看下面:
 
 - ------
 
 #include <sys/socket.h>
 
 int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
 
 - ------
 
 参数addr是一个指向'struct sockaddr'或者'struct sockaddr_in'的指针。
 
 如果执行成功,我们就得到了对方的地址了,然后用inet_ntoa()用gethostbyad dr()来得到对方更多的信息。如果还想知道更多的,请参阅RFC 1413。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 我是谁: gethostname()
 
 - ------
 
 #include <unistd.h>
 
 int gethostname(char *hostname, size_t size);
 
 - ------
 hostname是一个存放主机名字的字符数组
 
 返回的hostname可以作为gethostbyname()的参数,这样又可以得到自己的IP地址 了。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 我的IP是多少? 
 
 现在把我们所学的集中起来,逐步就可以完成一个实用的程序了。还是来研究一 下如下的情况:
 
 $ telnet microsoft.com
 Trying 206.163.24.176 (not the real address but I'm too lazy to try :) )
 
 我们看到,telnet程序所做的第一件事情是域名解析!这个工作是由gethostbyn ame()完成的。
 
 - ------
 
 #include <netdb.h> 
 
 struct hostent *gethostbyname(const char *name);
 
 - ------
 
 一个特殊的结构hostent:
 
 - ------
 
 struct hostent {
 char *h_name;
 char **h_aliases;
 int h_addrtype;
 int h_length;
 char **h_addr_list;
 };
 #define h_addr h_addr_list[0]
 
 - ------
 这个结构可以被分为:
 
 h_name - 正式的机器名
 h_aliases - 一个以NULL结尾的字符串,表示机器的别名。 
 h_addrtype - 地址所使用的类型,通常是AF_INET。 
 h_length - 用字节数表示的地址长度。
 h_addr_list - 一个以0结尾的数组,表示机器的网络地址,以网络的字节顺序排 列。 
 h_addr - 在h_addr_list里的第一个地址。
 
 gethostbyname()返回一个指向hostent结构的指针或者是一个NULL指针表示错误 (尽管如此,错误代码没有设定!)。
 
 现在我们就来完成我们的DNS程序:
 
 - ------
 
 #include <stdio.h> 
 #include <stdlib.h> 
 #include <errno.h> 
 #include <netdb.h> 
 #include <sys/types.h>
 #include <netinet/in.h> 
 
 int main(int argc, char *argv[])
 {
 struct hostent *h;
 
 if (argc != 2) { /* error checking on the command line */
 fprintf(stderr,"Usage: getip <host name>\n");
 exit(1);
 }
 
 if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */
 herror("gethostbyname");
 exit(1);
 }
 
 printf("Host name : %s\n", h->h_name);
 printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr))); 
 
 return 0;
 }
 
 - ------
 
 用gcc -o getip getip.c就可以了完成了。
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 客户机和服务器程序
 
 好了,让我们用一个小的客户机-服务器应用来结束本文的讨论。
 
 这个小程序的目的就是让一个用户和服务器连接,接收预先定义好的数据,然后 断开。不过这个程序中有很多错误需要你来修改,作为你的作业来完成:)。
 
 - ------
 
 <++> socket/server.c
 /* SERVER PROGRAM */
 #include <stdio.h> 
 #include <stdlib.h> 
 #include <errno.h> 
 #include <string.h> 
 #include <sys/types.h> 
 #include <netinet/in.h> 
 #include <sys/socket.h> 
 #include <sys/wait.h> 
 
 #define PORT 1500 /* the port users will be connecting to */
 
 #define BACKLOG 5 /* how many pending connections queue will hold */
 
 main()
 {
 int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
  struct sockaddr_in my_addr; /* our address information */
 struct sockaddr_in their_addr; /* their address information */
 int sin_size;
 
 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 /* remember to error check (-1 on error) */
 
 my_addr.sin_family = AF_INET; /* host byte order */
 my_addr.sin_port = htons(PORT); /* short, network byte order */
 my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
 bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
 
 bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
 
 listen(sockfd, BACKLOG)
 
 
 while(1) { /* start out accept() loop */
 sin_size = sizeof(struct sockaddr_in);
 new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)
 printf("server: got connection from %s\n", inet_ntoa(their_addr.sin_ad dr));
 fork(); /* this is the child process */
 send(new_fd, "Hello, world!\n", 14, 0)
 close(new_fd);
 exit(0);
 
 while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
 }
 }
 
 /* END SERVER PROGRAM, REMEMBER TO DO YOUR ERROR CHECKING */
 <-->
 <++> socket/client.c
 /* CLIENT PROGRAM */
 
 #include <stdio.h> 
 #include <stdlib.h> 
 #include <errno.h> 
 #include <string.h> 
 #include <netdb.h> 
 #include <sys/types.h> 
 #include <netinet/in.h> 
 #include <sys/socket.h> 
 
 #define PORT 1500 /* the port client will be connecting to */
 
 #define MAXDATASIZE 100 /* max number of bytes we can get at once */
 
 int main(int argc, char *argv[])
 {
 int sockfd, numbytes; 
 char buf[MAXDATASIZE];
 struct hostent *he;
 struct sockaddr_in their_addr; /* connector's address information */
 
 if (argc != 2) {
 fprintf(stderr,"Usage: client <host name>\n");
 exit(1);
 }
 
 he = gethostbyname(argv[1]); /* get the host info */
 /* did you check for errors? */
 
 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 
 their_addr.sin_family = AF_INET; /* host byte order */
 their_addr.sin_port = htons(PORT); /* short, network byte order */
 their_addr.sin_addr = *((struct in_addr *)he->h_addr);
 bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */
 
 connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr ));
 
 numbytes = recv(sockfd, buf, MAXDATASIZE, 0);
 
 buf[numbytes] = '\0';
 
 printf("Received: %s",buf);
 
 close(sockfd);
 
 return 0;
 }
 
 /* END CLIENT...YOU CHECKED FOR ERRORS RIGHT? :) */
 <-->
 
 =+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+==+== +==+==+=
 
 以上是关于SOCKET编程的一些入门的指导,如果还想深入了解的话可以从以下的 地方找到资料:
 
 -------
 
 Internetworking with TCP/IP, volumes I-III by Douglas E. Comer and Dav id
 L. Stevens. Published by Prentice Hall. Second edition ISBNs: 0-13-468 505-9, 
 0-13-472242-6, 0-13-474222-2. There is a third edition of this set whi ch
 covers IPv6 and IP over ATM. 
 
 Using C on the UNIX System by David A. Curry. Published by O'Reilly &
  Associates, Inc. ISBN 0-937175-23-4.
 
 TCP/IP Network Administration by Craig Hunt. Published by O'Reilly &
 Associates, Inc. ISBN 0-937175-82-X. 
 
 TCP/IP Illustrated, volumes 1-3 by W. Richard Stevens and Gary R. Wrig ht.
 Published by Addison Wesley. ISBNs: 0-201-63346-9, 0-201-63354-X, 
 0-201-63495-3. 
 
 UNIX Network Programming by W. Richard Stevens/ Published by Prentice
  Hall. ISBN 0-13-949876-1. 
 
 --------
 相关的RFC的资料:
 -------
 
 RFC-768 -- The User Datagram Protocol (UDP)
 (ftp://nic.ddn.mil/rfc/rfc768.txt)
 
 RFC-791 -- The Internet Protocol (IP)
 (ftp://nic.ddn.mil/rfc/rfc791.txt)
 
 RFC-793 -- The Transmission Control Protocol (TCP)
 (ftp://nic.ddn.mil/rfc/rfc793.txt)
 
 RFC-854 -- The Telnet Protocol
 (ftp://nic.ddn.mil/rfc/rfc854.txt)
 
 RFC-951 -- The Bootstrap Protocol (BOOTP)
 (ftp://nic.ddn.mil/rfc/rfc951.txt)
 
 RFC-1350 -- The Trivial File Transfer Protocol (TFTP)
 (ftp://nic.ddn.mil/rfc/rfc1350.txt)
 
 -------
 
 
 ****************************************************
 by Lamagra             [email protected]>
 repaired by solo       <[email protected]>
 Copyright by isbase    http://www.isbase.com/
 ****************************************************
 
 如要转载请保持文章的完整性
 欢迎访问我们的站点http://www.isbase.com
 绿色兵团给你安全的保证
  
  
  
 
 ---------------------------------------------------------------------- ----------
  
  
 ©1999 Isbase Corporation. All rights reserved. 
 
  -- 由爱故生忧
 由爱故生怖
 若离于爱者
 无忧亦无怖
  ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.109.30.248]
  | 
 
 
 |