一个获取客户端时间的程序:
#include "unp.h"
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>");
if ( (sockfd = 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); /* daytime server */
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
err_quit("inet_pton error for %s", argv[1]);
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if (n < 0)
err_sys("read error");
exit(0);
}
- socket创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字,它是TCP套接字的花哨名字。
- int sockfd;
- sockfd = socket(AF_INET,SOCK_STREAM,0);
- sockfd小于0,连接失败;
- 我们把服务器的IP地址和端口号填入一个网际套接字地址结构(一个名为servaddr的sockaddr_in结构变量);置地址族为AF_INET,端口号为13(时间获取服务器的众所周知的端口号);IP地址为第一个命令行参数的值(argv[1]);端口号使用库函数htons(“主机到网络短整数”)去转换二进制端口号,又调用库函数inet——pton(“呈现形式到数值”)去把ASCII命令行参数(如206.168.112.96)转化为合适的格式
- IP地址:argv[1]
- 端口号转换:servaddr.sin_port = htons(13);
- 【注】使用bzero不使用memset是因为bzero(带2个参数)比memset(带3个参数)更好记忆。
- #define bzero(ptr,n) {memset(ptr, 0, n);}
- #define bzero(ptr,n) {memset(ptr, 0, n);}
- connect函数应用于一个TCP套接字时,将与由它的第二个参数指向的套接字地址结构指定的服务器建立一个TCP连接。该套接字地址结构的长度也必须作为该函数的第三个参数指定,我们总用C语言中的sizeof操作符来计算。
- struct sockaddr_in servaddr;
- connect(sockfd, (struct sockaddr) &servaddr, sizeof(servaddr));
- read函数读取服务器的应答,并用标准的I/O函数fputs上诉出结果;并且服务器的应答通常是如下格式的26字节字符串。
- Mon May 26 20:58:40 2003\r\n
- 只要一个unix函数(例如某个套接字函数)中有错误发生,全局变量errno就被置为一个指明该错误类型的正值,函数本身则通常返回-1。err_sys查看errno变量的值并输出相应的出错消息,例如errno值为ETIMEDOUT时,将输出“Connection timed out”(连接超时)
- 通常“E”开头的全大写字母名字,在<sys/errno.h>头文件中定义。
一个获取服务器端时间的程序
#include "unp.h"
#include <time.h>
int main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13); /* daytime server */
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
connfd = Accept(listenfd, (SA *) NULL, NULL);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
- 通过填写一个网际套接字地址结构并调用bind函数,将服务器端口(时间获取服务是13)捆绑都所创建的套接字。我们指定IP地址为INADDR_ANY。这样,要是服务器主机有多个网络接口,服务器进程可在任意网络接口上接受客户连接。
- 调用listen函数把该套接字转化为一个监听套接字,这样来自客户的外来连接可在该套接字上由内核接受。
- 通常情况下,服务器进程在accept调用中投入睡眠,等待某个客户连接的到达并被内核接受。TCP使用三次握手来建立连接,握手完毕时accept返回一个已连接描述符,上例中为connfd
- snprintf函数在这个字符串末尾添加一个回车符和一个回行符,随后write函数把结果字符串写给客户。
- sprintf无法检查目的缓冲区是否溢出
- snprintf要求第二个参数指定目的缓冲区大小,确保不溢出
- 同样地,gets、strcat和strcpy改用fgets、strncat和strncpy,更好的代替函数是strlcat和strlcpy,它们确保结果是正确终止的字符串。