memcache应对高并发量的优化

我们都知道memcahce是解决大并发量的很好一个工具,但是有一个问题值得注意,当突然有很多用户请求我们的某个页面,请求的参数相同,但对应的值却在memcahce中没有,此时这些请求都将会去查询数据库操作,瞬间对数据库照成了很大的压力。

下面我做了一个小例子来模拟这种情况。

原页面代码:

 connect("127.0.0.1",11211) or die("connect failed");

$key = $_GET['key'];
if($result = $memcache->get($key)) {
    $s = "get result form memcache: ".$key."/".$result."\n";
    file_put_contents("/var/www/html/log1.txt",$s,FILE_APPEND);
    echo $s;
}else {
    //模拟查询数据库
    sleep(5); //休眠5秒
    $result = mt_rand(1,100);
    $memcache -> set($key,$result,0,3600);
    $s = "get result not from memcache: ".$key."/".$result."\n";
    file_put_contents("/var/www/html/log2.txt",$s,FILE_APPEND);
    echo $s;
}
?>

此代码的意思是,我通过某个参数key来访问这个页面,如果此key在memcahce中存在,则在log1.txt中写入一条数据,如果不存在则休眠5秒(模拟查询数据库),在log2.txt中写入一条数据,同时将此key得到的结果放入memcahce中。

这里值得注意的是我在连接memcahce时候加了一句“or die(“connect failed”);”,即当连接报错时退出,因为有可能当memcahce的连接数超出了它本身限制的次数后,导致连接不上,从而会执行会执行else中的语句(而非memcahce中无值)。

测试并发

1)   使用ab命令测试

# ab -c 200 -n 30000 http://192.168.1.78/complicateTest.php?key=abcde

以上命令表示200个并发请求30000次

可以看到在log2文件中有许多数据,说明查询了很多次数据库:

2

可以使用对数据库查询加锁来处理,即在查询数据的时候,先在缓存中存一个值K,其他客户端需要查询数据库的时候要先判断K是否存在,若存在则休眠,如下:

 connect("127.0.0.1",11211) or die("connect failed");

$key = $_GET['key'];
getResult($memcache,$key);

function getResult($memcache,$key) {
    if($result = $memcache->get($key)) {
        $s = "get result form memcache: ".$key."/".$result."\n";
        file_put_contents("/var/www/html/log1.txt",$s,FILE_APPEND);
        echo $s;
    }else {
        $tmp_key = "_temp_".$key;
        if($memcache -> add($tmp_key, "temp key",0,60)) {
            sleep(5);
            $result = mt_rand(1,100);
            $memcache -> set($key,$result,0,3600);
            $s = "get result not from memcache: ".$key."/".$result."\n";
            file_put_contents("/var/www/html/log2.txt",$s,FILE_APPEND);
            echo $s;
            $memcache -> delete($tmp_key);
        }else {
            sleep(3);
            file_put_contents("/var/www/html/log3.txt","sleep...\n",FILE_APPEND);
            getResult($memcache, $key);
       
        }
    }
}
?>

此时我们再可以观察log2.txt中的日志,发现只有一条的,即只有一条是从数据库中查询的数据。

但如果sleep的线程如果太多的话,也可能造成进程阻塞,

另一种方法是,将数据设置在memcache的时间为永久性的,在数据中设置一个过期时间,每次获取数据时判断是否过期,若过期则加锁重新查询数据库。加锁失败则返回旧数据,但是这样做的前提是要确保memcache有这个数据,若没有则还是会返回空。所以事先需要先将用户可以查询到的数据都放到内存中,但遇到内存突然重启就会出现返回空数据的情况

 connect("127.0.0.1",11211) or die("connect failed");

$key = $_GET['key'];
print_r(getResult($memcache,$key));

function getResult($memcache,$key) {
    $result = array();
    if($result = $memcache->get($key)) {
        if($result["expired"] > strtotime(date("Y-m-d H:i:s"))) {
            //memcache中有数据,且未过期,返回原数据
            return $result;
        }else {
            return lockData($result,$memcache,$key);
        }
    }else {
        //memcache中无数据
        return lockData($result,$memcache,$key);
    }
}

