CSDN博客

img smallboy_5

Sockets

发表于2008/10/3 22:51:00  556人阅读

关于Socket编程,在《Linux从入门到精通》里有简单的介绍,更详细的可以参考
《UNIX网络编程 卷1:联网的API:套接字与XTI 第2版》清华影印版,其中还讲了
线程(Thread)编程。极好的参考书,可惜没人把它翻译过来。
胡淑瑜翻译了一篇“网络编程”,我把它收集进来了。
如有更新,请参考胡先生的个人主页
 
________________________________________________________________________________
|                                   版权声明                                   |
|                                                                              |
|  1、本文可以转载、修改及引用,但请保留本声明和其后所付英文原文。             |
|  2、任何情况下,作者和译者姓名不可删除。                                     |
|  3、任何情况下,本文不可进入商业领域。                                       |
|                                                                              |
|                                                  胡淑瑜                      |
|                                        (husuyu@linux.cqi.com.cn)             |
|                                                                              |
|                                                 1998年11月                   |
|______________________________________________________________________________|

第59章
目录
   网络程序设计
      端口(Ports)和套接字(Sockets)
      套接字程序设计
         socket()系统调用(System Call)
         bind()系统调用
         listen()系统调用
         accept()系统调用
         setsockopt和getsockopt系统调用
         connect()系统调用
      程序清单59.1服务器端(Server Side)面向套接字(socket-oriented)协议           
      程序清单59.2客户端函数(The lient Side function)
         无连接(Connectionless)套接字程序设计
      程序清单59.3服务端
      注意
      记录(Record)和文件锁定(Locking)
      进程间通信
      小结
-------------------------------------------------------------------------------
                                   --第59章--
                           
                                  网络程序设计      
                                
作者 Kamran Husain,Tim Parker
译者 胡淑瑜

本章内容

   端口和套接字
   套接字程序设计
   记录和文件锁定
   进程间通信
   
阅读本章需你具有如下网络程序设计的基本概念

   端口和套接字
   记录和文件锁定
   进程间通信
   
   本文不可能在几页之内就能与你说清网络程序设计的全部内容.事实上,一本第一卷就有
800页的专门描述网络程序设计的参考书是最有用的.如果你真想进行网络编程的话,你需要
具有编译器,TCP/IP和网络操作系统的大量经验.另外,你还需有极大的耐心.
   
   欲知TCP/IP详细内容,请参见Tim Parker所著之< > (Sams Publish-
ing).

                                  端口和套接字
                                 
   网络程序设计全靠套接字接受和发送信息.尽管套接字这个词好象显得有些神秘,但其实
这个概念极易理解.

   大多数网络应用程序使用两个协议:传输控制协议(TCP)和用户数据包协议(UDP).他们都
使用一个端口号以识别应用程序.端口号为主机上所运行之程序所用,这样就可以通过号码
象名字一样来跟踪每个应用程序.端口号让操作系统更容易的知道有多少个应用程序在使用
系统,以及哪些服务有效.

   理论上,端口号可由每台主机上的管理员自由的分配.但为了更好的通信通常采用一些约
定的协议.这些协议使能通过端口号识别一个系统向另一个系统所请求的服务的类型.基于
如此理由,大多数系统维护一个包含端口号及它们所提供哪些服务的文件.

   端口号被从1开始分配.通常端口号超出255的部分被本地主机保留为私有用途.1到255之
间的号码被用于远程应用程序所请求的进程和网络服务.每个网络通信循环地进出主计算机
的TCP应用层.它被两个所连接的号码唯一地识别.这两个号码合起来叫做套接字.组成套接
字的这两个号码就是机器的IP地址和TCP软件所使用的端口号.

   因为网络通讯至少包括两台机器,所以在发送和接收的机器上都存在一个套接字.由于每
台机器的IP地址是唯一的,端口号在每台机器中也是唯一的,所以套接字在网络中应该是唯
一的.这样的设置能使网络中的两个应用程序完全的基于套接字互相对话.

   发送和接收的机器维护一个端口表,它列出了所有激活的端口号.两台机器都包括一个进
程叫做绑定,这是每个任务的入口,不过在两台机器上恰恰相反.换句话说,如果一台机器的
源端口号是23而目的端口号被设置成25,那么另一台机器的源端口号设置成25目的端口号设
置成23.

                                  套接字程序设计 
                 
   Linux支持伯克利(BSD)风格的套接字编程.它同时支持面向连接和不连接类型的套接字.
