Unix网络编程卷1 读书笔记 第一章 简介
0.概要简介
本章介绍了服务器-客户端的基本架构,socket套接字,从OSI模型层面分析socke的作用,提供了一个时间服务的客户端和服务端的最小实现。本章学完后应该对网络编程有一个直观的印象。
image.png
重点是1,2,5,7节。
1.知识总结
1.1 客户端-服务端架构
要编写通过计算机网络通信的程序,首先要确定这些程序相互通信所用的协议(protocol)。在深入设计一个协议的细节之前,应该从高层次决断通信由哪个程序发起以及响应在何时产生。举例来说,一般认为Web服务器程序是一个长时间运行的程序(即所谓的守护程序,daemon),它只在响应来自网络的请求时才发送网络消息。协议的另一端是Web客户程序,如某种浏览器,与服务器进程的通信总是由客户进程发起。大多数网络应用就是按照划分成客户(client)和服务器(server)来组织的。
1.2 OSI模型与socket编程接口
OSI的7层模型是老生常谈的内容了,但是之前每次看的时候仅限于记忆,并没有深入的理解。
Unix网络编程卷1 读书笔记 第一章 简介
对于一个新的事物如果仅仅是记忆概念,对之没有其他更多的操作实践,那么算不上理解掌握了它。之前也接触过OSI的7层模型,但总是感觉概念无法实际落地。看完本章关于socket编程接口和协议模型的论述,对协议觉理解加深了一步。简单的说socket编程接口是上三层(应用层、表示层、会后层)进入传输层的接口。
本书讲述的套接字编程接口是从顶上三层(网际协议的应用层)进入传输层的接口。本书的焦点是:如何使用套接字编写使用TCP或者UDP的网络应用程序。
1.3 socket编程客户端-服务端基本流程
Unix网络编程卷1 读书笔记 第一章 简介2.动手实验
本章实验基于安装了Ubuntu系统的台式机和安装了lubuntu系统的cubieboard cc-a80开发板,进行两个实验:
- 客户端实验
实现一个客户端程序,在Ubuntu系统以系统自带的时间服务为服务端,获取时间。 - 服务端实验
实现一个服务端程序,将该服务端程序部署在cc-a80,使用上个实验中的位于Ubuntu系统的客户端向 cc-a80服务器端发出请求获取时间。
2.1 实验代码
作者在前言中已经给出了书中代码的下载地址,但我总觉得还是自己手动敲一遍理解更深。作者把常用的头文件、错误处理、socket接口均封装成响应的文件,实验学习过程代码组织目录结构保持和书中一致,具体实现本着用到什么实现什么的原则。为了分享,笔者在github上建立一个仓库,地址如下:
https://github.com/Sam-Z/unix_network_programming_volume_1.git
├── intro
│ ├── daytimetcpcli.c
│ ├── daytimetcpcli.mk
│ ├── daytimetcpsrv.c
│ └── daytimetcpsrv.mk
├── lib
│ ├── libwrapsock.mk
│ ├── unp.h
│ └── wrapsock.c
目录结构如下,intro目录下是第一章的两个程序所在目录,在该目录下提供了两个makefile,例如执行
make -f daytimetcpcli.mk
就可以编译获取时间的客户端程序,服务端的程序类似。lib目录是库函数目录,当前只有socket接口的包裹函数和unp.h头文件,后续随着学习的深入,逐渐完善。本书的后续学习实验代码均会更新到该仓库。
为了便于后续服务端实验,在客户端和服务端的程序将端口号统一用MY_DAY_TIME_SERVER_PORT宏给出,第一个实验中该宏设置为13(系统时间服务端口号固定为13),第二个实验中该宏设置为一个未被使用的端口即可(笔者实验代码中使用的是45000)。
2.2获取时间客户端
- 源码
#include"unp.h"
#define MAXLINE (256)
int main(int argc, char **argv)
{
// 1. 参数校验
if (argc != 2){
printf("usage: a.out <IPaddress>\n");
exit(0);
}
// 2. 建立socket
int sockfd = 0;
if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("socket error!\n");
exit(0);
}
// 3. 建立连接
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(MY_DAY_TIME_SERVER_PORT);
if (inet_pton(AF_INET, argv[1], &addr.sin_addr) < 0){
printf("inet_pton error for %s\n", argv[1]);
exit(0);
}
if ( connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0){
printf("connect error!\n");
exit(0);
}
// 4. 读取数据
char recvline[MAXLINE+1];
int n = 0;
while( (n = read(sockfd, recvline, MAXLINE)) > 0 ){
recvline [n] = 0;
printf("%s\n", recvline);
}
if (n<0){
printf("read error!\n");
}
exit(0);
}
- 执行
在Ubuntu源码编译生成可执行文件后,按照如下命令执行,127.0.0.1是本地回环IP地址,即从本地电脑的系统服务获取时间。
$ daytimetcpcli 127.0.0.1
执行过程出现如下错误。
connect error!
百度一番,有人说是由于系统的时间服务未开启,按照如下操作开启系统时间服务。
安装服务
apt-get install xinetd
配置
sudo vi /etc/xinetd.d/daytime
将其中的disable 值改为no。
重启服务
sudo /etc/init.d/xinetd restart
再次执行,正常获取到时间,结果如下
$ daytimetcpcli 127.0.0.1
31 DEC 2017 14:38:36 CST
2.3 获取时间服务端实验
- 源码
#include "unp.h"
#include <time.h>
int main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buffer[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(MY_DAY_TIME_SERVER_PORT);
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for(;;){
connfd = Accept(listenfd, (struct sockaddr *)NULL, NULL);
ticks = time(NULL);
snprintf(buffer, sizeof(buffer), "MY_DAY_TIME_SERVER:%s.24s\n", ctime(&ticks));
Write(connfd, buffer, strlen(buffer));
Close(connfd);
}
}
- 执行
源码在cc-a80环境编译生成可执行文件,启动服务程序。
linaro@cubieboard4:~$ daytimetcpsrv&
Ubuntu端使用客户端发起访问
$ daytimetcpcli 192.168.20.100
MY_DAY_TIME_SERVER:Sun Dec 31 14:26:28 2017
这里的192.168.20.100是cc-a80的IP地址。