//加锁,查询数据库
function lockData($result,$memcache,$key) {
    $tmp_key = "_temp_".$key;
    if($memcache -> add($tmp_key, "temp key",0,60)) {
        sleep(5);
        $result["data"] = mt_rand(1,100);
        $result["expired"] = strtotime(date("Y-m-d H:i:s")." +1 hour");
        $memcache -> set($key,$result,0,0);
        $memcache -> delete($tmp_key);
    }else {
        //若memcache中没有数据则会返回空,若有值则是返回旧数据
    }
    return $result;
}

?>

 

比较好的方式是使用gearmand来处理并发,gearmand可以确保相同的请求只执行一次,gearmand的介绍可以参考这里

1)  gearmand安装

#yum install libdrizzle libdrizzle-devel

#rpm -ivh http://dl.iuscommunity.org/pub/ius/stable/Redhat/6/x86_64/epel-release-6-5.noarch.rpm

#yum install gearmand

2)  安装php的gearmand扩展

首先要先确定有phpize,如果没有可以使用:

#yum install php-devel

#wget http://pecl.php.net/get/gearman-1.1.0.tgz

#tar zxf gearman-1.1.0.tgz

#yum install libgrearman-devel.x86_64

#yum install re2c

#cd gearman-1.1.0

#/usr/bin/phpize

#./configure

#make && make install

修改php.ini文件,加入

extension=gearman.so

3)  启动gearman

#/etc/init.d/gearman start

#service httpd restart

代码示例:

服务端gearmanWorker.php:

addServer("127.0.0.1", 4730);
  $worker->addFunction("querydb","querydb");
  while ($worker->work());


function querydb($job) {
    $key = $job -> workload();
    sleep(5);
    $result = mt_rand(1,100);
    $s = "get result not form memcache: ".$key."/".$result."\n";
    file_put_contents("/var/www/html/log2.txt",$s,FILE_APPEND);
    return $result;
}

?>

客户端complicateTest.php:

 connect("127.0.0.1",11211) or die("connect failed");

$client= new GearmanClient();
$client->addServer("127.0.0.1", 4730);

$key = $_GET['key'];
echo getResult($memcache,$key,$client);

function getResult($memcache,$key,$client) {
    if($result = $memcache->get($key)) {
       //从缓存中取
       $s = "get result form memcache: ".$key."/".$result."\n";
       file_put_contents("/var/www/html/log1.txt",$s,FILE_APPEND);
    }else {
        //memcache中无数据
        $result = $client -> doNormal("querydb",$key,$key);
        $memcache -> set($key, $result,0,3600);
    }
    return $result;
}

?>

首先启动服务端

#nohup /usr/bin/php gearmanWorker.php > log.txt 2>&1 &

使用ab命令测试:

ab -c 200 -n 10000 http://192.168.1.78/complicateTest3.php?key=sc842da

可以发现log2.txt中也只有一条记录,即只有一次从数据库中取,其他的都是从缓存中获取。
 

maven安装与配置

1. 首先下载maven,下载地址:http://maven.apache.org/download.html
放置计算机任意位置,比如我放在:D:\apache-maven-3.0.4-bin\apache-maven-3.0.4

2. 配置环境变量,在path中添加,maven的bin目录,即:D:\apache-maven-3.0.4-bin\apache-maven-3.0.4\bin(注:需先在环境变量中配置JAVA_HOME)

3. maven常用命令:

进入项目根目录下:

1)项目打包:mvn jar:jar
2)导出lib包文件:
mvn dependency:copy-dependencies -DoutputDirectory=lib -DincludeScope=compile
(注:需进入项目的根目录操作)

eclipse配置maven

window -> preferences

1

User Settings为maven的配置文件,我的为:

D:\apache-maven-3.0.4-bin\apache-maven-3.0.4\conf\settings.xml

Local Repository为maven类库下载的目录,可以在settings.xm中配置,我配置的是:

<localRepository>E:\workplace\javaeclipse\pub\m2Repositories</localRepository>

4. 新建maven项目,可查看这里

关于新建maven时出现以下错误,可以查看这里

1

清除mysql数据同步文件

1. 按文件:删除mysqld-bin.000123之前的日志,不包含mysqld-bin.000123

MYSQL>purge binary logs to ‘mysqld-bin.000123’;

Query OK, 0 rows affected (0.16 sec)

2. 按时间:删除2012-12-20 00:00:00 之前的日志

MYSQL>purge binary logs before ‘2012-12-20 00:00:00’;

3. 按时间:请理三天之前的日志

MYSQL> purge master logs before date_sub(now(), interval 3 day);

自动清理日志 :

