分类: 服务器

服务器

git基于某个Tag修改提交

如果要在某个tag的基础上做修改,直接切换到tab,修改后是无法提交的。

因为这时HEAD指向了一个具体的commit id,而没有处在一个分支中。

解决方法
先根据这个tag新建一个分支
git checkout -b 新分支 tag名

$ git checkout -b newbranch tag1.1

然后在这个新分支上修改后,提交代码

Java SpringBoot 上传图片到服务器,完美可用

基本上每个我们项目都会有上传图片的操作

老规矩先上效果图

%title插图%num

%title插图%num

1.首先贴一下上传文件的工具类

import java.io.File;
import java.io.FileOutputStream;

/**
* @Author: Manitozhang
* @Data: 2019/1/9 16:51
* @Email: manitozhang@foxmail.com
*
* 文件工具类
*/
public class FileUtil {

public static void uploadFile(byte[] file, String filePath, String fileName) throws Exception {
File targetFile = new File(filePath);
if(!targetFile.exists()){
targetFile.mkdirs();
}
FileOutputStream out = new FileOutputStream(filePath+fileName);
out.write(file);
out.flush();
out.close();
}

}
2.再贴一下Controller层的代码

@Resource
HttpServletRequest request;

//处理文件上传
@RequestMapping(value=”/testuploadimg”, method = RequestMethod.POST)
public @ResponseBody String uploadImg(@RequestParam(“file”) MultipartFile file) {
String fileName = file.getOriginalFilename();
//设置文件上传路径
String filePath = request.getSession().getServletContext().getRealPath(“imgupload/”);
try {
FileUtil.uploadFile(file.getBytes(), filePath, fileName);
return “上传成功”;
} catch (Exception e) {
return “上传失败”;
}
}
因为我们的路径是上传到SpringBoot自带的Tomcat服务器中,所以在项目中看不到,不过可以直接获取到

声明:这个方法只要Tomcat重启之后之前上传的图片就会丢失

这就可以上传成功了,上传位置可以修改

用树莓派搭建svn服务器

打算用树莓派作为自己的服务器了, 搭建一个svn服务器是必要的, 来看看:

1.   安装svn服务器:

 

sudo apt-get install subversion
2.   创建svn仓库, 我用/home/pi/svn_taoge作为svn仓库的根路径

 

 

svnadmin create /home/pi/svn_taoge
3.   修改配置文件:

vim /home/pi/svn_taoge/conf/svnserve.conf
%title插图%num

4. 都配置好了, 现在来给用户开放权限吧, 用户名taoge, 密码xxxxxx, 如下:

%title插图%num

为了安全, 其实都应该对密码进行加密, 我这里先不考虑这些了。

5. 接下来肯定是要启动svn服务啊:

 

svnserve -d -r /home/pi/svn_taoge
可以ps看一下, 确实启动了。但是, 这里其实有个问题, 那就是当树莓派服务器重启后, svnserve并没有重启。 那怎么让svnserve开启自动重启呢? 在/etc/rc.local中加入如上语句就可以了, 试了一下, 靠谱。

 

访问svn的方法是:svn://192.168.1.34  , 其中192.168.1.34就是树莓派的ip.  访问过程, 肯定是要输入用户和密码的。

接下来, 我在Windows上安装了TortoiseSVN客户端。 怎么初次创建文件呢? 如下两种方法都可以

1. 用 svn的import功能, 初始化创建仓库文件。

2  用 svn的 check out先下载文件(实际上仓库为空), 然后add文件, 然后commit.

 

在这里, 我用方法2, 搞定, 来看看:

%title插图%num

如上是Windows下的svn客户端, 其实也可以创建linux下的svn客户端, 道理类似。比如, 可以直接在树莓派上执行 svn co svn://localhost  , 就把刚才的test.txt下载下来了, 此时, svn服务端和svn客户端在同一台机器上。

另外, 可以去树莓派服务器上看下svn服务器中是否有test.txt文件, 发现没有, 为什么呢? 因为svn服务器做了手脚, 并不会直接存test.txt文件, 想详细了解, 可以自己在网上查询原因, 简单。

 