在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接.再不连接通讯中数
据被作为信息的一部分被交换.无论那一种方式,服务器总是最先启动,把自己绑定(Banding
)在一个套接字上,然后侦听信息.服务器究竟怎样试图去侦听就得依靠你编程所设定的连接
的类型了.

   你需要了解一些系统调用
   
      socket()
      
      bind()
      
      listen()
      
      accept()
      
      setsockopt()和getsockopt()
      
      connect()
      
      sendto()
      
      recvfrom()
      
   我们将在以下的例子中使用这些系统调用.
   
                                  socket()系统调用
               
   socket()系统调用为客户机或服务器创建一个套接字,套接字函数在如下定义:

   #include
   
   #include
   
   int socket(int family, int type, int protocol)     
   
   在Linux中family=AF_UNIX.type可以是SOCK_STREAM它是可靠的虽然通讯速度较慢,也可
以是SOCK_DGRAM它通讯速度较快但不可靠.如果type=SOCK_STREAM那么protocol=IPPROTO_
TCP.如果type=SOCK_DGRAM那么protocol=IPPROTO_UDP.

   如果出错,函数将返回-1.否则返回一个套接字描述符你可以在程序后面的调用中通过套
接字描述符使用这个套接字.

   套接字创建时没有指定名字.客户机用套接字的名字读写它.这就是如下绑定函数所要做
之事.

                                  bind()系统调用
               
   bind()系统调用为没有名字的套接字分配一个名字.绑定函数是这样定义的:

   #include
   
   #include
   
   int bind(int sockfd, struct sockaddr *saddr, int addrlen)
   
   第一个参数是一个套接字描述符.第二个参数是名字所用的一个结构.第三个参数是结构
的大小.

   现在你已经为你的客户机或服务器限定了一个地址,你可以connect()服务器或是在服务
器上侦听了.如果你的程序是一个服务器,那么它把自己设置为侦听然后等待连接.让我们来
看看哪些函数能让你试图这样做.

                                  listen()系统调用
               
   listen()系统调用被服务器所使用.下面有它的定义:
   
   #include
   
   #include
   
   int listen(int sockfd, int backlog);
   
   sockfd是套接字描述符.backlog是在一时间内尚未被决定是否拒绝的连接的号码.一般
使用标准值5.如发生错误则返回值小于1.

   如果这个调用成功,你就已经可以接受连接了.
   
                                  accept()系统调用
               
   accept()调用被服务器用于接受任何从客户机connect()调用所引入的信息.必须明白的
是,如果没有接受到连接这个函数将不返回任何值.它是象这样定义的:

   #include
   
   #include
   
   int accept(int sockfd, struct sockaddr *peeraddr, int addrlen)
   
   除peeraddr指向发出连接请求的客户机的信息外,其它参数和bind()系统调用的相同.在
信息引入的基础上,peeraddr所指向的结构的域被填上相应的值.

                      setsockopt()和getsockopt()系统调用 
   
   Linux所提供的socket库含有一个错误(bug).此错误表现为你不能为一个套接字重新启
用同一个端口号,即使在你正常关闭该套接字以后.例如,比方说,你编写一个服务器在一个
套接字上等待的程序.服务器打开套接字并在其上侦听是没有问题的.无论如何,总有一些原
因(不管是正常还是非正常的结束程序)使你的程序需要重新启动.然而重启动后你就不能把
它绑定在原来那个端口上了.从bind()系统调用返回的错误代码总是报告说你试图连接的端
口已经被别的进程所绑定.

   问题就是Linux内核在一个绑定套接字的进程结束后从不把端口标记为未用.在大多数UN
IX系统中,端口可以被一个进程重复使用,甚至可以被其它进程使用.

   在Linux中绕开这个问题的办法是,但套接字已经打开但尚未有连接的时候用setsockopt