4. 修改my.cnf文件配置bin-log过期时间

[mysqld]

expire-logs-days=3

表示删除3天之前的数据

 

 

使用凯撒算法进行数据通信加密

凯撒加密(Caesar cipher)是一种简单的消息编码方式:它根据字母表将消息中的每个字母移动常量位k。举个例子如果k等于3,则在编码后的消息中,每个字母都会向前移动3位:a会被替换为d;b会被替换成e;依此类推。字母表末尾将回卷到字母表开头。于是,w会被替换为z,x会被替换为a。

需求

1ZNNVKOC5UF9KPYKH~$LZ$Y

 

对于使用rsa算法加密解密数据,可参考《使用rsa算法进行数据通信加密》

客户端算法:




 

服务端算法:


%-";
    $new_str = "";
    $i = 0;
    $j = 0;
    for($i = 0; $i < strlen($str); $i++){
        $c = substr($str, $i, 1);
        if($j = strpos($en_str, $c)) {
            $j = $j + $step;
            $j = $j > strlen($en_str)-1 ? $j - strlen($en_str) : $j;
            $c = substr($en_str, $j, 1);
        }
        $new_str = $new_str.$c;
    }
    return $new_str;
}
/**
 * 解密
 * @param string $str 解密字符串
 * @param int $step 位移量
 */
function caesar_decrypt($str, $step)
{
    $de_str = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/<>%-";
    $new_str = "";
    $i = 0;
    $j = 0;
    for($i = 0; $i < strlen($str); $i++){
        $c = substr($str, $i, 1);
        if($j = strpos($de_str, $c)) {
            $j = $j - $step;
            $j = $j < 0 ? $j + strlen($de_str) : $j;
            $c = substr($de_str, $j, 1);
        }
        $new_str = $new_str.$c;
    }
    return $new_str;
}

$str = "hello world;";
$str = caesar_encrypt($str, 10);
echo "encrypt: ".$str."
"; $str = caesar_decrypt($str, 10); echo "decrypt: ".$str."
"; ?>

 
以上的加密算法只能加密解密中文字符,若需要对中文加密,则需要先将中文转换字母或数字,比如可使用urlencode()方法转换,在php中直接用urlencode()方法即可,但要注意此中文一定要是UTF-8格式的,在JS中有点复杂,具体实现如下:
此JS代码参考地址
 




 

常用排序(冒泡,选择,插入,快速)

算法:

1. 冒泡排序

说明:外层循环以第1个元素为起点i,内层循环以第i+1为起点j,比较i和j对应的元素大小,只要有j元素小于i元素,则两者替换

代码示例:

public static int[] sort(int[] arr) {
        int temp = 0;
        for(int i=0;i<arr.length-1;i++) {
            for(int j=i+1;j<arr.length;j++) {
                if(arr[j] < arr[i]) {
                   temp = arr[i];
                    arr[i] = arr[j];
                   arr[j] = temp;
                }
            }
        }
        return arr;
}

 

2.选择排序:

说明:类似于冒泡排序,外层循环以第1个元素为起点i,内层循环以第i+1为起点j,先找出内层元素中最小的元素,两者替换(优于冒泡排序)

代码示例:

public static int[] sort(int[] arr) {
        int temp = 0;
        for(int i=0;i<arr.length-1;i++) {
            int lowIndex = i;
            for(int j=i+1;j<arr.length;j++) {
                if(arr[j] < arr[lowIndex]) {
                   lowIndex = j;
                }
            }

            temp = arr[i];
            arr[i] = arr[lowIndex];
            arr[lowIndex] = temp;
        }
        return arr;
}

 

3.插入排序

图解:

1

2

代码示例:

public static int[] sort(int[] arr) {
    int temp = 0;
    for(int i=1;i<arr.length;i++) {
        for(int j=0;j<i;j++) {
        if(arr[i] < arr[j]) {
           temp = arr[i];
           arr[i] = arr[j];
           arr[j] = temp;
        }
        }
    }
    return arr;
}

 

4.快速排序:

图解:

3

代码示例:

    public static int getPoint(int[] arr, int low, int high) {
        int point = low;
        int contrast = arr[point];
        for (int i = low + 1; i <= high; i++) {
            if (arr[i] < contrast) {
                point++;
                swap(arr, point, i);
            }
        }
        swap(arr, low, point);
        return point;
    }

    public static int[] quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int point = getPoint(arr, low, high);
            quickSort(arr, low, point - 1);
            quickSort(arr, point + 1, high);

        }

        return arr;
    }

 

 

