Linux网络编程-七 web服务器项目

  • 1 web服务器开发准备
    • 1.1 Html语言基础(和Markdown一个性质,某些程度上和Markdown通用,所以我在编辑的时候在<>里都加了空格,避免编辑器出问题)
    • 1.2 Html标签介绍
      • 1.2.1 题目标签
      • 1.2.2 文本标签
      • 1.2.3 列表标签
      • 1.2.4图片标签
      • 1.2.5 超链接标签
    • 1.3 http超文本传输协议
      • 1.3.1 http请求消息
      • 1.3.2 http响应消息
  • 2 web服务器开发
    • 2.1 基于epoll的web服务器
    • 2.2 基于epoll的服务器程序
    • 2.3 日志相关函数
    • 2.4 基于libevent的Web服务器

1 web服务器开发准备

为了编写web服务器,我们需要学会编写html页面,以及掌握部分http协议知识,这两部分内容将在接下来进行介绍。这两个准备工作之后,还需要知道web服务器的通信流程是什么?还需要思考如何支持多浏览器并发访问!

1.1 Html语言基础(和Markdown一个性质,某些程度上和Markdown通用,所以我在编辑的时候在<>里都加了空格,避免编辑器出问题)

Html简介
Html(Hyper Texture Markup Language)是超文本标记语言,在计算机中以 .html或者.htm作为扩展名,可以被浏览器识别,就是经常见到的网页。

Html的语法非常简洁,比较松散,以相应的英语单词关键字进行组合,html标签不区分大小写,标签大多数成对出现,有开始,有结束,例如 < html>,但是并没有要求必须成对出现.同时也有固定的短标签,例如< br/>,< hr/>。

学习html基本可以认为就是学习各种标签,标签也可以设置属性,例如< font color=“red”>hello, world< /font>,示例中color代表标签的颜色属性,red代表标签是红色字体,hello,world为实际显示的内容.可以新建一个文本文档,然后将后缀名修改.html文件,用代码编辑器打开该html文件可以编辑文件(例如notepad++),将上述内容保存到文件中,双击该文件可以看到如下效果:

Html的组成可以分为如下部分:

  1. < !doctype html> 声明文档类型,可以不写
  2. < html> 开始 和< /html> 结束,属于html的根标签
  3. < head>< /head> 头部标签,头部标签内一般有 < title>< /title>
  4. < body>< /body> 主体标签,一般用于显示内容

例如:

<html> <head><title>这是一个标题</title></head><body><font color="red" size="5">hello, world</font></body>
</html>

如果想要添加注释,可以使用 < !—我是注释 -->的方式.
也可以指定页面类型和字符编码,下面设置页面类型为html,并且字符编码为utf8:
< meta http-equiv=“content-Type” content=“text/html; charset=utf8”>
Html标签属性,可以双引号,单引号,或者不写。

1.2 Html标签介绍

1.2.1 题目标签

共有6种,< h1>,< h2>,…< h6>,其中< h1>最大,< h6>最小。

1.2.2 文本标签