树莓派搭建web服务器

安装nginx+sqlite+php5打造轻量级W服务器

简单介绍一下

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。如果建站只要求静态网页建议使用Nginx

 

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等

 

PHP是一种简单、轻便的服务器端脚本语言。

 

 

教程开始

*步安装Nginx

sudo apt-get install nginx

第二步安装php+sqlite

sudo apt-get install php5-fpm sqlite

第三步配置nginx

1打开文件

sudo  nano  /etc/nginx/sites-available/default

2修改文件

改之前这样
%title插图%num

改成这样

location / {

index index.php index.html index.htm ;

}

location ~ \.php$ {

fastcgi_pass      unix:/var/run/php5-fpm.sock;

fastcgi_index index.php;

fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;

include        fastcgi_params;

}

location ~ \.sqlite$ {

deny all;

}

}

第四步大功告成,重启服务

sudo service nginx restart

一个轻量级的WEB服务器已经建设好了

开始测试一下我们的服务器

编写一个php网站

 

1.建立文件

sudo vi /var/www/html/index.php

2写一个简单的网页代码

<?PHP

echo “长春工业大学 计算机科学与工程学院”;

3.保存退出

4打开浏览器输入你的树莓派IP就可以看到你的网页啦

%title插图%num

Qt之FTP的上传下载(代码实现)

FTP服务器实现是我整个智能监控系统的一部分,所以在这里记录。
实现并不是很难,大家一起学习看看。
我用的是腾讯云,在此感谢腾讯的校园计划。
整个UI就是两个按钮,一个上传,一个下载。
看看界面,

%title插图%num

非常简单的。。。。

在此实现的FTP包括两个类,一个是界面类,一个是Ftp实现的类,

widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<client.h>
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
Q_OBJECT

public:
explicit Widget(QWidget *parent = 0);
~Widget();

FtpCLient client;
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();

private:
Ui::Widget *ui;
};

#endif // WIDGET_H

widget.cpp
#include “widget.h”
#include “ui_widget.h”

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
client.FtpSetUserInfor(“userFtpName”, “userFtpPwd”);
client.FtpSetHostPort(“userHost”);
}

Widget::~Widget()
{
delete ui;
}

void Widget::on_pushButton_clicked()
{
client.FtpPut(“F:\\this_qt\\ftp\\image\\4.jpg”, “3.jpg”);
}

void Widget::on_pushButton_2_clicked()
{
client.FtpGet(“1.jpg”, “F:\\this_qt\\ftp\\image\\2.jpg”);
}

client.h
#ifndef FTPCLIENT_H
#define FTPCLIENT_H
#include <QObject>

#include <QFile>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QNetworkReply>
#include <QByteArray>
#include <QMessageBox>
#include <QFileInfo>
#include <QDir>

class FtpCLient:public QObject
{
Q_OBJECT
protected slots:
void finished(QNetworkReply * reply);
public:
FtpCLient();
void FtpGet(QString sor, QString dev);
void FtpPut(QString source, QString dev);
void FtpSetUserInfor(QString user, QString pwd);
void FtpSetHostPort(QString str, int port =21);
private:
QFile * m_pFile;
QNetworkReply *m_pReply;
QNetworkAccessManager * m_pManager;
QUrl * m_pUrl;

};

#endif // FTPCLIENT_H

client.cpp
#include “client.h”

FtpCLient::FtpCLient()
{
m_pManager = new QNetworkAccessManager();
m_pUrl = new QUrl();
m_pUrl->setScheme(“ftp”);
connect(m_pManager,SIGNAL(finished(QNetworkReply*)),this,SLOT(finished(QNetworkReply *)));
}

void FtpCLient::finished(QNetworkReply * reply)
{
m_pFile->write(reply->readAll());
m_pFile->flush();
m_pFile->close();
reply->deleteLater();
}