linux -计划任务

在linux下设置计划任务

1. 命令使用

-u 指定一个用户

-l 列出某个用户的任务计划

-r 删除某个用户的任务

-e 编辑某个用户的任务

比如使用:

#crontab –u www –e

 表示设置www组下的计划任务,如果使用crontab –e 则表示设置root下的

 2. 语法

分     小时   日    月    星期    命令

0-59  0-23  1-31  1-12    0-6     command

从左到右依次为:

1)每个小时的第几分钟执行该任务

2)每天的第几个小时执行该任务

3)每月的第几天执行该任务

4)每年的第几个月执行该任务

5)每周的第几天执行该任务

6)指定要执行的程序

(注:星期中0表示星期日,每一行表示一个计划任务)

 特殊符号:

 “*” 代表取值范围内的所有数字

 “/” 代表”每”

 “-” 代表从某个数字到某个数字

 “,” 分开几个离散的数字

 3. 举例说明:

若我想添加www组下计划任务

#crontab –u www –e

   例1:

*/5 1-6 * * * /usr/local/php/bin/php /home/wwwroot/datacopy/catchdata.php

这个表示每天的1-6点,每隔5分钟执行catchdata.php脚本。

这里要注意的是,这个是通过计划任务执行的,在catchdata.php中引用某文件的路径要写成绝对路径,因为在计划任务中是不识别环境变量的,而且catchdata所在用户和用户组都要为www的,其要有执行权限。

   例2:

1 5 */3 * * /data/sh/regularly_clean_up_data.sh > /dev/null 2>&1

每隔3天的5点1分执行regularly_clean_up_data.sh这个sh脚本,将执行后的输出结果丢弃,当然你也可是将输出的结果放在一个文件中,比如使用:

> /data/log/regularly_clean_up_data.log

修改完成后需重启计划任务

#service crond restart

 4. 关于计划任务

cron服务每分钟不仅要读一次/var/spool/cron内的所有文件,还需要读一次/etc/crontab,因此我们配置这两个文件也能运用cron。

1)使用crontab 命令实质是修改/var/spool/cron下对应用户的cron文件,

可以使用vi直接编辑此cron文件

2)另外也可以修改/etc/crontab文件修改计划任务,其文件格式如下:

1

使用举例:

47 * * * * root /usr/sbin/ntpdate -b -s time.windows.com > /dev/null 2>&1

每47分钟同步一次时间

Linux之改变文件属性及权限

在linux中对于文件属性及权限的修改,用到的命令有如下3个:

1)chgrp: 改变文件所属用户组。
2)chown: 改变文件所有者。
3)chmod: 改变文件权限。
1. chgrp用法:
   chgrp [-R] dirname/filename
   -R: 进行递归的持续更改,即连同子目录下的所有文件目录。
   例:
1
2.chown用法:
  chown [-R] 账号名称 文件或目录
  chown [-R] 账号名称:组名 文件或目录
  -R: 进行递归的持续更改,即连同子目录下的所有文件目录。
  例1:
2
 例2:
3
3. chmod用法:
   chmod [-R] xyz 文件或目录
   xyz: 为数字类型的权限属性,为rwx属性数字的相加。
   注:linux的基本权限有9个,r:4   w:2   x:1
   如:-rwxrwx—
       owner = rwx = 4+2+1 = 7
       group = rwx = 4+2+1 = 7
       others = — = 0+0+0 = 0
   1) 使用数字:
 
例1:
4
 2)使用符号: 

chmod

u

g

o

a

+(加入)

– (除去)

=(设置)

r

w

x

文件或目录

      注:u,g,o 代表3种身份,a代表all
例1:
5
例2:
6
例3:
7

Java数据克隆

为何要使用克隆:
假设有一个对象Car, 他有一个属性name, 当我们需要造一个Car,我们可以使用
Car benchi = new Car(“奔驰”),需要两辆,我们使用Car benchi2 = benchi;
但是当改变对象benchi的name属性,benchi2也同样会改变,但这并不是我们所希望的,所以我们可以使用java的clone方法来实现。
Java上要实现数据的深度克隆一般要实现Cloneable接口。
如下例子:

1)例一:

package test;

public class Car implements Cloneable {

    private String name = "";

