概述vim
要编写经过计算机网络通讯的程序,首先要肯定这些程序相互通讯所用的协议。大多数网络是按照划分红客户和服务器来组织的。本章及后续章节的焦点是TCP/IP协议族,也可称为网际协议族。下图为客户与服务器使用TCP在同一个以太网中通讯:服务器
图1.1 客户与服务器使用TCP在同一个以太网进行通讯网络
同一网络中的客户机与服务器无需出于同局域网,上图1.1所示的是同一个局域网。下图1.2所示的是处于不一样局域网的客户机与服务器,这两个局域网经过使用路由器链接到广域网。socket
图1.2 出于不一样局域网的客户主机与服务器主机经过广域网进行链接tcp
现在讨论Unix是常常使用POSIC一词,它是一种被多数厂商采纳的标准。函数
一个简单的时间获取客户程序ui
// 该头文件包含了大部分网络程序都须要的许多系统头文件 #include "unp.h" // main函数定义,其形式参数就是命令行参数 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_INET)字节流(SOCK_STREAM)套接字,该函数返回一个小整数描述符。若是socket函数调用失败,调用err_sys函数放弃程序运行 if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); // 把IP地址和端口号填入一个网际套接字地址结构(一个名为servadrr的sockdrr_in结构变量),使用bzero把整个结构清零 bzero(&servaddr, sizeof(servaddr)); // 置地址族为AF_INET,端口号为13,IP地址为第一个命令行参数的值(argv[1]) // 网际套接字结构中IP地址和端口号必须使用特定格式,为此调用库函数htons去转换二进制端口号,又调用inet_pton去把ASCII命令行参数转换为合适的格式 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]); // connect函数应用于TCP套接字时,将由它的第二个参数指向套接字地址结构指定的服务器创建一个TCP链接 // 套接字地址结构的长度必须做为该函数的第三个参数指定 if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0) err_sys("connect error"); // 使用read函数读取服务器的应答,并用标准I/O函数fputs输出结果 // 把read放在循环以便读取完数据,当read返回0或者负数时终止循环 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); }
咱们从官网www.unpcook.com下载源代码unpv13e.tar.gz。解压后进入文件夹。个人是Ubuntu系统。根据文件夹中Readme的提示输入相应的命令。this
redhat@redhat-virtual-machine:~/桌面/unpv13e$ ./configure redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./lib redhat@redhat-virtual-machine:~/桌面/unpv13e/lib$ make redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd ./libfree redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ make // 若是报错以下,则须要在当前目录下打开inet_ntop.c文件 // 将第60行的size_t size修改成socklen_t size 而后保存 // 从新输入make后不报错便可 gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o inet_ntop.o inet_ntop.c inet_ntop.c: In function ‘inet_ntop’: inet_ntop.c:60:9: error: argument ‘size’ doesn’t match prototype size_t size; ^ In file included from inet_ntop.c:27:0: /usr/include/arpa/inet.h:64:20: error: prototype declaration extern const char *inet_ntop (int __af, const void *__restrict __cp, ^ make: *** [inet_ntop.o] Error 1 redhat@redhat-virtual-machine:~/桌面/unpv13e/libfree$ cd ../libgai redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ make // 如下的warning不用理会 /usr/include/arpa/inet.h: In function ‘inet_ntop’: inet_ntop.c:152:23: warning: ‘best.len’ may be used uninitialized in this function [-Wmaybe-uninitialized] if (best.base == -1 || cur.len > best.len) ^ inet_ntop.c:123:28: note: ‘best.len’ was declared here struct { int base, len; } best, cur; ^ gcc -I../lib -g -O2 -D_REENTRANT -Wall -c -o inet_pton.o inet_pton.c ar rv ../libunp.a in_cksum.o inet_ntop.o inet_pton.o a - in_cksum.o a - inet_ntop.o a - inet_pton.o ranlib ../libunp.a // 用root权限将以上编译生成的libunp.a 文件复制到/usr/lib目录中 redhat@redhat-virtual-machine:~/桌面/unpv13e/libgai$ cd .. redhat@redhat-virtual-machine:~/桌面/unpv13e$ sudo cp libunp.a /usr/lib [sudo] redhat 的密码: // 打开unp.h文件将其中的#include "../config.h" 改为 #include "config.h" redhat@redhat-virtual-machine:~/桌面/unpv13e$ vim lib/unp.h // 进入intro目录编译客户端文件并用root权限运行 redhat@redhat-virtual-machine:~/桌面/unpv13e$ cd intro/ redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ make daytimetcpcli redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpcli 127.0.0.1 // 错误提示没法链接 connect error: Connection refused // 咱们先打开服务器 redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ make daytimetcpsrv redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpsrv // 而后再打开另外一个终端,在那里再运行客户端便可 redhat@redhat-virtual-machine:~/桌面/unpv13e/intro$ sudo ./daytimetcpcli 127.0.0.1 [sudo] redhat 的密码: Mon Dec 25 21:00:36 2017
上面提到了客户端获取时间的程序代码,下面为服务器端的程序。spa
#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); // 填写一个网际套接字地址结构并调用bind函数,把服务器的端口捆绑到所建立的套接字中 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函数把该套接字转换成一个监听套接字,使来自客户端的链接能够在该套接字上由内核接受 // socket、bind、listen这三个函数调用步骤是任何tcp服务器准备监听描述符的正常步骤 // LISTENQ定义在头文件中,它指定系统内核容许在这个监听描述副符上排队的最大客户链接数 Listen(listenfd, LISTENQ); // 服务器进程在accept调用中被投入睡眠,等待客户的链接 // TCP链接的三次握手完毕时accept返回,其返回值是一个被称为已链接描述符的新描述符 for ( ; ; ) { connfd = Accept(listenfd, (SA *) NULL, NULL); // time函数获取当前时间,ctime函数把时间转换成直观可读的时间格式 ticks = time(NULL); // snprintf函数在这个字符串末尾添加一个回车符和一个换行符 // write函数把结果字符串写给客户 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); Write(connfd, buff, strlen(buff)); // 终止链接 Close(connfd); } }