//设置FTP服务器用户名和密码
void FtpCLient::FtpSetUserInfor(QString user, QString pwd)
{
m_pUrl->setUserName(user);
m_pUrl->setPassword(pwd);
}
//设置地址和端口
void FtpCLient::FtpSetHostPort(QString str, int port )
{
m_pUrl->setHost(str);
m_pUrl->setPort(port);
}
//下载文件
void FtpCLient::FtpGet(QString sor, QString dev)
{
QFileInfo info;
info.setFile(dev);
m_pFile = new QFile(info.filePath());
m_pFile->open(QIODevice::Append|QIODevice::WriteOnly);
m_pUrl->setPath(sor);

m_pReply = m_pManager->get(QNetworkRequest(*m_pUrl));
}
//上传文件
void FtpCLient::FtpPut(QString source, QString dev)
{
QFile file(source);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();

m_pUrl->setPath(dev);
m_pManager->put(QNetworkRequest(*m_pUrl), data);
}

至此qt的FTP客户端就完成了

java实现 FTP实现上传、下载

上传

/**
* FTP上传单个文件测试
*/
public static void uploadFtpFile(String hostname,String username,
String password,String uploadFilePath,String fileName,String ftpWorkPath)
throws RuntimeException{
FTPClient ftpClient = new FTPClient();
FileInputStream fis = null;

try {
ftpClient.connect(hostname);
ftpClient.login(username, password);

File srcFile = new File(uploadFilePath+fileName);
fis = new FileInputStream(srcFile);
//设置上传目录
ftpClient.changeWorkingDirectory(“/”+ftpWorkPath);
ftpClient.setBufferSize(1024);
ftpClient.setControlEncoding(“GBK”);

//设置为被动模式
ftpClient.enterLocalPassiveMode();

//设置文件类型(二进制)
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.storeFile(fileName, fis);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“FTP客户端出错!”, e);
} finally {
IOUtils.closeQuietly(fis);
try {
ftpClient.disconnect();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“关闭FTP连接发生异常!”, e);
}
logger.info(“已上传至FTP服务器路径!”);
}
}
下载

/**
* 获取FTPClient对象
*
* @param ftpHost
* FTP主机服务器
* @param ftpPassword
* FTP 登录密码
* @param ftpUserName
* FTP登录用户名
* @param ftpPort
* FTP端口 默认为21
* @return
*/
public static FTPClient getFTPClient(String ftpHost, String ftpUserName,
String ftpPassword, int ftpPort) throws RuntimeException{
FTPClient ftpClient = new FTPClient();
try {
ftpClient = new FTPClient();
ftpClient.connect(ftpHost, ftpPort);// 连接FTP服务器
ftpClient.login(ftpUserName, ftpPassword);// 登陆FTP服务器
if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
logger.info(“未连接到FTP,用户名或密码错误。”);
ftpClient.disconnect();
} else {
logger.info(“FTP连接成功。”);
}
} catch (SocketException e) {
e.printStackTrace();
logger.info(“FTP的IP地址可能错误,请正确配置。”);
throw new RuntimeException(“FTP的IP地址可能错误,请正确配置!”, e);
} catch (IOException e) {
e.printStackTrace();
logger.info(“FTP的端口错误,请正确配置。”);
throw new RuntimeException(“FTP的端口错误,请正确配置!”, e);
}
return ftpClient;
}

/**
* 从FTP服务器下载文件
* @param ftpHost FTP IP地址
* @param ftpUserName FTP 用户名
* @param ftpPassword FTP用户名密码
* @param ftpPort FTP端口
* @param ftpPath FTP服务器中文件所在路径 格式: ftptest/aa
* @param localPath 下载到本地的位置 格式:H:/download
* @param fileName 文件名称
*/
public static boolean downloadFtpFile(String ftpHost, String ftpUserName,
String ftpPassword, int ftpPort, String ftpPath, String localPath,
String fileName) throws RuntimeException{
boolean flag = false;
FTPClient ftpClient = null;

try {
ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
ftpClient.setControlEncoding(“UTF-8”); // 中文支持
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory(ftpPath);

File localFile = new File(localPath + File.separatorChar + fileName);
OutputStream os = new FileOutputStream(localFile);
ftpClient.retrieveFile(fileName, os);
os.close();
ftpClient.logout();
flag = true;
} catch (FileNotFoundException e) {
logger.error(“没有找到” + ftpPath + “文件”);
e.printStackTrace();
throw new RuntimeException(“没有找到” + ftpPath + “文件:”, e);
} catch (SocketException e) {
logger.error(“连接FTP失败.”);
e.printStackTrace();
throw new RuntimeException(“连接FTP失败:”, e);
} catch (IOException e) {
e.printStackTrace();
logger.error(“文件读取错误。”);
e.printStackTrace();
throw new RuntimeException(“文件读取错误:”, e);
}
return flag;
}
测试代码