()系统调用在其上设定选项(options).setsockopt()调用设置选项而getsockopt()从给定
的套接字取得选项.

   这里是这些调用的语法:
   
   #include
   
   #include
   
   int getsockopt(int sockfd, int level, int name
   , char *value, int *optlen)
   
   int setsockopt(int sockfd, int level, int name
   , char *value, int *optlen)
   
   sockfd必须是一个已打开的套接字.level是函数所使用的协议标准(protocol level)(T
CP/IP协议使用IPPROTO_TCP,套接字标准的选项实用SOL_SOCKET),选项的名称(name)在套接
字说明书中(man page)有详细说明.*value指向为getsockopt()函数所获取的值或setsocko
pt()函数所设置的值的地址.optlen指针指向一个整数,该整数包含参数以字节计算的长度.
其值被getsockopt()设置且其值必须被程序员设定当使用一个经由setsockopt().

   选项的所有细节可以在使用手册中setsockopt的第二节(setsockopt(2))找到.

   现在我们再回到Linux的错误上来.当你打开一个套接字时必须同时用下面的代码段来调
用setsockopt()函数:

   #ifdef LINUX
   
   opt = 1; len = sizeof(opt);
   
   setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt
   ,&len);     
   
   #endif
   
   只有当你想让程序不光是在Linux系统下使用时,#ifdef和#endif描述才是必须的.有些U
NIX系统可能不支持或不需要SO_REUSEADDR标志.

                                  connect()系统调用
               
   connect()调用被在面向连接的系统中客户机连接服务器时使用.connect()调用必须被
用在bind()调用之后.它是这样定义的:

   #include
   
   #include
   
   int connect(int sockfd, struct sockaddr *servs
   addr, int addrlen)   
   
   除servsaddr外所有的参数都和bind调用相同,servsaddr指向客户机所连接的服务器的
信息.当接到请求后,accept调用为服务器创建一个新的套接字.然后服务器就可以fork()一
个新进程然后再去等待其它连接.在服务器端你可以象程序清单59.1所显示的那样编写代码

                                  程序清单59.1
               
                            面向套接字协议的服务器端  
         
#include    

#include    

#include 

#include 

#define MY_PORT 6545

main(int argc, char *argv[])

{

int sockfd, newfd;

int cpid; /* child id */

struct sockaddr_in servaddr;

struct sockaddr_in clientInfo;

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

    {

    myabort("Unable to create socket");

    }

#ifdef LINUX

opt = 1; len = sizeof(opt);

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

#endif

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

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_family = htons(MY_PORT);

/*

* The htonl (for a long integer) and htons (for short integer) convert

* a host oriented byte order * into a network order.

*/

if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0)

    {

    myabort("Unable to bind socket");

    }

listen(sockfd,5);

for (;;)

    {

    /* wait here */

    newfd=accept(sockfd,(struct sockaddr *)&clientInfo,

            sizeof(struct sockaddr);

    if (newfd < 0)

        {

        myabort("Unable to accept on socket");

        }

    if ((cpid = fork()) < 0)

        {

        myabort("Unable to fork on accept");

        }

    else if (cpid == 0) {  /* child */

        close(sockfd); /* no need for original */

        do_your_thing(newfd);

        exit(0);

        }

        close(newfd); /* in the parent */

}

   在面向连接的协议的程序中,服务器执行以下函数:
   
      调用socket()函数创建一个套接字.
      
      调用bind()函数把自己绑定在一个地址上
      
      调用listen()函数侦听连接
      
      调用accept()函数接受所有引入的请求
      
      调用read()函数获取引入的信息然后调用write()回答
      
   现在让我们来看看客户端所要做的事情,见程序清单59.2.

                                  程序清单59.2
               
                                   客户端函数
                
#include    

#include    

#include 

#include 

#define MY_PORT 6545

#define MY_HOST_ADDR "204.25.13.1"

int getServerSocketId()

{

    int fd, len;

    struct sockaddr_in   unix_addr;

                /* create a Unix domain stream socket */

    if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)

        {

        return(-1);

        }

#ifdef LINUX

opt = 1; len = sizeof(opt);

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

#endif

                /* fill socket address structurew/our address */

    memset(&unix_addr, 0, sizeof(unix_addr));

    unix_addr.sin_family = AF_INET;

    /* convert internet address to binary value*/

    unix_addr.sin_addr.s_addr = inet_addr(MY_HOST_ADDR);

    unix_addr.sin_family = htons(MY_PORT);

    if (bind(fd, (struct sockaddr *) &unix_addr, len) < 0)

        return(-2);

    memset(&unix_addr, 0, sizeof(unix_addr));

    if (connect(fd, (struct sockaddr *) &unix_addr, len) < 0)

        return(-3);

    return(fd);

}
阅读全文
0 0

相关文章推荐

img
取 消
img