tcp连接的建立过程:
1).服务器必须准备好接受外来的连接。这通过调用socket,bind和listen函数来完成,称为被动打开(passive open)。
2).客户通过connect进行主动打开(active open)。这引起客户tcp发送一个SYN(表示同步)分节,它告诉服务器客户将在连接中发送的数据的初始序列号。一般情况下SYN分节不携带数据,他只含有一个ip头部、一个tcp头部及可能有的tcp选项。
3).服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个分节向客户发送SYN和对客户SYN的确认。
4).客户必须确认服务器的SYN。
建立tcp连接的日常系统类似于电话系统。socket函数等同于有电话可用。bind函数用于告诉其他人你的电话号码,让它们可以向你打电话。listen是打开电话震铃,它使你可以听到一个外来的电话。connect函数要求你知道对方的电话号码并拨打它。accept是被呼叫回电话。从accept返回客户的标志类似于让电话机的呼叫者ID功能部件显示打电话人的电话号码。然而有不同的是:从accept返回客户的标志是在建立连接以后,而呼叫者id功能部件显示打电话人的电话号码是在我们选择接或者不接电话之前。
获取时间的客户程序:
#include "unp.h"
#include "error.c"
int main(int argc, char **argv) {
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
if(argc != 2) {
err_quit("usage:a.out<IPaddress>");
}
/*
socket函数创建网际(AF_NET)字节流(SOCK_STREAM)套接口。该函数返回一个小整数描述字,在以后的其他函数调用中(如connect和read),我们就用它来标记这个套接口。
*/
if((sockfd=socket(AF_INET, SOCK_STREAM,0)) < 0)
err_sys("socket error");
/*
指定服务器的ip地址和端口。bzero()把整个结构清零。
*/
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13); // daytime server
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
err_quit("inet_pton error for %s", argv[1]);
/*
建立与服务器的连接。其中SA代表struct sockaddr.
*/
if(connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
/*
读入并输出服务器的应答。tcp是一种无记录边界的字节流协议。记录的结束由服务器关闭连接表示。
*/
while((n=read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0;
if(fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if(n < 0)
err_sys("read error");
exit(0);
}
获取时间的服务器程序:
#include "unp.h"
#include <time.h>
#include "error.c"
int main(char argc, char ** argv) {
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
//创建一个tcp套接口
//listenfd = Socket(AF_INET, SOCK_STREAM, 0);
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13);
//把ip地址指定为INADDR_ANY,它允许服务器在任意网络接口上接受客户连接。
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//Bind(listenfd, (SA*)&servaddr, sizeof(servaddr));
/*
捆绑服务器众所周知的端口到套接口。
*/
if(bind(listenfd, (SA*)&servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
/*
把套接口变换成监听套接口。来自客户的外来连接就在这个监听套接口上由内核接受。socket、bind和listen是任何tcp服务器准备所谓的监听描述字(listening descriptor)的三个通常的步骤。常值LISTENQ在头文件unp.h中定义,它指定系统内核允许在这个监听描述字上排队的最大客户连接数。
*/
listen(listenfd, LISTENQ);
for(;;) {
/*
接受客户端连接,发送应答。一般情况下,服务器进程在调用accept函数后处于睡眠状态,等待单个客户的到达和内核对它的接受。accept返回一个称为已连接描述字的新描述字。该描述字用于与新近连接的客户通信。
*/
connfd = accept(listenfd, (SA*)NULL, NULL);
ticks = time(NULL);
/*
snprintf第2个参数指定缓冲区的大小,可确保缓冲区不溢出。
*/
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(connfd, buff, strlen(buff));
close(connfd);
}
return 0;
}