public static void main(String[] args) {
//下载
// FavFTPUtil.downloadFtpFile(“192.168.2.133″,”adks”,”adconcepts2017″,
// “fileConllect/test”,”E:\\xcc”, “53840afe-0682-4960-84ef-3f3b972a0f12.zip”);

FavFTPUtil.uploadFtpFile(“192.168.2.133″,”test”,”123″,
“E:\\360\\pic\\”, “a.txt”,
“fengWu/”);

}

简单FTP实现总结

首先,做FTP需要用到文件操作,进程和网络编程的内容。

第二,明确编程环境,基于LINUX系统下的gcc编译器。

第三,设定一个大体思路,FTP能干神魔?自己能够实现怎样的FTP功能?

第四,就是对各个功能的实现给一个大体的思路,并实现它。

*后就是对程序的整体编排,实现简单的FTP功能。

做一个简单的FTP服务器,首先想到的是,它会有客户端和服务端;其次,它可以进行文件的上传和下载,以及其他的功能,像ls,ls_R, pwd等功能;*后就是,应该有一个系统日志文件来记录客户端所进行的操作及出现错误信息时应有的错误提示。

那么,在做FTP之前,我自己先有一个答题的构思,因此画了一个流程图,如下:
%title插图%num

我不想再长篇叙述我的FTP的编写过程,就之说一下在编写过程中所遇到的问题和解决方法。

问题一:如何解决缓存问题?

我们都知道,数据在使用之前一般都存放在缓冲区中,可想而知,如果缓冲区太小就会发生数据的溢出,如果缓冲区过大就有点不太科学了,同时也会影响程序的执行效率;所以,就要对缓存这块进行处理。刚开始在做ls时,我是将服务器对命令ls解析的结果,也就是文件名全都放在一个以为数组里面filename[1024000],但是这个数组的长度却设置的很大,而实际机器给数据所分配的缓冲区一般为1024个字节,所以这样做势必对缓存区的影响很大。如果数据比较少,那么就没有神魔问题;如果数据过大,数据溢出,则会影响其他的,因此,用了一个while(1)这样一个循环来解决这个问题,当然是有判断条件的,当条件满足时程序会自动跳出循环。

问题二:文件上传和下载过程中遇到的同名问题怎样解决?

有的时候可能因为疏忽的原因会给不同内容的文件取一个相同的名字,毫无疑问,这就会发生文件的覆盖。解决的办法有两种:一、注意不要对不同文件其相同名字;二、通过ls_R查询上传或下载目录下的所有文件名,如果和你将要取的名字发生冲突,那就赶快换。。。

问题三:这个是我自己现在还没有解决的问题,首先是我自己没有考虑用户权限的问题,假如说在上传和下载的文件里,有人想恶意篡改一些东西就会变得轻而易举,所以在用户权限这块还得加强;其次就是系统日志,在我的程序里并没有写错误处理日志,对于一个好的程序来说应该是有的,因为这个错误处理日志可以帮助编写程序者找到很多错误,假如说某段代码里面出现了问题却并没有错误提示,那么就会带来很大不便,所以这块还得继续学习!

*后就是我对这个暑假的总结了,五个字,快乐并充实!

什么是http服务器

本篇文章旨在从服务器后台开发的角度剖析一个简单的http服务器的运行原理.

我们知道浏览器是http(s)的客户端,目的是连接远程的http服务器,然后服务器返回浏览器数据.浏览器接收数据解析数据之后展现出来.我们看到的外在表现就是,浏览器访问一个url,然后就得到相应的web页面.

同样我们知道,浏览器与http服务器是通过http协议,传输层是tcp协议,因为他是有连接,可靠的协议.关于http协议简单的介绍一下:

