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错误。