2017-2018-1 20155323 《信息安全系统设计基础》第8周学习总结
3.基于socket 使用教材的csapp.h csapp.c,实现daytime(13)服务器(端口我们使用13+后三位学号)和客户端
服务器响应消息格式是
“
客户端IP:XXXX
服务器实现者学号:XXXXXXXX
当前时间: XX:XX:XX
”
上方提交代码
提交一个客户端至少查询三次时间的截图测试截图
提交至少两个客户端查询时间的截图测试截图
- 实验过程:
编写服务器代码:
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero
#include<time.h>
#define SERVER_PORT 13323
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main(void)
{
// 声明并初始化一个服务器端的socket地址结构
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
// 创建socket,若成功,返回socket描述符
int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if(server_socket_fd < 0)
{
perror("Create Socket Failed:");
exit(1);
}
int opt = 1;
setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定socket和socket地址结构
if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))
{
perror("Server Bind Failed:");
exit(1);
}
// socket监听
if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))
{
perror("Server Listen Failed:");
exit(1);
}
while(1)
{
// 定义客户端的socket地址结构
struct sockaddr_in client_addr;
socklen_t client_addr_length = sizeof(client_addr);
// 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信
// accept函数会把连接到的客户端信息写到client_addr中
int new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);
if(new_server_socket_fd < 0)
{
perror("Server Accept Failed:");
break;
}
char buffer1[BUFFER_SIZE];
char buffer2[BUFFER_SIZE];
bzero(buffer1, BUFFER_SIZE);
bzero(buffer2, BUFFER_SIZE);
time_t ticks;
ticks = time(NULL);
recv(new_server_socket_fd, buffer1, BUFFER_SIZE, 0);
recv(new_server_socket_fd, buffer2, BUFFER_SIZE, 0);
printf("客户端IP:%s
服务器实现者学号:%s
当前时间:%.24s
", buffer1,buffer2,ctime(&ticks));
// 关闭与客户端的连接
}
// 关闭监听用的socket
close(server_socket_fd);
return 0;
}
注意实验要求是要一个客户端至少查询三次时间并且要可以同时打开两个客户端,所以close语句必须要在循环外。
然后是编写客户端代码:
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero
#define SERVER_PORT 13323
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main()
{
// 声明并初始化一个客户端的socket地址结构
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htons(INADDR_ANY);
client_addr.sin_port = htons(0);
// 创建socket,若成功,返回socket描述符
int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_socket_fd < 0)
{
perror("Create Socket Failed:");
exit(1);
}
// 绑定客户端的socket和客户端的socket地址结构 非必需
if(-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr))))
{
perror("Client Bind Failed:");
exit(1);
}
// 声明一个服务器端的socket地址结构,并用服务器那边的IP地址及端口对其进行初始化,用于后面的连接
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
if(inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) == 0)
{
perror("Server IP Address Error:");
exit(1);
}
server_addr.sin_port = htons(SERVER_PORT);
socklen_t server_addr_length = sizeof(server_addr);
// 向服务器发起连接,连接成功后client_socket_fd代表了客户端和服务器的一个socket连接
if(connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0)
{
perror("Can Not Connect To Server IP:");
exit(0);
}
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
strcpy(buffer,"127.0.0.1");
// 向服务器发送buffer中的数据
if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Send Failed:");
exit(1);
}
bzero(buffer, BUFFER_SIZE);
strcpy(buffer,"20155323");
if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Send Failed:");
exit(1);
}
close(client_socket_fd);
return 0;
}
实验截图:
教材学习内容总结
并发编程
基于进程的并发编程
- 常用函数:
fork
exec
waitpid
- 需要说明的内容:
必须要包括一个SIGCHLD处理程序来回收僵死子进程的资源。
父进程需要关闭它的已连接描述符的拷贝以避免内存泄漏。
父子进程之间共享文件表,但是不共享用户地址空间。
进程的优劣
- 注意:进程的模型:共享文件表,但不是共享用户地址空间。
- 优点:一个进程不可能不小心覆盖两一个进程的虚拟存储器。
- 缺点:独立的地址空间使得进程共享状态信息变得更加困难。进程控制和IPC的开销很高。
基于I/O多路复用的并发编程
- 只允许对描述符做的三件事:
1.分配他们。
2.将一个此种类型的变量赋值给另一个变量。
3.用FD_ZERO、FD_SET、FD_CLR和FD_ISSET宏指令来修改和检查它们。
基于I/O多路复用的并发事件驱动服务器
状态机就是一组状态、输入事件和转移,转移就是将状态和输入时间映射到状态,自循环是同一输入和输出状态之间的转移。
I/O多路复用技术的优势
- 优点:
(1)比基于进程的设计给了程序员更多的对程序行为的控制
(2)运行在单一进程上下文中,因此,每个逻辑流都能访问该进程的全部地址空间,使得流之间共享数据变得很容易。
(3)不需要进程上下文切换来调度新的流。
- 缺点:
(1)编码复杂
(2)不能充分利用多核处理器
粒度:每个逻辑流每个时间片执行的指令数量。并发粒度就是读一个完整的文本行所需要的指令数量。
基于线程的并发编程
- 这是前两种创建并发逻辑流方法的混合。
Posix线程
- 线程的代码和本地数据被封装在一个线程例程中。每一个线程例程都以一个通用指针作为输入,并返回一个通用指针。
创建线程
- 通过调用
pthread create
函数创建一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。新线程可以通过调用pthread _self函数来获得自己的线程ID。
终止线程
-一个线程的终止方式:
(1)当顶层的线程例程返回时,线程会隐式的终止;
(2)通过调用
pthread _exit
函数,线程会显示地终止。如果主线程调用pthread _exit
,它会等待所有其他对等线程终止,然后再终止主线程和整个进程。
回收已终止线程的资源
- 通过调用
pthread _join
函数等待其他线程终止,回收已终止线程占用的所有存储器资源。pthread _join
函数只能等待一个指定的线程终止。
分离线程
- 在任何一个时间点上,线程是可结合的,或是分离的。一个可结合的线程能够被其他线程收回其资源和杀死;一个可分离的线程是不能被其他线程回收或杀死的。它的存储器资源在它终止时有系统自动释放。
- 通过调用
pthread _detach
函数被分离。
初始化线程
- 通过调用
pthread_once
函数初始化与线程例程相关的状态。
多线程程序中的共享变量
- 一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。
线程存储器模型
- 寄存器从不共享,虚拟存储器总是共享的。
将变量映射到存储器
- 全局变量:全局变量是定义在函数之外的变量。运行时虚拟内存的读/写区域只包含每个全局变量的一个实例,任何线程都可以引用。
- 本地自动变量:本地自动变量是定义在函数内部但没有static属性的变量。运行时每个线程的栈都包含它自己的所有本地自动变量的实例。
- 本地静态变量:本地静态变量是定义在函数内部并有static属性的变量。和全局变量一样运行时虚拟内存的读/写区域只包含每个本地静态变量的一个实例。
教材学习中的问题和解决过程
问题1:如何判断线程是否安全
解答:
线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。
线程不安全:如果一个函数不是线程安全的,就是线程不安全的。
代码托管
结对及互评
本周结对学习情况
20155314刘子健
- 结对学习内容
第十二章
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 50/50 | 1/1 | 5/5 | |
第二周 | 100/100 | 1/2 | 5/10 | |
第三周 | 100/200 | 1/3 | 5/15 | |
第四周 | 100/300 | 1/4 | 5/20 | |
第五周 | 100/400 | 1/5 | 5/25 | |
第六周 | 100/500 | 1/6 | 5/30 | |
第七周 | 100/600 | 1/7 | 5/35 |