一个标准的HTTP请求由以下几个部分组成

  1. <request-line>
  2. <headers>
  3. <CRLF>
  4. [<request-body><CRLF>]

在HTTP请求中,*行是请求行(request-line),用来说明请求类型、要访问的资源(URL)以及使用的HTTP版本;
紧接着是多行头部(headers)信息,用来说明服务器要使用的附加信息;
头部信息之后是一个回车换行符(\r\n),用于标明头部信息的结束。
以上是必须内容,根据需要可在头部信息结束之后增加主体数据(request-body);

主体数据之后是一个回车换行符(\r\n),用于标明主体数据的结束。

例如,我们可以在IE浏览器上输入下面的网址:

http://localhost:8000/hello/index.html

HTTP请求的头部信息如下:

  1. GET /hello/index.html HTTP/1.1
  2. Accept: */*
  3. Accept-Language: zh-cn
  4. Accept-Encoding: gzip, deflate
  5. Host: localhost:8000
  6. Connection: Keep-Alive
  7. Cookie: JSESSIONID=BBBA54D519F7A320A54211F0107F5EA6

收到请求数据之后,服务器解析,毕竟是明文字符,这个简单.然后服务器就知道了客户端的要求–获取目录hello/index.html文件.服务器读取文件内容发送给浏览器就好了.

后来随着业务逻辑越来越复杂,单单获取某个html文件功能早已不能满足需求,个性化需求呼之欲出.比如在线问卷调查表,他究竟是怎么把我们填写的数据传递给服务器的呢?

你可能会说那不是一样,客户端发送什么内容,服务器就接收什么内容.可是你想过没有,每个网站的需求是不一样的,本来服务器接收到浏览器的请求数据已经是够复杂的了,还让服务器来解析数据并响应不同的数据处理,这不太现实.

一般的,服务器*好只接收数据,如果让服务器也处理数据逻辑,势必会让服务器变得很复杂,稳定性也得不到保证.

另外一个角度是为了让程序复用,提高生产效率.也就是说,如果不关注业务逻辑,只注重接收数据,那么服务器程序可以给任何一个开发者使用.换句话说,我们不用从头开始写.直接使用现有的高性能的服务器就可以满足需求了.例如公司白领中午要吃饭,不可能跑回家自己去做饭吃,自己叫外卖就好了.

但是现实问题仍然没有解决,通过什么方式去处理业务逻辑呢?

你要给手机充电时,把插头插入插线板就能获取电了.插线板有接口,提供了电.

同理服务器程序*好也提供接口,浏览器通过统一的接口给服务器,然后我们从服务器接口中获取我们想要的数据.获取数据之后我们可以把数据交给第三方程序来处理逻辑,这样就做到与服务器业务分离了,good iead.

事实上,现在的http服务器就是这么做的,不过很复杂而已.下一篇开始为您介绍这些接口.

附录:HTTP Request Header 请求头

 

Header 解释 示例
Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html
Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5
Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip
Accept-Language 浏览器可接受的语言 Accept-Language: en,zh
Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes
Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache
Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close
Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new;
Content-Length 请求的内容长度 Content-Length: 348
Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded
Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT
Expect 请求的特定的服务器行为 Expect: 100-continue
From 发出请求的用户的Email From: user@email.com
Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com
If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”
If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT
If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”
If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d”
If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT
Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10
Pragma 用来包含实现特定的指令 Pragma: no-cache
Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Range 只请求实体的一部分,指定范围 Range: bytes=500-999
Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.zcmhi.com/archives/71.html
TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5
Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11
User-Agent User-Agent的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)
Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)
Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning

http服务器实现

前言
在实践的过程中,我发现,协议理解的深浅,阅读协议文档 < 看协议实现源码 < 自己实现协议的代码。
深入学习http服务器,这是本文的目的,而不是实现一个真正可用的http服务器。毕竟实现一个成熟可用http服务器的难度很大。软件都经历过很多版本的迭代,在不断测试、bug调试和完善功能的过程中,*终才变得成熟可用的。像BAT等大公司听说也是用现有的成熟框架来裁剪开发服务器的。本文参考的源码有boa服务器源码。boa源码下载
本文只是一个服务器的框架程序,在接下来的文章中,我将一步一步完善这个http服务器的功能,并把实验的成果分享出来。我想体现的是一个程序从零开发的思路,因为当面对一大坨一大坨完整的程序,有时会显得很茫然,没经验的很难体会到作者的设计意图。
多年的经验告诉我,如果想要一次性写出完美程序,那么*后就可能因为无从下手而什么都没有写。允许缺陷,开始动手吧!无论过程多么丑陋,*后也会结出经验的果实。
这是*篇,希望自己能够坚持下去(确实写文章也需要花费很多时间)。

一、select机制
因为下文的程序框架运用到了select机制,这里有必要再回顾一下,参考我以前的博文:TCP socket select用法分析
首先,我们来看看select函数的定义和参数的含义:

int select( int nfds, fd_set FAR* readfds, fd_set * writefds, fd_set * exceptfds, const struct timeval * timeout)
1
参数含义:

nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的*大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
readfds:(可选)指针,指向一组等待可读性检查的套接口。
writefds:(可选)指针,指向一组等待可写性检查的套接口。
exceptfds:(可选)指针,指向一组等待错误检查的套接口。
timeout:select()*多等待时间,对阻塞操作则为NULL。
返回值:
select()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError获取相应错误代码。

当返回为-1时,所有描述符集清0。
当返回为0时,表示超时。
当返回为正数时,表示已经准备好的描述符数。
select()返回后,在3个描述符集里,依旧是1的位就是准备好的描述符。这也就是为什么,每次用select后都要用FD_ISSET的原因。
select函数实现I/O多路复用,可以用来监视多个描述符,之后我们调用FD_ISSET函数确定具体是哪一个描述符准备好了。
那怎样才算准备好了呢?《unix环境高级编程》中,提到:

若对读集中的一个描述符进行的read操作不会阻塞,则认为此描述符是准备好的。
若对写集中的一个描述符进行的write操作不会阻塞,则认为此描述符是准备好的。
若对异常条件集中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。
对于读、写和异常条件,普通文件的文件描述符总是认为准备好的。
操作select函数,还需要以下几个函数配合。
void FD_CLR(int fd, fd_set *set) // 清除set集合中描述符fd
int FD_ISSET(int fd, fd_set *set) //判断set集合中描述符fd是否准备好
void FD_SET(int fd, fd_set *set) //将描述符fd添加进集合set(其实是将某一位置1)。
void FD_ZERO(fd_set *set) //将set集全部清除

我们来看看下文使用到的select程序片段:

fd_set block_read_fdset;
int max_fd;
void select_loop(int server_s)
{
FD_ZERO(&block_read_fdset);
max_fd = server_s+1;

while (1) {
BOA_FD_SET(server_s, &block_read_fdset);
//没有可读的文件描述符,就阻塞。
if (select(max_fd + 1, &block_read_fdset,NULL, NULL,NULL) == -1) {
if (errno == EINTR)
continue;
else if (errno != EBADF) {
perror(“select”);
}
}
if (FD_ISSET(server_s, &block_read_fdset))
process_requests(server_s);

}
}

上面的程序,定义一个block_read_fdset读集合,然后调用FD_ZERO(&block_read_fdset) 初始化,接着把监听socket连接的文件描述符加入到读集合block_read_fdset。注意,这时的socket_s还不能用read或write进行读写,必须调用accept函数返回的描述符才行!在while循环中调用select函数监听是否有客户端连接进来,当有客户端向服务器发起connect的时候,则认为server_s描述符是准备好,select函数就会返回,否则select会一直阻塞。因为我们这里没有设置select的超时时间,所以当监听的描述符没有准备好的时候,select默认会阻塞。当select返回的时候,会把在block_read_fdset读集合中没有准备好的文件描述相对应的位给清零。select返回后,我们调用FD_ISSET判断block_read_fdset中server_s描述符对应的位是否被置1了,如果是,说明文件描述符可读,然后调用process_requests函数处理客户端的请求。因为这里select只有添加了一个server_s文件描述符,所以有没有用FD_ISSET判断都无所谓。但是为了适应以后多个描述符的情况,还是添加了FD_ISSET判断,方便移植。

二、服务器源码
下面的服务器程序中,在main函数使用了socket套接字创建TCP面向连接的套接字,流程已经模式化了,在*后调用select_loop函数处理客户端请求。select_loop函数在前面已经分析过了,*后会调用process_requests函数处理客户端的请求。我们可以看到process_requests函数调用accept函数,之后就可以利用accept函数返回的文件描述符来与客户端通信。使用read函数来读取客户端发送过来的信息,并打印到终端。程序显示了一个服务器基本的框架。在后续的文章中,我将在process_requests函数中解析http报文。协议类的都是这样子,客户端发送过来一连串的字符,服务器根据协议约定好的规则去解析这些报文,并根据解析出来的字段去干某些事。如果,客户端发送的是加密的字符,还需要解密之后再进行解析。

//web-server.c, an http server

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>

#define BUFFER_SIZE 4096
#define MAX_QUE_CONN_NM 5
#define PORT 6000
//#define MAXSOCKFD 10
#define FILE_NAME_MAX 512
#define SOCKADDR sockaddr_in
#define S_FAMILY sin_family
#define SERVER_AF AF_INET

fd_set block_read_fdset;
int max_fd;
#define BOA_FD_SET(fd, where) { FD_SET(fd, where); \
if (fd > max_fd) max_fd = fd; \
}

void select_loop(int server_s);
int process_requests(int server_s);

int main(int argc,char* argv[])
{
int sockfd;
int sin_size = sizeof(struct sockaddr);
struct sockaddr_in server_sockaddr, client_sockaddr;
int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */

/*建立socket连接*/
if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
{
perror(“socket”);
exit(1);
}
printf(“Socket id = %d\n”,sockfd);

/*设置sockaddr_in 结构体中相关参数*/
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(server_sockaddr.sin_zero), 8);

setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

/*绑定函数bind*/
if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)
{
perror(“bind”);
exit(1);
}
printf(“Bind success!\n”);

/*调用listen函数*/
if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror(“listen”);
exit(1);
}
printf(“Listening….\n”);
select_loop(sockfd);
return 0;
}

void select_loop(int server_s)
{
FD_ZERO(&block_read_fdset);
max_fd = server_s+1;
while (1) {
BOA_FD_SET(server_s, &block_read_fdset);
//没有可读的文件描述符,就阻塞。
if (select(max_fd + 1, &block_read_fdset,NULL, NULL,NULL) == -1) {
if (errno == EINTR)
continue; /* while(1) */
else if (errno != EBADF) {
perror(“select”);
}
}
if (FD_ISSET(server_s, &block_read_fdset))
process_requests(server_s);
}
}

int process_requests(int server_s)
{
int fd; /* socket */
struct SOCKADDR remote_addr; /* address */
int remote_addrlen = sizeof (struct SOCKADDR);
size_t len;
char buff[BUFFER_SIZE];
bzero(buff,BUFFER_SIZE);
//remote_addr.S_FAMILY = 0xdead;
fd = accept(server_s, (struct sockaddr *) &remote_addr,
&remote_addrlen);

if (fd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
/* abnormal error */
perror(“accept”);
return -1;
}

int bytes = read(fd, buff, BUFFER_SIZE);
if (bytes < 0) {
if (errno == EINTR)
bytes = 0;
else
return -1;
}
printf(“recv from client:%s\n”,buff);
return 0;
}