    public Car(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object clone() {
        Car o = null; 
        try { 
            o = (Car) super.clone(); 
        } catch(CloneNotSupportedException e) { 
            e.printStackTrace(); 
        } 
        return o; 
    }

}

 

Car benchi1 = new Car(“奔驰”);

克隆一个Car, Car benchi2 = benchi1.clone();

改变benchi2的属性,benchi1的属性不会变。

2)例二:

public class Car implements Cloneable {

    private String name = "";
    private Wheel wheel = new Wheel();

    public Car(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object clone() { 
        Car o = null; 
        try { 
            o = (Car) super.clone(); 
        } catch(CloneNotSupportedException e) { 
            e.printStackTrace(); 
        } 
        return o; 
    }

    public Wheel getWheel() {
        return wheel;
    }

    public void setWheel(Wheel wheel) {
        this.wheel = wheel;
    }

}

Car还有一个属性Wheel(轮子), 如果我们对Car实现深度克隆的话,那么Wheel也必须实现Cloneable接口。

public class Wheel implements Cloneable {

    private int diamater = 30;

    public Wheel() {}

    public Wheel(int diamater) {
        this.setDiamater(diamater);
    }

    public Object clone() { 
        Wheel o = null; 
        try { 
            o = (Wheel) super.clone(); 
        } catch(CloneNotSupportedException e) { 
            e.printStackTrace(); 
        } 
        return o; 
    }

    public int getDiamater() {
        return diamater;
    }

    public void setDiamater(int diamater) {
        this.diamater = diamater;
    }
}

Car benchi1 = new Car();

克隆一个Car, Car benchi2 = benchi1.clone();

benchi2.setWheel((Wheel)(benchi1.getWheel().clone()));

同样,如果再Car中有对象(如:Map,List等)时,内置对象也要实现Cloneable接口。

 

腾讯微博应用接入

公司最近开发了一款应用,需要接入到腾讯微博,下面讲讲如何将自己的应用接入到腾讯微博频道

1. 首先要申请一个腾讯账号,成为一个微博应用开发者,开发者申请地址:

http://dev.t.qq.com/developer/

注:对应应用ui的要求:宽度:760,高度700px—1200px,详细可参考:

http://wiki.open.t.qq.com/index.php/%E8%85%BE%E8%AE%AF%E5%BE%AE%E5%8D%9A%E5%BA%94%E7%94%A8%E6%8E%A5%E5%85%A5UI%E8%A7%84%E8%8C%83

2. 接着我们需要创建一个自己的应用, 这里我选的是站内应用。

为什么呢,站内应用最大的好处是:你可以在腾讯微博应用列表中搜索到这个应用,若使用的用户多,可获取腾讯微博应用的推荐

一般如果我们只想将自己的网站接入到腾讯微博,例如:我们网页上有一个很好的效果,想让用户分享到他的腾讯微博,这时我们可选择网页应用。

1

3. 接着按照要求填写你的应用信息,填写完毕后,你会获得你的App Key 和 App Secret

这里值得特别注意的是,你的应用实际地址一定要写全了,因为腾讯会通过他的地址最终回调你的实际地址,将用户的某些信息传给你的后台。如下地址,我们若要获取用户的某些信息,也必须是在main这个control层获取

2

4. 腾讯微博应用的接入,必须调用它的api, 在其官网也给出了SDK,供应用的接入使用,其SDK使用的是OAuth2.0鉴权,因为我申请的是站内应用,而且站内应用有一个特点:无需授权,也就是说,如果你的应用为站内应用,当用户访问你的应用的时候,腾讯微博就会默认将用户的某些信息传给你的后台,比如openid和openkey,

这就是调用腾讯微博api的必要两个参数(我们只需要把传过来的值保存在session中,这样需要调用其api的时候直接从session中取值就可以了),所以我使用的是OpenId&Openkey协议来实现调用api功能(当然你也可以使用它的SDK)。

5. 调用腾讯的api发微薄,参考其官方文档,调用这个接口需传入通用参数和私有参数,主要讲讲通用参数,对于通用参数,主要是获取签名值处理较为繁琐

3

6. 根据其官方给出的说明,我对发送一条微博并发送一张图片的调用做了封装,这里我采取的是POST访问,当然你也可以使用GET方式,
具体代码如下:

send_weibo_add_pic_url($content, $pic_url);
        if(stristr($curlRet, "ok")) {
            echo '1';
        } else {
            echo '0';
        }
    }
    
    private function send_weibo_add_pic_url($content, $pic_url) {
        $postUrl = "http://open.t.qq.com/api/t/add_pic_url";
        $postData = array();
        $postData['format'] = "xml";
        $postData['content'] = $content; //分享内容
        $postData['clientip'] = "";
        $postData['pic_url'] = $pic_url;
        $postData['longitude'] = "";
        $postData['latitude'] = "";
        $postData['syncflag'] = "0";
        $postData['compatibleflag'] = "0";
        $postData['appid'] = Config::$client_id;  //应用app key
        $postData['openid'] = $_SESSION['t_openid'];
        $postData['openkey'] = $_SESSION['t_openkey'];
        $postData['reqtime'] = date("Y-m-d H:i:s");
        $postData['wbversion'] = "1";
        $postData['sig'] = $this->genSig("/t/add_pic_url", $postData);
        $postData = $this->formatPostData($postData);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_URL, $postUrl);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        $curlRet = curl_exec($ch);
        curl_close($ch);
        return $curlRet;
    }
    
    /**
     * 获取签名
     * @param String $Posturl
     * @param array $postData
     */
    function genSig($Posturl, $postData) {
        $sig = "POST";
        $sig .= "&". urlencode($Posturl);
        $sig .= "&". urlencode($this->sortKey($postData));
        $appkey = Config::$client_secret . "&"; //应用 app Secret
        return base64_encode(hash_hmac("ripemd160", $sig, $appkey));
    }
    
    /**
     * key值排序
     * @param array $postData
     */
    function sortKey($postData) {
        $str = "";
        $keys = array_keys($postData);
        sort($keys);
        foreach ($keys as $k) {
            $s = $k . "=" . $postData[$k];
            $str .= "&" . $s;
        }
        $str = substr($str, 1);
        return $str;
    }
    
    /**
     * 格式化url
     * @param array $postData
     */
    function formatPostData($postData) {
        $o = "";
        foreach ($postData as $k=>$v)
        {
            $o .= "$k=".urlencode($v)."&";
        }
        $postData = substr($o, 0, -1);
        return $postData;
    }
    
}


 