< font>标签,可以设置颜色和字体大小属性
颜色表示方法(可以参考网站: http://tool.oschina.net/commons?type=3):

  • 英文单词 red green blue …
  • 使用16进制的形式表示颜色:#ffffff
  • 使用rgb(255,255,0)

字体大小可以使用size属性,大小范围为1-7,其中7最大,1最小。

有时候需要使用换行标签 ,这是一个短标签 < br/>。

与之对应另外还有一个水平线也是短标签, < hr/>,水平线也可以设置颜色和大小。

1.2.3 列表标签

列表标签分无序列表和有序列表,分别对应< ul>和< ol>。
无序列表的格式如下:

<ul><li>列表内容1</li><li>列表内容2</li>…
</ul>

无序列表可以设置type属性:

  • 实心圆圈:type=disc
  • 空心圆圈:type=circle
  • 小方块: type=square

有序列表的格式如下:

<ol><li>列表内容1</li><li>列表内容2</li>…
</ol>

有序列表同样可以设置type属性:

  • 数字:type=1,也是默认方式
  • 英文字母:type=a或type=A
  • 罗马数字:type=i或type=I

1.2.4图片标签

图片标签使用< img>,内部需要设置若干属性,可以不必写结束标签属性:

  1. src=”3.gif” 图片来源,必写
  2. alt=”小岳岳” 图片不显示时,显示的内容
  3. title=”我的天呐” 鼠标移动到图片上时显示的文字
  4. width=”600” 图片显示的宽度
  5. height=”400” 图片显示的高度

例如:

<img src="3.gif" alt="小岳岳" title="我的天呐!" width="300" height="200" />

注意:当图片未定义宽高,图片百分百比例显示,如果只改变图片宽度或者高度,会等比例缩放。

1.2.5 超链接标签

超链接标签使用< a>,同样需要设置属性表明要链接到哪里。
属性:

  1. href=”http://www.itcast.cn”,前往地址,必填,注意要写http://
  2. title=”前往传智” 鼠标移动到链接上时显示的文字
  3. target=”_self”或者”_blank”,_self是默认值,在自身页面打开,_blank是新开页面前往连接地址 。
    示例:
<a href=”http://www.itcast.cn” title=”去往传智” target=”_self” >来传智</a>

当我们访问某个网站的时候,当请求的资源不存在,经常会给我们报告一个错误,显示为404错误,一般会给请求用户返回一个错误页,大家可以自行尝试一下编写一个我们自己的错误页。

1.3 http超文本传输协议

http协议和html前面的ht都是超文本的意思,所以http与html是配合非常紧密的一对,我们可以认为http就是为了传输html这样的文件,http位于应用层,侧重于解释。

http协议对消息区分可以分为请求消息和响应消息。

1.3.1 http请求消息

我们要开发的服务器与浏览器通信采用的就是http协议,在浏览器想访问一个资源的时候,在浏览器输入访问地址(例如http://127.0.0.1:8000),地址输入完成后当敲击回车键的时候,浏览器就将请求消息发送给服务器。

我们可以先用测试工具创建一个socket服务器:
之后通过浏览器请求地址,就会看到浏览器发送过来的请求消息:
这个消息看起来很乱很复杂,对应的就是我们说的请求消息。
请求消息分为四部分内容:

  1. 请求行 说明请求类型,要访问的资源,以及使用的http版本
  2. 请求头 说明服务器使用的附加信息,都是键值对,比如表明浏览器类型
  3. 空行 不能省略-而且是\r\n,包括请求行和请求头都是以\r\n结尾
  4. 请求数据 表明请求的特定数据内容,可以省略-如登陆时,会将用户名和密码内容作为请求数据

请求类型
http协议有很多种请求类型,对我们来说常见的用的最多的是get和post请求。常见的请求类型如下:

  1. Get 请求指定的页面信息,并返回实体主体
  2. Post 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
  3. Head 类似于get请求,但是响应消息没有内容,只是获得报头
  4. Put 从客户端向浏览器传送的数据取代指定的文档内容
  5. Delete 请求服务器删除指定的页面
  6. Connect HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
  7. Options 允许客户端查看浏览器的性能
  8. Trace 回显服务器收到的请求,主要用于测试和诊断

get 和 post 请求都是请求资源,而且都会提交数据,如果提交密码信息用get请求,就会明文显示,而post则不会显示出涉密信息。

1.3.2 http响应消息

响应消息是代表服务器收到请求消息后,给浏览器做的反馈,所以响应消息是服务器发送给浏览器的,响应消息也分为四部分:

  1. 状态行 包括http版本号,状态码,状态信息
  2. 消息报头 说明客户端要使用的一些附加信息,也是键值对
  3. 空行 \r\n 同样不能省略
  4. 响应正文 服务器返回给客户端的文本信息

示例:

http常见状态码

http状态码由三位数字组成,第一个数字代表响应的类别,有五种分类:

  1. 1xx 指示信息–表示请求已接收,继续处理
  2. 2xx 成功–表示请求已被成功接收、理解、接受
  3. 3xx 重定向–要完成请求必须进行更进一步的操作
  4. 4xx 客户端错误–请求有语法错误或请求无法实现
  5. 5xx 服务器端错误–服务器未能实现合法的请求

常见的状态码如下:

  • 200 OK 客户端请求成功
  • 301 Moved Permanently 重定向
  • 400 Bad Request 客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 Forbidden 服务器收到请求,但是拒绝提供服务
  • 404 Not Found 请求资源不存在,eg:输入了错误的URL
  • 500 Internal Server Error 服务器发生不可预期的错误
  • 503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常

http常见文件类型分类

http与浏览器交互时,为使浏览器能够识别文件信息,所以需要传递文件类型,这也是响应消息必填项,常见的类型如下:

  • 普通文件: text/plain; charset=utf-8
  • *.html: text/html; charset=utf-8
  • *.jpg: image/jpeg
  • *.gif: image/gif
  • *.png: image/png
  • *.wav: audio/wav
  • *.avi: video/x-msvideo
  • *.mov: video/quicktime
  • *.mp3: audio/mpeg

特别说明

  • charset=iso-8859-1 西欧的编码,说明网站采用的编码是英文;
  • charset=gb2312 说明网站采用的编码是简体中文;
  • charset=utf-8 代表世界通用的语言编码;可以用到中文、韩文、日文等世界上所有语言编码上;
  • charset=euc-kr 说明网站采用的编码是韩文;
  • charset=big5 说明网站采用的编码是繁体中文;

2 web服务器开发

我们要开发web服务器已经明确要使用http协议传送html文件,那么我们如何搭建我们的服务器呢?注意http只是应用层协议,我们仍然需要选择一个传输层的协议来完成我们的传输数据工作,所以开发协议选择是TCP+HTTP,也就是说服务器搭建浏览依照TCP,对数据进行解析和响应工作遵循HTTP的原则。

这样我们的思路很清晰,编写一个TCP并发服务器,只不过收发消息的格式采用的是HTTP协议,如下图:
为了支持并发服务器,我们可以有多个选择,比如多进程服务器,多线程服务器,select,poll,epoll等多路IO工具都可以,甚至如果读者觉得libevent非常熟练的话,也可以使用libevent进行开发。

2.1 基于epoll的web服务器

由于我们知道epoll在大量并发少量活跃的情况下效率很高,所以本文以epoll为例,介绍epoll开发的主体流程:
处理客户端请求流程:
使用epoll开发的流程我们已经很熟悉,主要涉及到的问题大部分都是处理http协议或者处理客户端请求的一些细节:

2.2 基于epoll的服务器程序

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>#include "pub.h"
#include "wrap.h"//封装的错误函数int http_request(int cfd, int epfd);int main()
{//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, //则web服务器就会收到SIGPIPE信号struct sigaction act;act.sa_handler = SIG_IGN;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGPIPE, &act, NULL);//改变当前进程的工作目录char path[255] = {0};sprintf(path, "%s/%s", getenv("HOME"), "webpath");chdir(path);//创建socket--设置端口复用---bindint lfd = tcp4bind(9999, NULL);//设置监听Listen(lfd, 128);//创建epoll树int epfd = epoll_create(1024);if(epfd<0){perror("epoll_create error");close(lfd);return -1;}//将监听文件描述符lfd上树struct epoll_event ev;ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);int i;int cfd;int nready;int sockfd;struct epoll_event events[1024];while(1){//等待事件发生nready = epoll_wait(epfd, events, 1024, -1);if(nready<0){if(errno==EINTR){continue;}break;}for(i=0; i<nready; i++){sockfd = events[i].data.fd;//有客户端连接请求if(sockfd==lfd){//接受新的客户端连接cfd = Accept(lfd, NULL, NULL);//设置cfd为非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);//将新的cfd上树ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);}else {//有客户端数据发来http_request(sockfd, epfd);}            }       }
}int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{char buf[1024] = {0};sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);if(len>0){sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);}strcat(buf, "\r\n");Write(cfd, buf, strlen(buf));return 0;
}int send_file(int cfd, char *fileName)
{//打开文件int fd = open(fileName, O_RDONLY);if(fd<0){perror("open error");return -1;}//循环读文件, 然后发送int n;char buf[1024];while(1){memset(buf, 0x00, sizeof(buf));n = read(fd, buf, sizeof(buf));if(n<=0){break;}else {Write(cfd, buf, n);}}
}int http_request(int cfd, int epfd)
{int n;char buf[1024];//读取请求行数据, 分析出要请求的资源文件名memset(buf, 0x00, sizeof(buf));n = Readline(cfd, buf, sizeof(buf));if(n<=0){//printf("read error or client closed, n==[%d]\n", n);//关闭连接close(cfd);//将文件描述符从epoll树上删除epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1; }printf("buf==[%s]\n", buf);//GET /hanzi.c HTTP/1.1char reqType[16] = {0};char fileName[255] = {0};char protocal[16] = {0};sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);//printf("[%s]\n", reqType);printf("--[%s]--\n", fileName);//printf("[%s]\n", protocal);char *pFile = fileName;if(strlen(fileName)<=1){strcpy(pFile, "./");}else {pFile = fileName+1;}//转换汉字编码strdecode(pFile, pFile);printf("[%s]\n", pFile);//循环读取完剩余的数据,避免产生粘包while((n=Readline(cfd, buf, sizeof(buf)))>0);//判断文件是否存在struct stat st;if(stat(pFile, &st)<0){printf("file not exist\n");//发送头部信息send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);//发送文件内容send_file(cfd, "error.html");  }else //若文件存在{//判断文件类型//普通文件if(S_ISREG(st.st_mode)){printf("file exist\n");//发送头部信息send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);//发送文件内容send_file(cfd, pFile);}//目录文件else if(S_ISDIR(st.st_mode)){printf("目录文件\n");char buffer[1024];//发送头部信息send_header(cfd, "200", "OK", get_mime_type(".html"), 0); //发送html文件头部send_file(cfd, "html/dir_header.html");   //文件列表信息struct dirent **namelist;int num;num = scandir(pFile, &namelist, NULL, alphasort);if (num < 0){perror("scandir");close(cfd);epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1;}else {while (num--) {printf("%s\n", namelist[num]->d_name);memset(buffer, 0x00, sizeof(buffer));if(namelist[num]->d_type==DT_DIR){sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);}else{sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);}free(namelist[num]);Write(cfd, buffer, strlen(buffer));}free(namelist);}//发送html尾部sleep(10);send_file(cfd, "html/dir_tail.html");        }}return 0;
}

