第四章:基于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错误。

原文地址:https://www.cnblogs.com/whutao/p/14169805.html