Java中socket使用

1. 什么是socket

所谓socket通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。

以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过 SocketImpl类及其子类完成的。

重要的Socket API:

java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。

.Accept方法用于产生”阻塞”,直到接受到一个连接,并且返回一个客户端的Socket对象实例。”阻塞”是一个术语,它使程序运行暂时”停留”在这个地方,直到一个会话产生,然后程序继续;通常”阻塞”是由循环产生的。

.getInputStream方法获得网络连接输入,同时返回一个IutputStream对象实例。

.getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。

注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。

socket主要用于数据的传输,例如我们可以在服务器上利用socket搭建一个服务端,监听某个端口,这样外部就可以通过连接这个端口访问我们服务器上的资源,现在socket用于比较广泛的是聊天工具

2. socket的使用

下面是一个我写的用于实现一个通过接口获取数据的例子

1)  服务端

说明:监听某个端口,当有客服端连接的时候,启动一个线程用于处理连接客服端的请求,实现数据传输。

数据传输:首先先读取客服端传来的信息,根据信息获取相应的数据,再将数据传输给客服端。

package com.jjt;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import org.apache.log4j.Logger;
import com.business.StatusContext;

public class StatusServer {

    private static Logger logger = Logger.getLogger(StatusServer.class);
    private static final int PORT = 5432;

    private static int clientCount = 0;

    public static void main(String[] args) {
        new StatusServer().connect();
    }

    /**
     * 启动服务
     */
    private void connect() {
        try {
            ServerSocket ss = new ServerSocket(PORT);
            logger.info("服务器启动");
            logger.info("*************************************");
            while(true) {
                try {
                    Socket socket = ss.accept();
                    System.out.println("client count: " + (++clientCount));
                    new Thread(new SocketThread(socket)).start();
                } catch (Exception e) {
                    logger.error("客户端接受出错:" + e);
                }
            }
        } catch (IOException e) {
            logger.error("服务器启动失败:" + e);
        }
    }
}

class SocketThread implements Runnable {
    private static Logger logger = Logger.getLogger(SocketThread.class);

    private Socket socket = null;
    InputStream in = null;
    InputStreamReader isr = null;
    BufferedReader br = null;
    OutputStream os = null;
    PrintWriter pw = null;

