OSI七层模型
- TCP在第四层----》传输层,传输层的数据叫做
segment
- IP在第三层—》网络层,网络层的数据叫做
Packet
- ARP在第二层–》数据链路层,数据链路层的数据叫做``Frame`
程序中的数据会先打包在TCP的segment
,然后TCP的segment
会被打包到IP的Packet
,然后再到以太网Ethernet
的Frame
,传递到对端后,各个层解析自己的协议,然后把数据交给更高层的协议处理
- 最下面两层是随系统提供的设备驱动程序和网络硬件。我们基本上不太需要关注它们
- 网络层是IPv4.Ipv6。我们可以彻底绕过IP层直接读写数据链路层的帧
- 传输层可以选择TCP或者UDP,当然,我们也可以使用原始套接字 绕开它们
- 最上面三层可以合并为一层,简称应用层,就是web服务端、web服务器等所在的层
用户进程和内核之间通过系统提供的API来交流
服务器 与客户端
- 客户段与服务器使用TCP在同一个以太网(局域网)中通信
- 客户端和服务器一般是用户进程,而TCP/I[协议通常是内核中协议栈的一部分
- 客户段与服务器使用TCP在不同以太网(局域网)中通信
- 这两个局域网是使用路由器(router)连接到广域网(
WAN
)的 - 路由器是广域网的架设设备
- 当今最大的广域网是因特网
获取时间
时间获取客户端
用于获取时间服务
IPV4版本
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define MAXLINE 4096
int main(int argc, char **argv){
int sockfd, n;
struct sockaddr_in servaddr;
char recvline[MAXLINE + 1];
if (argc != 2){
printf("usage: a.out <IPaddress>");
exit(-1);
}
// 创建套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("socket error");
exit(-1);
}
// 设置要连接的服务器属性
bzero(&servaddr, sizeof(servaddr)); // 清零
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13); // 时间获取服务器的端口都是13
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ // 将argv[1]转换为合适的格式: ipv4/ipv6
printf("inet_pton error for %s: %s", argv[1], strerror(errno));
exit(-1);
}
// 使用tcp套接字与指定服务器建立一个tcp连接
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s", strerror(errno));
exit(-1);
}
// read读取服务器应答: read返回0(表示对端关闭)/负数(表示读取错误)
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF){ // 使用fputs输出到标准输出端
printf("fputs error");
exit(-1);
}
}
if (n < 0){
printf("read error");
exit(-1);
}
exit(0);
}
IPV6版本
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define MAXLINE 4096
int main(int argc, char **argv){
int sockfd, n;
struct sockaddr_in6 servaddr;
char recvline[MAXLINE + 1];
if (argc != 2){
printf("usage: a.out <IPaddress>");
exit(-1);
}
// 创建套接字
if ((sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0){
printf("socket error");
exit(-1);
}
// 设置要连接的服务器属性
bzero(&servaddr, sizeof(servaddr)); // 清零
servaddr.sin6_family = AF_INET6;
servaddr.sin6_port = htons(13); // 时间获取服务器的端口都是13
if (inet_pton(AF_INET, argv[1], &servaddr.sin6_addr) <= 0){ // 将argv[1]转换为合适的格式: ipv4/ipv6
printf("inet_pton error for %s: %s", argv[1], strerror(errno));
exit(-1);
}
// 使用tcp套接字与指定服务器建立一个tcp连接
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s", strerror(errno));
exit(-1);
}
// read读取服务器应答: read返回0(表示对端关闭)/负数(表示读取错误)
while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline[n] = 0; /* null terminate */
if (fputs(recvline, stdout) == EOF){ // 使用fputs输出到标准输出端
printf("fputs error");
exit(-1);
}
}
if (n < 0){
printf("read error");
exit(-1);
}
exit(0);
}
时间获取服务器
IPV4版本
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#define MAXLINE 4096
#define LISTENQ 1024
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); // 这里指定了IP地址为INADDR_ANY,意思是如果服务器主机有多个网络接口,服务器进程就可以在任意网络上接受客户连接
servaddr.sin_port = htons(13);
printf("%d", htons(13));
if( bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
printf("bind error, %s", strerror(errno));
exit(0);
}
if(listen(listenfd, LISTENQ) < 0){
printf("listen error, %s", strerror(errno));
exit(0);
} // 监听listenfd,这样来自客户端的连接就可以在该套接字上由内核接受。 LISTENQ表示内核允许在这个监听描述符上排队的最大客户连接数
for ( ; ; ) {
if((connfd = accept(listenfd, (struct sockaddr *) NULL, NULL)) < 0){
printf("accept error, %s", strerror(errno));
continue;
}// 当前服务器进程会在accept上睡眠阻塞,直到某个客户断连接唤醒:TCP连接使用三次握手来建立连接,握手完毕accept返回一个已连接的描述符,我们就可以使用这个描述符与客户端通信了
//sleep(60);
ticks = time(NULL); // time返回一个int秒数
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); // ctime将int转换成直观可读的时间格式,然后使用snprintf复制到buff中(最大不能超过sizeof(buff)个字符)
write(connfd, buff, strlen(buff)); // 将消息通过connfd写回给客户端
close(connfd); //关闭connfd: 该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自的对端确认
printf("客户端连接关闭\n");
}
}
- 这个服务器,一次只能处理一个请求。 如果有多个客户端同时到达,系统内核在某个最大数目的限制下把它们排入队列
- 对于一个服务器来讲,在建立连接之后(之前也一样),客户端是否提前关闭不影响服务器的行为,服务器还是会继续自己的处理
- 在服务器将消息写到了通道中,然后就死掉了;但是此时客户端还没有来的及将消息读走,那此时消息还在吗?
还在
问:如果我们想要知道客户端的IP地址和端口,该怎么改写?
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#define MAXLINE 4096
#define LISTENQ 1024
int main(int argc, char **argv){
int listenfd, connfd;
struct sockaddr_in servaddr, cliaddr;
char buff[MAXLINE];
time_t ticks;
socklen_t len;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 这里指定了IP地址为INADDR_ANY,意思是如果服务器主机有多个网络接口,服务器进程就可以在任意网络上接受客户连接
servaddr.sin_port = htons(13);
if( bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
printf("bind error, %s", strerror(errno));
exit(0);
}
if(listen(listenfd, LISTENQ) < 0){
printf("listen error, %s", strerror(errno));
exit(0);
} // 监听listenfd,这样来自客户端的连接就可以在该套接字上由内核接受。 LISTENQ表示内核允许在这个监听描述符上排队的最大客户连接数
for ( ; ; ) {
len = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &len)) < 0){
printf("accept error, %s", strerror(errno));
continue;
}
printf("connectio from %s, port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, buff, sizeof(buff)),
ntohs(cliaddr.sin_port));
//sleep(60);
ticks = time(NULL); // time返回一个int秒数
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); // ctime将int转换成直观可读的时间格式,然后使用snprintf复制到buff中(最大不能超过sizeof(buff)个字符)
write(connfd, buff, strlen(buff)); // 将消息通过connfd写回给客户端
close(connfd); //关闭connfd: 该调用引发正常的TCP连接终止序列:每个方向上发送一个FIN,每个FIN又由各自的对端确认
printf("客户端连接关闭\n");
}
}