三、客户端测试程序
接下来,写一个简单的客户端程序来测试服务器程序。程序如下:

/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 6000
#define BUFFER_SIZE 4096
#define FILE_NAME_MAX 512

int main(int argc,char* argv[])
{
int sockfd;
//char buff[BUFFER_SIZE];
struct hostent *host;
struct sockaddr_in serv_addr;

if(argc != 2)
{
fprintf(stderr,”Usage: ./client Hostname(or ip address) \ne.g. ./client 127.0.0.1 \n”);
exit(1);
}

//地址解析函数
if ((host = gethostbyname(argv[1])) == NULL)
{
perror(“gethostbyname”);
exit(1);
}
//创建socket
if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror(“socket”);
exit(1);
}
bzero(&serv_addr,sizeof(serv_addr));
//设置sockaddr_in 结构体中相关参数
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT); //将16位的主机字符顺序转换成网络字符顺序
serv_addr.sin_addr = *((struct in_addr *)host->h_addr); //获取IP地址
bzero(&(serv_addr.sin_zero), 8); //填充0以保持struct sockaddr同样大小

//调用connect函数主动发起对服务器端的连接
if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr))== -1)
{
perror(“connect”);
exit(1);
}

char buff[BUFFER_SIZE]= “<letter>getPwd</letter>”; //读写缓冲区
int count;
count=send(sockfd,buff,100,0);
if(count<0)
{
perror(“Send file informantion”);
exit(1);
}
printf(“client send OK count = %d\n”,count);
return 0;
}