    public SocketThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            String msg = this.readMessage();
            if(msg != null) {
                StatusContext satatusContext = new StatusContext();
                String XMLData = satatusContext.obtainXMLData(msg);
                this.writeMessage(XMLData);
                this.socket.close();
                //this.closeStream();
            }
        } catch (Exception e) {
            logger.error("读写信息出错:" +e);
        }
    }

    /**
     * 从客户端读取信息
     */
    private String readMessage(){
        String message = null;
        try {
            in = socket.getInputStream();
            Request request = new Request(in);
            request.parse();
            message = request.getUri();
            if(message == null) return null;
            String[] msgArr = message.split("\\?");
            if(msgArr.length != 2) return null;
            message = msgArr[0].replace("/", "") + "#" + msgArr[1];
        } catch (Exception e) {
            logger.error("读取信息出错:"+ e);
            return null;
        }
        return message;
    }

    /**
     * 向客户端写入信息
     */
    private boolean writeMessage(String msg){
        boolean isSuccess = false;
        try {
            os = socket.getOutputStream();
            pw = new PrintWriter(os);
            pw.println(msg);
            pw.flush();
            os.flush();
            isSuccess = true;
        } catch (Exception e) {
            logger.error("写入信息出错:" + e);
        } 
        return isSuccess;
    }

    private void closeStream() {
        try {
            br.close();
            isr.close();
            in.close();
            pw.close();
            os.close();
            socket.close();
        } catch (Exception e) {
            logger.error("关闭连接出错:" + e);
        }
    }

}

 

2)  客服端

说明:首先连接服务端,先写入向服务端发送的数据,再读取服务端传来的数据信息

package com.jjt;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import org.apache.log4j.Logger;
import com.tool.FltDefine;

public class SocketClient {
    private static Logger logger = Logger.getLogger(StatusServer.class);
    private Socket socket = null;
    OutputStream os = null;
    PrintWriter pw = null;
    InputStream in = null;
    InputStreamReader isr = null;
    BufferedReader br = null;

    public static void main(String[] args) {
        try {
            new SocketClient().connectServer();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 连接服务器端
     */
    private void connectServer() throws Exception {
        String key = "aaaa";
        String parmas = "bb=cc&dd=ee";

        String message = key + "#" + parmas;
        socket = new Socket("127.0.0.1", 5432);
        if(this.writeMessage(message)) {
            System.out.println(this.readMessage());
        }
        this.closeStream();
    }

    /**
     * 向服务端写入信息
     */
    private boolean writeMessage(String msg){
        boolean isSuccess = false;
        try {
            os = socket.getOutputStream();
            pw = new PrintWriter(os);
            pw.println(msg);
            pw.flush();
            os.flush();
            isSuccess = true;
        } catch (Exception e) {
            logger.error("写入信息出现:" + e);
        } 
        return isSuccess;
    }

    /**
     * 从服务端读取信息
     */
    private String readMessage(){
        boolean isStop = false;
        String message = null;
        try {
            in = socket.getInputStream();
            isr = new InputStreamReader(in);
            br = new BufferedReader(isr);
            while(true && !isStop) {
                message = br.readLine();
                if(message != null) {
                    isStop = true;
                }
            }
        } catch (Exception e) {
            logger.error("读取信息出错:"+ e);
            return null;
        }
        return message;
    }

    private void closeStream() {
        try {
            br.close();
            isr.close();
            in.close();
            pw.close();
            os.close();
            socket.close();
        } catch (Exception e) {
            logger.error("关闭连接出错:" + e);
        }
    }

}

 

3)  附加类

说明:当我们直接在浏览器上敲上连接服务端的地址,我们需要对这个地址进行解析,因为通过浏览器传给服务器的内容与直接用程序连接是不同的,在服务端readMessage()方法中用用到这个类

package com.jjt;

import java.io.InputStream;
import java.io.IOException;

public class Request {

    private InputStream input;
    private String uri;

    public Request(InputStream input) {
        this.input = input;
    }

    public void parse() {
        // Read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j = 0; j < i; j++) {             request.append((char) buffer[j]);         }         uri = parseUri(request.toString());     }     private String parseUri(String requestString) {         int index1, index2;         index1 = requestString.indexOf(' ');         if (index1 != -1) {             index2 = requestString.indexOf(' ', index1 + 1);             if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }

    public String getUri() {
        return uri;
    }

}