zoukankan      html  css  js  c++  java
  • 第四章:基于TCP套接字编程(一)

    0.一个时间获取的服务端程序

     1 #include    "unp.h"
     2 #include    <time.h>
     3 
     4 int main(int argc, char **argv)
     5 {
     6     int                    listenfd, connfd;
     7     struct sockaddr_in    servaddr;
     8     char                buff[MAXLINE];
     9     time_t                ticks;
    10 
    11     listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    12 
    13     bzero(&servaddr, sizeof(servaddr));
    14     servaddr.sin_family      = AF_INET;
    15     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    16     servaddr.sin_port        = htons(13);    /* daytime server */
    17 
    18     Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    19 
    20     Listen(listenfd, LISTENQ);
    21 
    22     for ( ; ; ) {
    23         connfd = Accept(listenfd, (SA *) NULL, NULL);
    24 
    25         ticks = time(NULL);
    26         snprintf(buff, sizeof(buff), "%.24s
    ", ctime(&ticks));
    27         //printf("%s
    ", buff);
    28         for(int i=0; i< strlen(buff); i++) {
    29             Write(connfd, &(buff[i]), 1);
    30         }
    31 
    32         Close(connfd);
    33     }
    34 }

    下面针对该代码进行分析

    1.socket函数

      为了执行网络IO,一个进程必须做的第一件事情,就是调用socket函数,指定期望的通信协议类型:(TCP,UDP,Unix域字节流协议等)

    1 /* Create a new socket of type TYPE in domain DOMAIN, using
    2    protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
    3    Returns a file descriptor for the new socket, or -1 for errors.  */
    4 extern int socket (int __domain, int __type, int __protocol) __THROW;

    其中domain是指明协议族,type执行套接字类型,protocol指明某个协议类型常量,或者设置为0.domain和type总要求是一个组合。

    domain 说明
    AF_INET IPV4协议
    AF_INET6 IPV6协议
    AF_LOCAL Unix域协议
    AF_ROUTE 路由套接字
    AF_KEY 密钥套接字

    socket的type常量

    type 说明
    SOCK_STREAM 字节流套接字
    SOCK_DGRAM 数据报套接字
    SOCK_SEQPACKET 有序分组套接字
    SOCK_RAW 原始套接字

    socket的domain常量

     1 /* Types of sockets.  */
     2 enum __socket_type
     3 {
     4   SOCK_STREAM = 1,        /* Sequenced, reliable, connection-based
     5                    byte streams.  */
     6 #define SOCK_STREAM SOCK_STREAM
     7   SOCK_DGRAM = 2,        /* Connectionless, unreliable datagrams
     8                    of fixed maximum length.  */
     9 #define SOCK_DGRAM SOCK_DGRAM
    10   SOCK_RAW = 3,            /* Raw protocol interface.  */
    11 #define SOCK_RAW SOCK_RAW
    12   SOCK_RDM = 4,            /* Reliably-delivered messages.  */
    13 #define SOCK_RDM SOCK_RDM
    14   SOCK_SEQPACKET = 5,        /* Sequenced, reliable, connection-based,
    15                    datagrams of fixed maximum length.  */
    16 #define SOCK_SEQPACKET SOCK_SEQPACKET
    17   SOCK_DCCP = 6,        /* Datagram Congestion Control Protocol.  */
    18 #define SOCK_DCCP SOCK_DCCP
    19   SOCK_PACKET = 10,        /* Linux specific way of getting packets
    20                    at the dev level.  For writing rarp and
    21                    other similar things on the user level. */
    22 #define SOCK_PACKET SOCK_PACKET
    23 
    24   /* Flags to be ORed into the type parameter of socket and socketpair and
    25      used for the flags parameter of paccept.  */
    26 
    27   SOCK_CLOEXEC = 02000000,    /* Atomically set close-on-exec flag for the
    28                    new descriptor(s).  */
    29 #define SOCK_CLOEXEC SOCK_CLOEXEC
    30   SOCK_NONBLOCK = 00004000    /* Atomically mark descriptor(s) as
    31                    non-blocking.  */
    32 #define SOCK_NONBLOCK SOCK_NONBLOCK
    33 };
    protocol 说明
    IPPROTO_CP TCP传输协议
    IPPROTO_UDP UDP传输协议
    IPPROTO_SCTP SCTP传输协议

    socket的protocol常量

      AF_INET AF_INET6 AF_LOCAL AF_ROUTE AF_KEY
    SOCK_STREAM TCP|SCTP TCP|SCTP    
    SOCK_DGRM UDP UDP    
    SOCK_SEQPACKET SCTP SCTP    
    SOCK_RAW IPV4 IPV6  

    socket中domain和type的组合

      socket函数的返回值与文件描述符类似,我们把它称为套接字描述符(socket descriptor),简称sockfd。

     2.bind函数

    bind函数把一个本地协议地址赋予一个套接字,一般来说,协议地址是32位的IPV4地址或者128位的IPV6地址与16位的TCP或UDP端口号的组合。

    1 /* Give the socket FD the local address ADDR (which is LEN bytes long).  */
    2 extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
    3      __THROW;

    第一个参数是socket函数生成的sockfd,第二个参数是一个指向某个协议的地址结构的指针。第三个参数是该地址结构的长度。对于TCP,bind函数可以指定一个端口或者一个IP,或者都指定,或者都不指定。

    如果不绑定端口,则调用connect或者listen的时候,内核就要为socket生成一个临时端口。

    3.listen函数

    listen函数由TCP服务器调用,调用connect发起连接的客户套接字。listen函数把一个未连接套接字转换成一个被动套接字,知识内核应接受指向该套接字的连接要求。调用listen导致套接字从closed状态转换到listen状态。

    1 /* Prepare to accept connections on socket FD.
    2    N connection requests will be queued before further requests are refused.
    3    Returns 0 on success, -1 for errors.  */
    4 extern int listen (int __fd, int __n) __THROW;

    关于N参数,内核为任何一个给定的监听套接字维护两个队列:

    (1)未完成连接队列,每个这样的SYN分解对应其中一项:已由某个客户发出并达到服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态。

    (2)已完成队列,每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于established状态。

      

    至于N的值怎么定义,这个地方太复杂了.

    UNP 中把listen函数封装了一下,允许环境变量LISTENQ覆盖传进来的参数。

     1 /* include Listen */
     2 void
     3 Listen(int fd, int backlog)
     4 {
     5     char    *ptr;
     6 
     7         /*4can override 2nd argument with environment variable */
     8     if ( (ptr = getenv("LISTENQ")) != NULL)
     9         backlog = atoi(ptr);
    10 
    11     if (listen(fd, backlog) < 0)
    12         err_sys("listen error");
    13 }
    14 /* end Listen */

    4.accept函数

    由TCP服务器调用,用于从已完成连接队列返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

     1 /* Await a connection on socket FD.
     2    When a connection arrives, open a new socket to communicate with it,
     3    set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
     4    peer and *ADDR_LEN to the address's actual length, and return the
     5    new socket's descriptor, or -1 for errors.
     6 
     7    This function is a cancellation point and therefore not marked with
     8    __THROW.  */
     9 extern int accept (int __fd, __SOCKADDR_ARG __addr,
    10            socklen_t *__restrict __addr_len);

    5.connect函数

    TCP客户用connect函数来建立与TCP服务器的连接

    1 /* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
    2    For connectionless socket types, just set the default address to send to
    3    and the only address from which to accept transmissions.
    4    Return 0 on success, -1 for errors.
    5 
    6    This function is a cancellation point and therefore not marked with
    7    __THROW.  */
    8 extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

    fd是socket函数返回的套接字描述符,addr和len分别是一个指向套接字地址结构的指针和该结构的大小。套接字地址结构必须含有服务器的IP地址和端口号。

    客户在调用函数connect钱不必非得调用bind函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口。

    如果是TCP套接字,调用connect函数将激发TCP的三路握手程序过程,而且仅在连接建立成功或出错时才返回,其中出错返回可能有以下几种情况。

    (1)若TCP客户没有说到SYN分节的相应,则返回ETIMEDOUT错误。例如:4.4BSD内核发送一个SYN,若无响应,则等待6S再发送一个,若仍无相应,则等待24S后再发送一个,若总共等了75S后仍未收到响应,则返回本错误。

    (2)若对客户的SYN的响应是RST(表示复位),则表明该服务主机在我们指定的端口上没有进程在运行,这是一种hard error,客户一收到RST,就马上返回ECONNREFUSED错误。

    (3)若客户发出的SYN在中间的某个路由器上引发了一个destination unreachable 的ICMP错误。则认为是一种软错误。若在规定时间内仍未收到响应,则返回EHOSTUNREACH或者ENETUNREACH错误。

  • 相关阅读:
    C语言左移和右移
    mmap详谈
    eclipse插件自动生成类图
    async 和 defer 的区别
    SVN里恢复到某一天的版本操作
    解决跨域的jsonp+Java实例
    HTTP请求行、请求头、请求体等
    ajax在什么情况下会走success和error
    记阅读POST与GET的区别
    记一些快捷键
  • 原文地址:https://www.cnblogs.com/whutao/p/14169805.html
Copyright © 2011-2022 走看看