您的位置:首页 > 理论基础 > 计算机网络

UNP v1 第一章:简介

2015-10-03 15:14 543 查看

前言

开坑作死小能手在csapp的大坑才刚开始的情况下又来开个新坑了。。。不想像csapp那坑那样作死了。。。此篇以代码为主。。。

UNIX网络编程分为两卷,分别为套接字联网API和进程间通信。

1.1 概述

编写网络通信程序前,需要确定程序通信所用的协议。大多数的网络应用是分为客户(Client)和服务器(Server)来组织的,通常称之为C/S结构。在书中使用一个头文件unp.h,其中包括了需要的头文件、一些错误处理函数以及包裹函数。

1.2 时间获取客户端

下面实现了一个查询当前时间的客户端程序。客户端与服务器建立一个TCP连接后,服务器返回当前的时间与日期。

#include "unp.h"

int main(int argc, char **argv) {
int sockfd, n;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;

if (argc != 2) {    // 使用argv[1]参数传递服务器端地址。
err_quit("Usage: daytimetcpcli <IPaddress>");
}

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {   // 创建网际(AF_INET)字节流(SOCK_STREAM)套接字。
err_sys("socket error");
}

bzero(&servaddr, sizeof(servaddr));   // 使用bzero将servaddr清零,也可使用memset(),但memset带3个参数容易犯错。
servaddr.sin_family = AF_INET;    // 将地址族置为AF_INET。
servaddr.sin_port = htons(13);    // 服务器端端口号设为13。
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) {    // 与指定的服务器端建立一个TCP连接。SA是一个宏定义,等同于struct sockaddr。
err_sys("connect error");
}

while ((n = read(sockfd, recvline, MAXLINE)) > 0) {   // 读取服务器的应答,写入recvline中。当返回值为0(对端关闭连接)或为负(发送错误)时结束循环。
recvline
= 0;
if (fputs(recvline, stdout) == EOF) {
err_sys("fputs error");
}
}

if (n < 0) {
err_sys("read error");
}

exit(0);
}


在上述代码中用到的err_开头的函数即书中提供的错误处理函数,如
err_quit("inet_pton error for %s", argv[1])
等同于

fprintf(stderr, "inet_pton error for %s", argv[1]);
exit(1);


1.3 时间获取服务器端

#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);   // 将地址设为INADDR_ANY目的是若服务器主机有多个网络接口,服务器程序可以在任意一个接口上接受客户连接。
servaddr.sin_port = htons(13);

Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));   // 将端口捆绑到服务器进程的套接字。

Listen(listenfd, LISTENQ);    // 将套接字转换为监听套接字,常量LISTENQ是允许这个监听描述符排队的最大客户连接数。

for ( ; ; ) {
connfd = Accept(listenfd, (SA *) NULL, NULL);   // 接受客户连接
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, sizeof(buff));    // 发送应答

Close(connfd);    // 关闭与客户的连接
}
}


在服务器端程序中使用了包裹函数,它将错误处理函数包含进其中,缩短了程序的长度。如
Socket()
,它的实现如下

int Socket(int family, int type, int protocol) {
int n;

if ((n = socket(family, type, protocol)) < 0) {
err_sys("socket error");
}
return(n);
}


1.4 IPv6版本

上述的两个程序均为IPv4版本,通过稍微修改即可成为IPv6的版本。

在IPv4中,地址族为AF_INET,IPv6中则要修改为AF_INET6。除地址族外,将
struct sockaddr_in
改为
struct sockaddr_in6
,并将该结构变量中的
sin_
开头的成员修改为
sin6_
。除此外,在服务器程序中,需将
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
修改为
servaddr.sin6_addr = in6addr_any


1.5 效果

编译客户端与服务器端后在命令行中进行测试,因为我的客户端与服务器端都在同一台电脑上,所以用了环回地址127.0.0.1以及IPv6的换回地址::1作为参数。先启动服务器端,服务器端开启时需要有root权限,下面分别为IPv4与IPv6的服务器端。

$sudo ./daytimetcpsrv
$sudu ./daytimetcpsrvv6


开启服务器端后即可使用客户端程序获取当前时间。

$./daytimetcpcli 127.0.0.1
Sat Oct  3 14:22:04 2015
$./daytimetcpcliv6 ::1
Sat Oct  3 14:23:38 2015
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unpv1 UNIX网络编程