程序很简单,只是使用socket的框架建立一个客户端,然后用send函数发送一段字符串。我们打开终端实验一下。
打开一个终端,A 终端,先运行服务器程序,结果如下:

ubuntu@ubuntu:~/project/web-server$ ./web-server
Socket id = 3
Bind success!
Listening….

然后再打开一个终端,B终端,运行客户端程序,结果如下:

ubuntu@ubuntu:~/project/web-server$ ./client 127.0.0.1
client send OK count = 100

再看看A终端,发现服务器程序接收到了来自客户端发送的字符串。

ubuntu@ubuntu:~/project/web-server$ ./web-server
Socket id = 3
Bind success!
Listening….
recv from client:<letter>getPwd</letter>

解决GitHub访问速度缓慢,延迟高的问题

随着微软大大宣布GitHub针对个人用户的仓库免费,相信每位开发者都感受到了“真香”。

 

%title插图%num

然而因为一些众所周知的原因,国内访问GitHub总会遇到下载速度缓慢、链接意外终止的情况。

%title插图%num

为了更加愉快地使用全球*大同性交友网站上的优质资源,我们来做一些简单的本机上的调整。

通过查看下载链接,能够发现*终被指向到Amazon的服务器(http://github-cloud.s3.amazonaws.com)了。由于国内访问亚马逊网站非常慢,我们需要修改Hosts文件来实现流畅访问。

*步,打开本机上的Hosts文件

首先,什么是Hosts文件?

在互联网协议中,host表示能够同其他机器互相访问的本地计算机。一台本地机有唯一标志代码,同网络掩码一起组成IP地址,如果通过点到点协议通过ISP访问互联网,那么在连接期间将会拥有唯一的IP地址,这段时间内,你的主机就是一个host。

在这种情况下,host表示一个网络节点。host是根据TCP/IP for Windows 的标准来工作的,它的作用是包含IP地址和Host name(主机名)的映射关系,是一个映射IP地址和Host name(主机名)的规定,规定要求每段只能包括一个映射关系,IP地址要放在每段的*前面,空格后再写上映射的Host name主机名 。对于这段的映射说明用“#”分割后用文字说明。

~Windows

Hosts文件的路径是:

C:\Windows\System32\drivers\etc

由于文件没有后缀名,可以利用鼠标右键点击,选择用记事本打开,如下图。

%title插图%num

~Mac

终端内输入:

sudo vim /etc/hosts

打开之后,我们就要向里面追加信息了。

第二步,追加域名的IP地址

我们可以利用https://www.ipaddress.com/ 来获得以下两个GitHub域名的IP地址:

(1) github.com

(2) github.global.ssl.fastly.net

打开网页后,利用输入框内分别查询两个域名:

%title插图%num

先试一下github.com:

%title插图%num

在标注的IP地址中,任选一个记录下来。

再来是github.global.ssl.fastly.net:

%title插图%num

将以上两段IP写入Hosts文件中:

%title插图%num

保存。

第三步,刷新 DNS 缓存

在终端或CMD中,执行以下命令:

ipconfig /flushdns

收工。

现在再来试一下 git clone 命令,是不是可以轻松过百K了? 🙂

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速