2.3 日志相关函数

待学习

2.4 基于libevent的Web服务器

这里不再作出分析,只给出代码

//通过libevent编写的web服务器
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "pub.h"
#include <event.h>
#include <event2/listener.h>
#include <dirent.h>#define _WORK_DIR_ "%s/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"int copy_header(struct bufferevent *bev,int op,char *msg,char *filetype,long filesize)
{char buf[4096]={0};sprintf(buf,"HTTP/1.1 %d %s\r\n",op,msg);sprintf(buf,"%sContent-Type: %s\r\n",buf,filetype);if(filesize >= 0){sprintf(buf,"%sContent-Length:%ld\r\n",buf,filesize);}strcat(buf,"\r\n");bufferevent_write(bev,buf,strlen(buf));return 0;
}
int copy_file(struct bufferevent *bev,const char *strFile)
{int fd = open(strFile,O_RDONLY);char buf[1024]={0};int ret;while( (ret = read(fd,buf,sizeof(buf))) > 0 ){bufferevent_write(bev,buf,ret);}close(fd);return 0;
}
//发送目录,实际上组织一个html页面发给客户端,目录的内容作为列表显示
int send_dir(struct bufferevent *bev,const char *strPath)
{//需要拼出来一个html页面发送给客户端copy_file(bev,_DIR_PREFIX_FILE_);//send dir info DIR *dir = opendir(strPath);if(dir == NULL){perror("opendir err");return -1;}char bufline[1024]={0};struct dirent *dent = NULL;while( (dent= readdir(dir) ) ){struct stat sb;stat(dent->d_name,&sb);if(dent->d_type == DT_DIR){//目录文件 特殊处理//格式 <a href="dirname/">dirname</a><p>size</p><p>time</p></br>memset(bufline,0x00,sizeof(bufline));sprintf(bufline,"<li><a href='%s/'>%32s</a>   %8ld</li>",dent->d_name,dent->d_name,sb.st_size);bufferevent_write(bev,bufline,strlen(bufline));}else if(dent->d_type == DT_REG){//普通文件 直接显示列表即可memset(bufline,0x00,sizeof(bufline));sprintf(bufline,"<li><a href='%s'>%32s</a>     %8ld</li>",dent->d_name,dent->d_name,sb.st_size);bufferevent_write(bev,bufline,strlen(bufline));}}closedir(dir);copy_file(bev,_DIR_TAIL_FILE_);//bufferevent_free(bev);return 0;
}
int http_request(struct bufferevent *bev,char *path)
{strdecode(path, path);//将中文问题转码成utf-8格式的字符串char *strPath = path;if(strcmp(strPath,"/") == 0 || strcmp(strPath,"/.") == 0){strPath = "./";}else{strPath = path+1;}struct stat sb;if(stat(strPath,&sb) < 0){//不存在 ,给404页面copy_header(bev,404,"NOT FOUND",get_mime_type("error.html"),-1);copy_file(bev,"error.html");return -1;}if(S_ISDIR(sb.st_mode)){//处理目录copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);send_dir(bev,strPath);}if(S_ISREG(sb.st_mode)){//处理文件//写头copy_header(bev,200,"OK",get_mime_type(strPath),sb.st_size);//写文件内容copy_file(bev,strPath);}return 0;
}void read_cb(struct bufferevent *bev, void *ctx)
{char buf[256]={0};char method[10],path[256],protocol[10];int ret = bufferevent_read(bev, buf, sizeof(buf));if(ret > 0){sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);if(strcasecmp(method,"get") == 0){//处理客户端的请求char bufline[256];write(STDOUT_FILENO,buf,ret);//确保数据读完while( (ret = bufferevent_read(bev, bufline, sizeof(bufline)) ) > 0){write(STDOUT_FILENO,bufline,ret);}http_request(bev,path);//处理请求}}
}
void bevent_cb(struct bufferevent *bev, short what, void *ctx)
{if(what & BEV_EVENT_EOF){//客户端关闭printf("client closed\n");bufferevent_free(bev);}else if(what & BEV_EVENT_ERROR){printf("err to client closed\n");bufferevent_free(bev);}else if(what & BEV_EVENT_CONNECTED){//连接成功printf("client connect ok\n");}
}
void listen_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{//定义与客户端通信的buffereventstruct event_base *base = (struct event_base *)arg;struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);bufferevent_setcb(bev,read_cb,NULL,bevent_cb,base);//设置回调bufferevent_enable(bev,EV_READ|EV_WRITE);//启用读和写
}int main(int argc,char *argv[])
{char workdir[256] = {0};sprintf(workdir,_WORK_DIR_,getenv("HOME"));//HOME=/home/itheima chdir(workdir);struct event_base *base = event_base_new();//创建根节点struct sockaddr_in serv;serv.sin_family = AF_INET;serv.sin_port = htons(9999);serv.sin_addr.s_addr = htonl(INADDR_ANY);struct evconnlistener * listener =evconnlistener_new_bind(base,listen_cb, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,(struct sockaddr *)&serv, sizeof(serv));//连接监听器event_base_dispatch(base);//循环event_base_free(base); //释放根节点evconnlistener_free(listener);//释放链接监听器return 0;
}

Linux网络编程-七相关推荐

  1. Linux网络编程必看书籍推荐

    首先要说讲述计算机网络和TCP/IP的书很多. 先要学习网络知识才谈得上编程 讲述计算机网络的最经典的当属Andrew S.Tanenbaum的<计算机网络>第五版,这本书难易适中. &l ...

  2. 不为人知的网络编程(七):如何让不可靠的UDP变的可靠?

    为什么80%的码农都做不了架构师?>>>    本文内容来自学霸君资深架构师袁荣喜的技术分享. 1.前言 最近和很多实时音视频领域的朋友交流中都有谈论到 RUDP(Reliable ...

  3. Linux网络编程——黑马程序员笔记

    01P-复习-Linux网络编程 02P-信号量生产者复习 03P-协议 协议: 一组规则. 04P-7层模型和4层模型及代表协议 分层模型结构: OSI七层模型: 物.数.网.传.会.表.应TCP/ ...

  4. [Linux网络编程学习笔记]索引

    一.Linux基本知识 [学习笔记]Linux平台的文件I/O操作 [学习笔记]Linux平台的文件,目录及操作 [Linux学习笔记]标准输入输出 [Linux学习笔记]进程概念及控制 [Linux ...

  5. Linux网络编程基础

    2019独角兽企业重金招聘Python工程师标准>>> (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍 客户端和服务端 网络程序和普通的程序有一个最大的 ...

  6. 150集Linux网络编程培训视频吐血整理 | 网络基础

    学习视频来源:<黑马程序员 - Linux网络编程> 协议的概念 什么是协议 从应用的角度出发,协议可理解为"规则",是数据传输和数据的解释的规则. 假设,A.B双方欲 ...

  7. Linux网络编程——千峰物联网笔记

    B站视频:千峰物联网学科linux网络编程 网址:https://www.bilibili.com/video/BV1RJ411B761?p=1 目录 第一章:计算机网络概述 1.1计算机网络发展简史 ...

  8. linux网络编程大杂烩==Linux应用编程7

    一.Linux 网络编程框架 1.网络是分层的 (1)OSI 七层模型:应用层.表示层.会话层.传输层.网络层.数据链路层.物理层. (2)网络为什么要分层:互联网及其复杂,需要分层以便更好地实现网络 ...

  9. Linux网络编程基础知识

    Linux网络编程基础知识 1. 协议的概念 1.1 什么是协议 1.2 典型协议 2 网络应用程序设计模式 2.1 C/S模式 2.2 B/S模式 2.3 优缺点 3 分层模型 3.1 OSI七层模 ...

最新文章

  1. java中堆与栈的区别_java中堆和栈的区别分析
  2. python 内置标准库socketserver模块的思考
  3. [html] 举例说明html的修饰元素有哪些?
  4. linux mysql 修改字符集_linux下mysql修改字符集,远程连接
  5. 组策略设置IE 11的Compatible View
  6. 数据挖掘–聚类思维导图
  7. 美丽的字符正方形FINAL
  8. presentViewController:navigationController animated:YES completion:^(void)
  9. bzoj 4895: 项链分赃(增强版)
  10. 无人驾驶(一)---汽车can总线通信之 peak pcan驱动安装与通信
  11. Intent.parseUri()详解
  12. HDUOJ 6555 The Fool
  13. 多目标优化系列(七)SPEA2
  14. JVM面试知识点合集 — Android 春招 2022
  15. 计算机睡眠打印机不可用设置,win7系统下打印机属性显示不可用如何解决
  16. cdh5.9运行mapreduce uber任务报java.lang.RuntimeException: native snappy library not available错误
  17. jbuilder的set!方法重构接口
  18. 预测师的随想系列二:记一次囚徒博弈游戏
  19. 红旗linux hba卡wwn,Redhat Linux下安装HBA卡并查看WWN号
  20. Facebook创始人原型电影《社交网络》票房夺冠

热门文章

  1. Java编程快速有效的学习方法有哪些?
  2. 类型转换——int转换成char(截短)
  3. centos 7.3 iscsi登录失败问题及解决方法
  4. 西北计算机大赛奖金有多少,我校学生2019年中国大学生计算机设计大赛西北赛区决赛中获得佳绩...
  5. 微信后台基于时间序的海量数据冷热分级架构设计实践
  6. 瓦片地图面面观之缩放级别
  7. 站长平台之360推送工具
  8. golang 源码分析之channel
  9. Linux - 部署node项目
  10. python 0 100被7整除_python: 输出 1~100 之间不能被 7 整除的数,每行输出 10 个数字,要求应用字符串格式化方法美化输出格式。...