Mysql 常用命令


连接Mysql

 
格式: mysql -h主机地址 -u用户名 -p用户密码
 
1、连接到本机上的MYSQL。
首先打开命令行窗口,然后进入目录mysql/bin,再键入命令mysql -u root -p,回车后提示你输密码.注意用户名前可以有空格也可以没有空格,但是密码前必须没有空格,否则让你重新输入密码。

如果刚安装好MYSQL,超级用户root是没有密码的,故直接回车即可进入到MYSQL中了,MYSQL的提示符是: mysql>

2、连接到远程主机上的MYSQL。假设远程主机的IP为:110.110.110.110,用户名为root,密码为abcd123。则键入以下命令:
    mysql -h110.110.110.110 -u root -p 123;(注:u与root之间可以不用加空格,其它也一样)

 
———————————————————————————————————————————————————————————

修改密码

格式:mysqladmin -u用户名 -p旧密码 password 新密码
 
1、给root加个密码ab12。
首先在DOS下进入目录mysql/bin,然后键入以下命令
    mysqladmin -u root -password ab12
注:因为开始时root没有密码,所以-p旧密码一项就可以省略了。

2、再将root的密码改为djg345。
    mysqladmin -u root -p ab12 password djg345

 
———————————————————————————————————————————————————————————

增加新用户

格式:grant select on 数据库.* to 用户名@登录主机 identified by “密码”
 
1、增加一个用户test1密码为abc,让他可以在任何主机上登录,并对所有数据库有查询、插入、修改、删除的权限。首先用root用户连入MYSQL,然后键入以下命令:
    grant select,insert,update,delete on *.* to [email=test1@”%]test1@”%[/email]” Identified by “abc”;

但增加的用户是十分危险的,你想如某个人知道test1的密码,那么他就可以在internet上的任何一台电脑上登录你的mysql数据库并对你的数据可以为所欲为了,解决办法见2。

2、增加一个用户test2密码为abc,让他只可以在localhost上登录,并可以对数据库mydb进行查询、插入、修改、删除的操作(localhost指本地主机,即MYSQL数据库所在的那台主机),这样用户即使用知道test2的密码,他也无法从internet上直接访问数据库,只能通过MYSQL主机上的web页来访问了。
    grant select,insert,update,delete on mydb.* to [email=test2@localhost]test2@localhost[/email] identified by “abc”;

如果你不想test2有密码,可以再打一个命令将密码消掉。
    grant select,insert,update,delete on mydb.* to [email=test2@localhost]test2@localhost[/email] identified by “”;

 
———————————————————————————————————————————————————————————

创建数据库

 
注意:创建数据库之前要先连接Mysql服务器

命令:create database <数据库名>

例1:建立一个名为xhkdb的数据库
   mysql> create database xhkdb;

例2:创建数据库并分配用户

①CREATE DATABASE 数据库名;

②GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER ON 数据库名.* TO 数据库名@localhost IDENTIFIED BY ‘密码’;

③SET PASSWORD FOR ‘数据库名’@’localhost’ = OLD_PASSWORD(‘密码’);

依次执行3个命令完成数据库创建。注意:中文 “密码”和“数据库”是户自己需要设置的。

 
 
———————————————————————————————————————————————————————————

显示数据库

命令:show databases (注意:最后有个s)
mysql> show databases;

注意:为了不再显示的时候乱码,要修改数据库默认编码。以下以GBK编码页面为例进行说明:

1、修改MYSQL的配置文件:my.ini里面修改default-character-set=gbk
2、代码运行时修改:
   ①Java代码:jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=gbk
   ②PHP代码:header(“Content-Type:text/html;charset=gb2312”);
   ③C语言代码:int mysql_set_character_set( MYSQL * mysql, char * csname);
该函数用于为当前连接设置默认的字符集。字符串csname指定了1个有效的字符集名称。连接校对成为字符集的默认校对。该函数的工作方式与SET NAMES语句类似,但它还能设置mysql- > charset的值,从而影响了由mysql_real_escape_string() 设置的字符集。

———————————————————————————————————————————————————————————
 

删除数据库

命令:drop database <数据库名>
例如:删除名为 xhkdb的数据库
mysql> drop database xhkdb;

例子1:删除一个已经确定存在的数据库
   mysql> drop database drop_database;
   Query OK, 0 rows affected (0.00 sec)

例子2:删除一个不确定存在的数据库
   mysql> drop database drop_database;
   ERROR 1008 (HY000): Can’t drop database ‘drop_database’; database doesn’t exist
      //发生错误,不能删除’drop_database’数据库,该数据库不存在。
   mysql> drop database if exists drop_database;
   Query OK, 0 rows affected, 1 warning (0.00 sec)//产生一个警告说明此数据库不存在
   mysql> create database drop_database;
   Query OK, 1 row affected (0.00 sec)
   mysql> drop database if exists drop_database;//if exists 判断数据库是否存在,不存在也不产生错误
   Query OK, 0 rows affected (0.00 sec)

 
———————————————————————————————————————————————————————————

连接数据库

命令: use <数据库名>

例如:如果xhkdb数据库存在,尝试存取它:
   mysql> use xhkdb;
屏幕提示:Database changed

use 语句可以通告MySQL把db_name数据库作为默认(当前)数据库使用,用于后续语句。该数据库保持为默认数据库,直到语段的结尾,或者直到发布一个不同的USE语句:
   mysql> USE db1;
   mysql> SELECT COUNT(*) FROM mytable;   # selects from db1.mytable
   mysql> USE db2;
   mysql> SELECT COUNT(*) FROM mytable;   # selects from db2.mytable

使用USE语句为一个特定的当前的数据库做标记,不会阻碍您访问其它数据库中的表。下面的例子可以从db1数据库访问作者表,并从db2数据库访问编辑表:
   mysql> USE db1;
   mysql> SELECT author_name,editor_name FROM author,db2.editor
       ->        WHERE author.editor_id = db2.editor.editor_id;

USE语句被设立出来,用于与Sybase相兼容。

有些网友问到,连接以后怎么退出。其实,不用退出来,use 数据库后,使用show databases就能查询所有数据库,如果想跳到其他数据库,用
   use 其他数据库名字
就可以了。

 
———————————————————————————————————————————————————————————

当前选择的数据库

命令:mysql> select database();
 
MySQL中SELECT命令类似于其他编程语言里的print或者write,你可以用它来显示一个字符串、数字、数学表达式的结果等等。如何使用MySQL中SELECT命令的特殊功能?

1.显示MYSQL的版本
mysql> select version(); 
+———————–+ 
| version()             | 
+———————–+ 
| 6.0.4-alpha-community | 
+———————–+ 
1 row in set (0.02 sec) 

2. 显示当前时间
mysql> select now(); 
+———————+ 
| now()               | 
+———————+ 
| 2009-09-15 22:35:32 | 
+———————+ 
1 row in set (0.04 sec) 

3. 显示年月日
SELECT DAYOFMONTH(CURRENT_DATE); 
+————————–+ 
| DAYOFMONTH(CURRENT_DATE) | 
+————————–+ 
|                       15 | 
+————————–+ 
1 row in set (0.01 sec) 
  
SELECT MONTH(CURRENT_DATE); 
+———————+ 
| MONTH(CURRENT_DATE) | 
+———————+ 
|                   9 | 
+———————+ 
1 row in set (0.00 sec) 
  
SELECT YEAR(CURRENT_DATE); 
+——————–+ 
| YEAR(CURRENT_DATE) | 
+——————–+ 
|               2009 | 
+——————–+ 
1 row in set (0.00 sec) 

4. 显示字符串
mysql> SELECT “welecome to my blog!”; 
+———————-+ 
| welecome to my blog! | 
+———————-+ 
| welecome to my blog! | 
+———————-+ 
1 row in set (0.00 sec) 

5. 当计算器用
select ((4 * 4) / 10 ) + 25; 
+———————-+ 
| ((4 * 4) / 10 ) + 25 | 
+———————-+ 
|                26.60 | 
+———————-+ 
1 row in set (0.00 sec) 

6. 串接字符串
select CONCAT(f_name, ” “, l_name) 
AS Name 
from employee_data 
where title = ‘Marketing Executive’; 
+—————+ 
| Name          | 
+—————+ 
| Monica Sehgal | 
| Hal Simlai    | 
| Joseph Irvine | 
+—————+ 
3 rows in set (0.00 sec) 
注意:这里用到CONCAT()函数,用来把字符串串接起来。另外,我们还用到以前学到的AS给结果列’CONCAT(f_name, ” “, l_name)’起了个假名。

 
———————————————————————————————————————————————————————————

创建数据表

 

命令:create table <表名> ( <字段名1> <类型1> [,..<字段名n> <类型n>]);

例如,建立一个名为MyClass的表,

字段名 数字类型 数据宽度 是否为空 是否主键 自动增加 默认值
id int 4 primary key auto_increment  
name char 20      
sex int 4     0
degree double 16      
 
 
 
 
 
 
 
mysql> create table MyClass(
> id int(4) not null primary key auto_increment,
> name char(20) not null,
> sex int(4) not null default ‘0’,
> degree double(16,2));
———————————————————————————————————————————————————————————
 

获取表结构

命令: desc 表名,或者show columns from 表名

mysql> desc MyClass;
mysql> show columns from MyClass;

使用MySQL数据库desc 表名时,我们看到Key那一栏,可能会有4种值,即’ ‘,’PRI’,’UNI’,’MUL’。
1. 如果Key是空的, 那么该列值的可以重复, 表示该列没有索引, 或者是一个非唯一的复合索引的非前导列;
2. 如果Key是PRI,  那么该列是主键的组成部分;
3. 如果Key是UNI,  那么该列是一个唯一值索引的第一列(前导列),并别不能含有空值(NULL);
4. 如果Key是MUL,  那么该列的值可以重复, 该列是一个非唯一索引的前导列(第一列)或者是一个唯一性索引的组成部分但是可以含有空值NULL。

如果对于一个列的定义,同时满足上述4种情况的多种,比如一个列既是PRI,又是UNI,那么”desc 表名”的时候,显示的Key值按照优先级来显示 PRI->UNI->MUL。那么此时,显示PRI。

一个唯一性索引列可以显示为PRI,并且该列不能含有空值,同时该表没有主键。
一个唯一性索引列可以显示为MUL, 如果多列构成了一个唯一性复合索引,因为虽然索引的多列组合是唯一的,比如ID+NAME是唯一的,但是没一个单独的列依然可以有重复的值,只要ID+NAME是唯一的即可。

———————————————————————————————————————————————————————————

删除数据表

 

命令:drop table <表名>

例如:删除表名为 MyClass 的表
   mysql> drop table MyClass;

DROP TABLE用于取消一个或多个表。您必须有每个表的DROP权限。所有的表数据和表定义会被取消,所以使用本语句要小心!

注意:对于一个带分区的表,DROP TABLE会永久性地取消表定义,取消各分区,并取消储存在这些分区中的所有数据。DROP TABLE还会取消与被取消的表有关联的分区定义(.par)文件。

对与不存在的表,使用IF EXISTS用于防止错误发生。当使用IF EXISTS时,对于每个不存在的表,会生成一个NOTE。

RESTRICT和CASCADE可以使分区更容易。目前,RESTRICT和CASCADE不起作用。

———————————————————————————————————————————————————————————

表插入数据

命令:insert into <表名> [( <字段名1>[,..<字段名n > ])] values ( 值1 )[, ( 值n )]

例如:往表 MyClass中插入二条记录, 这二条记录表示:编号为1的名为Tom的成绩为96.45, 编号为2 的名为Joan 的成绩为82.99, 编号为3 的名为Wang 的成绩为96.5。
   mysql> insert into MyClass values(1,’Tom’,96.45),(2,’Joan’,82.99), (2,’Wang’, 96.59);

注意:insert into每次只能向表中插入一条记录。

———————————————————————————————————————————————————————————

查询表中的数据

 

1)、查询所有行
命令: select <字段1,字段2,…> from < 表名 > where < 表达式 >
例如:查看表 MyClass 中所有数据
   mysql> select * from MyClass;

2)、查询前几行数据
例如:查看表 MyClass 中前2行数据
mysql> select * from MyClass order by id limit 0,2;

select一般配合where使用,以查询更精确更复杂的数据。

 
 
———————————————————————————————————————————————————————————

删除表中数据

命令:delete from 表名 where 表达式

例如:删除表 MyClass中编号为1 的记录
mysql> delete from MyClass where id=1;

 
下面是一个删除数据前后表的对比

FirstName LastName Age
Peter Griffin 35
Glenn Quagmire 33
 
 
 
 
下面以PHP代码为例删除 “Persons” 表中所有 LastName=’Griffin’ 的记录:

<?php 
   $con = mysql_connect("localhost","peter","abc123"); 
   if (!$con) 
   {
      die('Could not connect: ' . mysql_error()); 
   } 
   mysql_select_db("my_db", $con); 
   mysql_query("DELETE FROM Persons WHERE LastName='Griffin'"); mysql_close($con); 
?>

在这次删除之后,表是这样的:

FirstName LastName Age
Glenn Quagmire 33
 
 
 
———————————————————————————————————————————————————————————

修改表中数据

 

语法:update 表名 set 字段=新值,… where 条件
   mysql> update MyClass set name=’Mary’ where id=1;

例子1:单表的MySQL UPDATE语句:
   UPDATE [LOW_PRIORITY] [IGNORE] tbl_name SET col_name1=expr1 [, col_name2=expr2 …] [WHERE where_definition] [ORDER BY …] [LIMIT row_count]

例子2:多表的UPDATE语句:
UPDATE [LOW_PRIORITY] [IGNORE] table_references SET col_name1=expr1 [, col_name2=expr2 …] [WHERE where_definition]

UPDATE语法可以用新值更新原有表行中的各列。SET子句指示要修改哪些列和要给予哪些值。WHERE子句指定应更新哪些行。如果没有WHERE子句,则更新所有的行。如果指定了ORDER BY子句,则按照被指定的顺序对行进行更新。LIMIT子句用于给定一个限值,限制可以被更新的行的数目。

———————————————————————————————————————————————————————————

增加字段

 
命令:alter table 表名 add字段 类型 其他;
例如:在表MyClass中添加了一个字段passtest,类型为int(4),默认值为0
   mysql> alter table MyClass add passtest int(4) default ‘0’

加索引
   mysql> alter table 表名 add index 索引名 (字段名1[,字段名2 …]);
例子: mysql> alter table employee add index emp_name (name);

加主关键字的索引
  mysql> alter table 表名 add primary key (字段名);
例子: mysql> alter table employee add primary key(id);

加唯一限制条件的索引
   mysql> alter table 表名 add unique 索引名 (字段名);
例子: mysql> alter table employee add unique emp_name2(cardnumber);

删除某个索引
   mysql> alter table 表名 drop index 索引名;
例子: mysql>alter table employee drop index emp_name;

增加字段:
mysql> ALTER TABLE table_name ADD field_name field_type;

修改原字段名称及类型:
mysql> ALTER TABLE table_name CHANGE old_field_name new_field_name field_type;

删除字段:
MySQL ALTER TABLE table_name DROP field_name;

———————————————————————————————————————————————————————————

修改表名

 
命令:rename table 原表名 to 新表名;

例如:在表MyClass名字更改为YouClass
   mysql> rename table MyClass to YouClass;

当你执行 RENAME 时,你不能有任何锁定的表或活动的事务。你同样也必须有对原初表的 ALTER 和 DROP 权限,以及对新表的 CREATE 和 INSERT 权限。

如果在多表更名中,MySQL 遭遇到任何错误,它将对所有被更名的表进行倒退更名,将每件事物退回到最初状态。

RENAME TABLE 在 MySQL 3.23.23 中被加入。

———————————————————————————————————————————————————————————

备份数据库

 
命令在DOS的[url=file:////mysql//bin]//mysql//bin[/url]目录下执行

1.导出整个数据库
导出文件默认是存在mysql/bin目录下
    mysqldump -u 用户名 -p 数据库名 > 导出的文件名
    mysqldump -u user_name -p123456 database_name > outfile_name.sql

2.导出一个表
    mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
    mysqldump -u user_name -p database_name table_name > outfile_name.sql

3.导出一个数据库结构
    mysqldump -u user_name -p -d –add-drop-table database_name > outfile_name.sql
    -d 没有数据 –add-drop-table 在每个create语句之前增加一个drop table

4.带语言参数导出
    mysqldump -uroot -p –default-character-set=latin1 –set-charset=gbk –skip-opt database_name > outfile_name.sql

例如,将aaa库备份到文件back_aaa中:
  [root@test1 root]# cd /home/data/mysql
  [root@test1 mysql]# mysqldump -u root -p –opt aaa > back_aaa

———————————————————————————————————————————————————————————

一个建库和建表的实例1

drop database if exists school; //如果存在SCHOOL则删除
create database school; //建立库SCHOOL
use school; //打开库SCHOOL
create table teacher //建立表TEACHER
(
    id int(3) auto_increment not null primary key,
    name char(10) not null,
    address varchar(50) default ”深圳”,
    year date
); //建表结束

//以下为插入字段
insert into teacher values(””,”glchengang”,”深圳一中”,”1976-10-10”);
insert into teacher values(””,”jack”,”深圳一中”,”1975-12-23”);

注:在建表中
1、将ID设为长度为3的数字字段:int(3);并让它每个记录自动加一:auto_increment;并不能为空:not null;而且让他成为主字段primary key。

2、将NAME设为长度为10的字符字段

3、将ADDRESS设为长度50的字符字段,而且缺省值为深圳。

4、将YEAR设为日期字段。

———————————————————————————————————————————————————————————

 一个建库和建表的实例2

drop database if exists school; //如果存在SCHOOL则删除
create database school; //建立库SCHOOL
use school; //打开库SCHOOL
create table teacher //建立表TEACHER
(
    id int(3) auto_increment not null primary key,
    name char(10) not null,
    address varchar(50) default ‘深圳’,
    year date
); //建表结束

//以下为插入字段
insert into teacher values(”,’allen’,’大连一中’,’1976-10-10′);
insert into teacher values(”,’jack’,’大连二中’,’1975-12-23′);

如果你在mysql提示符键入上面的命令也可以,但不方便调试。
1、你可以将以上命令原样写入一个文本文件中,假设为school.sql,然后复制到c://下,并在DOS状态进入目录[url=file:////mysql//bin]//mysql//bin[/url],然后键入以下命令:
    mysql -uroot -p密码 < c://school.sql
如果成功,空出一行无任何显示;如有错误,会有提示。(以上命令已经调试,你只要将//的注释去掉即可使用)。

2、或者进入命令行后使用 mysql> source c://school.sql; 也可以将school.sql文件导入数据库中。
 

Ubuntu 安装mysql和简单操作

  ubuntu上安装mysql非常简单只需要几条命令就可以完成。

  1. sudo apt-get install mysql-server
 
  2. apt-get isntall mysql-client
 

  3.  sudo apt-get install libmysqlclient-dev

 
  安装过程中会提示设置密码什么的,注意设置了不要忘了安装完成之后可以使用如下命令来检查是否安装成功:
 
  sudo netstat -tap | grep mysql
 
  通过上述命令检查之后,如果看到有mysql 的socket处于 listen 状态则表示安装成功。
 
  登陆mysql数据库可以通过如下命令:
 
  mysql -u root -p 
 
  -u 表示选择登陆的用户名, -p 表示登陆的用户密码,上面命令输入之后会提示输入密码,此时输入密码就可以登录到mysql。
 
  Ubuntu 安装mysql和简单操作
 
  然后通过 show databases; 就可以查看当前的数据库。
  我们选择 mysql数据库就行下一步操作,使用use mysql 命令,显示当前数据库的表单:show tables 
  Ubuntu 安装mysql和简单操作
  
  写一个简单的程序来访问该数据库,实现 show tables 功能:
#include <mysql/mysql.h>
#include <stdio.h>
#include <stdlib.h>
int main() 
{
    MYSQL *conn;
    MYSQL_RES *res;
    MYSQL_ROW row;
    char server[] = "localhost";
    char user[] = "root";
    char password[] = "mima";
    char database[] = "mysql";
    
    conn = mysql_init(NULL);
    
    if (!mysql_real_connect(conn, server,user, password, database, 0, NULL, 0)) 
    {
        fprintf(stderr, "%s/n", mysql_error(conn));
        exit(1);
    }
    
    if (mysql_query(conn, "show tables")) 
    {
        fprintf(stderr, "%s/n", mysql_error(conn));
        exit(1);
    }
    
    res = mysql_use_result(conn);
    
    printf("MySQL Tables in mysql database:/n");
    
    while ((row = mysql_fetch_row(res)) != NULL)
    {
        printf("%s /n", row[0]);
    }
    
    mysql_free_result(res);
    mysql_close(conn);
    
    printf("finish! /n");
    return 0;
}

  编译代码的时候需要链接mysql的库,可以通过如下方式编译:

  g++ -Wall mysql_test.cpp -o mysql_test -lmsqlclient

  然后运行编译好的代码:

Ubuntu 安装mysql和简单操作

  可见结果和使用SQL语句 show tables 是一样的。

IO multiplexing 与 非阻塞网络编程

  使用I/O multipexing 的网络编程中,一般需要采用非阻塞网络编程的风格,防止服务端在处理高连接量大时候阻塞在某个文件描述符上面,比如某个socket 有大量的数据需要写,但是内核发送缓冲区已经填满,无法在一次write中将需要发送到数据发送出去,程序就会阻塞在该处,导致select/poll/epoll_wait() 此时不能处理其它到来的请求,同样read或者accept也可能出现阻塞的情况,比如当客户端发起connect,之后立刻关闭该链接,在服务端尚未调用accept之前就关闭了该连接,当后来服务端accept得以调用此时完成队列中又没有完成的三次握手的连接,accept就会导致进程睡眠(详细情况可以参见UNPv1非阻塞accept的描述)。因此I/O multiplexing 一般采用非阻塞网络编程的风格。

  对于read/wirte 操作来说,如果采用了非阻塞编程则需要为每个connection配备应用层缓冲区,read端主要防止一次来到数据太多,write主要防止出现阻塞,可以把没有发送完成的数据写入缓冲区,等到socket 可写之后继续发送。如果在新一次write请求到来的时候,应用层写缓冲区中还有之前未发送完的数据,则应该先将上次未写入内核的数据写入内核缓冲区,保证发送到顺序性。此处给一个简单的例子。

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <map>
#include <fcntl.h>
#include <errno.h>
#include <string>
#include <iostream>
#include <sys/select.h>

#define SEVER_PORT 1314
#define MAX_LINE_LEN 1024

using namespace std;

int Accept(int fd, struct sockaddr_in *addr)
{
    socklen_t addr_len = static_cast<socklen_t>( sizeof *addr);
    int connfd,flags;

    connfd = accept(fd,reinterpret_cast<struct sockaddr *>(addr),&addr_len);

    flags = fcntl(connfd,F_GETFL,0);
    fcntl(connfd,F_SETFL,flags | O_NONBLOCK);

    if(connfd < 0)
    {
        int ErrorCode = errno;
        switch(ErrorCode)
        {
            case 0:
            case EWOULDBLOCK:
            case ECONNABORTED:
            case EPROTO:
            case EINTR:
            case EMFILE:
                    errno = ErrorCode;
                    printf("Accept Error: %s/n",strerror(ErrorCode));
                break;
            default:
                break;
        }
    }
    return connfd;
}

int Read(int fd, map<int, string> &bufMap)
{
    struct iovec iov[2];
    char buf[MAX_LINE_LEN+1];
    char exbuf[65535]; // 如果一次read很多数据,则动用该缓冲区
    int nrcv;
    
    iov[0].iov_base = buf;
    iov[0].iov_len = MAX_LINE_LEN;
    
    iov[1].iov_base = exbuf;
    iov[1].iov_len = sizeof exbuf;
    
    nrcv = readv(fd, iov, 2);// 使用readv保证能将数据读取完
    
    if(nrcv > MAX_LINE_LEN)
    {
        bufMap[fd] += string(buf) + string(exbuf); // test !
        printf("extrabuf in use! /n");
    }
    else if( nrcv > 0)
    {
        bufMap[fd] += string(buf);
    }
    else
    {
        return nrcv;
    }

    return nrcv;
}

int getSocketError(int fd)
{
    int optval;
    
    socklen_t optlen = static_cast<socklen_t>(sizeof optval);
    
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
    {
        return errno;
    }
    else
    {
        return optval;
    }
}

int main()
{
    struct sockaddr_in cli_addr, server_addr;
    vector<int> client(FD_SETSIZE,-1);
    map<int ,string> bufMap;// 简易应用层缓冲区

    fd_set rset,wrset,allset;
    int listenfd, connfd, sockfd, maxfd, nready, ix,maxid, nrcv,flags,nwrt,one;
    char addr_str[INET_ADDRSTRLEN];

    int accepted = 0;

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SEVER_PORT);

    listenfd = socket(AF_INET,SOCK_STREAM,0);

    flags = fcntl(listenfd,F_GETFL,0);
    fcntl(listenfd,F_SETFL,flags | O_NONBLOCK);

    one = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&one, sizeof(one));

    if(bind(listenfd,(struct sockaddr *)&server_addr,sizeof server_addr) < 0)
    {
        printf("socket bind error: %s/n",strerror(errno));
        return 0;
    }

    listen(listenfd,10);

    FD_ZERO(&rset);
    FD_ZERO(&wrset);
    FD_ZERO(&allset);
    FD_SET(listenfd,&allset);
    maxfd = listenfd;
    maxid = -1;


    while(1)
    {
        rset = allset;
        nready = select(maxfd + 1, &rset,&wrset,NULL,NULL);


        if(nready < 0)
        {
            printf("select error: %s/n",strerror(errno));
            exit(1);
        }

        if(FD_ISSET(listenfd, &rset))
        {

            connfd = Accept(listenfd,&cli_addr);

            printf("recieve from : %s at port %d/n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port);

            for(ix = 0; ix < static_cast<int>(client.size()); ix++)
            {
                if(client[ix] < 0)
                {
                    client[ix] = connfd;
                    break;
                }
            }

            printf("client[%d] = %d/n",ix,connfd);

            if( FD_SETSIZE == ix)
            {
                printf("too many client! /n");
                exit(1);
            }

            if( connfd > maxfd)
            {
                maxfd = connfd;
            }

            FD_SET(connfd, &allset);

            accepted++;
            printf("accepted: %d/n",accepted);

            if(ix > maxid)
            {
                maxid = ix;
            }
            
            if(--nready == 0)
            {
                continue;
            }

        }

        for(ix = 0; ix <= maxid; ix++)
        {
            if((sockfd = client[ix]) < 0)
            {
                continue;
            }

            if(FD_ISSET(sockfd,&rset))
            {
            
                int left_len = bufMap[sockfd].length();
                
                if( 0 == (nrcv = Read(sockfd,bufMap)))
                {
                    client[ix] = -1;
                    printf("close! /n");
                    FD_CLR(sockfd,&allset);
                    bufMap.erase(sockfd);
                    close(sockfd);
                }
                else if ( nrcv > 0)
                {
                    printf("nrcv = %d /n",nrcv);

                    nrcv += left_len;//next time when client write to

                    //nwrt = write(sockfd,bufMap[sockfd].c_str(),200);// 模拟还有剩余
                    nwrt = write(sockfd,bufMap[sockfd].c_str(),nrcv);

                    if(nwrt < 0)
                    {
                        if( errno != EWOULDBLOCK)
                        {
                            printf("Write error: %s/n", strerror(errno));
                        }
                    }

                    printf("nwrt = %d /n",nwrt);

                    if(nwrt == nrcv) // 全部写到了内核缓冲区
                    {
                        bufMap[sockfd].clear();
                        //bufMap[sockfd].erase(0,nrcv);
                        if(FD_ISSET(sockfd,&wrset))
                        {
                            FD_CLR(sockfd,&wrset);
                        }
                    }
                    else // 还有剩余
                    {
                        printf("write left /n");
                        bufMap[sockfd].erase(0,nwrt);
                        std::cout << " after erase: "<<bufMap[sockfd] <<std::endl;
                        FD_SET(sockfd,&wrset);//开始关注写事件
                    }

                }
                else
                {
                    int err = getSocketError(sockfd);
                    
                    printf("SocketError: %s/n",strerror(err));
                }
            }

            if(FD_ISSET(sockfd,&wrset))
            {
                nrcv = bufMap[sockfd].size();
                printf("write again: nrcv left = %d /n",nrcv);
                nwrt = write(sockfd,bufMap[sockfd].c_str(),nrcv);

                if(nwrt == nrcv)
                {
                    bufMap[sockfd].clear();
                    if(FD_ISSET(sockfd,&wrset))
                    {
                        FD_CLR(sockfd,&wrset);
                    }
                    printf("Write complete! /n");
                }
                else
                {
                    bufMap[sockfd].erase(0,nwrt);
                }
            }

            if(--nready == 0)
            {
                break;
            }
        }
    }

    return 0;
}

 

APUE 3rd

以下是APUE 3rd edition 的preface,从04年的第二版到现在的第三版,APUE内容有所更新。点击下载

It’s been almost eight years since I first updated Advanced  Programming  in  the  UNIX

Environment, and already so much has changed.

• Before the second edition was published, The Open Group created a 2004 edition of the Single UNIX Specification, folding in the changes from two sets of corrigenda. In 2008, The Open Group created a new version of the Single UNIX Specification,  updating  the  base  definitions,  adding  new  interfaces,  and removing  obsolete  ones.  This  was  called  the  2008  version  of  POSIX.1, which included version 7 of the Base Specification and was published in 2009. In 2010, this was bundled with an updated curses interface and reissued as version 4 of

the Single UNIX Specification.

• Versions 10.5, 10.6, and 10.8 of the Mac OS X operating system, running on Intel processors, have been certified to be UNIX® systems by The Open Group.

• Apple  Computer  discontinued  development  of  Mac  OS  X  for  the  PowerPC platform.  From  Release  10.6  (Snow  Leopard)  onward,  new  operating  system

versions are released for the x86 platform only.

• The Solaris operating system was released in open source form to try to compete with the popularity of the open source model followed by FreeBSD, Linux, and Mac  OS  X. After  Oracle  Corporation  bought  Sun  Microsystems  in  2010,  it discontinued the development of OpenSolaris. Instead, the Solaris community formed  the  Illumos  project  to  continue  open  source  development  based  on OpenSolaris.  For more information, see http://www.illumos.org.

• In 2011, the C standard was updated, but because systems haven’t caught up yet with the changes, we still refer to the 1999 version in this text. Most notably, the platforms used in the second edition have become out-of-date. In this book, the third edition, I cover the following platforms:

1.  FreeBSD  8.0,  a  descendant  of  the  4.4BSD  release  from  the  Computer  Systems Research Group at the University of California at Berkeley, running on a 32-bit Intel Pentium processor.

2.  Linux 3.2.0 (the Ubuntu 12.04 distribution), a free UNIX-like operating system, running on a 64-bit Intel Core i5 processor.

3.  Apple Mac  OS  X,  version  10.6.8 (Darwin  10.8.0) on  a  64-bit  Intel  Core 2 Duo

processor. (Darwin  is  based  on  FreeBSD  and  Mach.) I chose  to  switch  to  an

Intel platform instead of continuing with one based on the PowerPC, because

the  latest  versions  of  Mac  OS  X  are no longer  being  ported  to  the  PowerPC

platform.  The drawback to this choice is that the processors covered are now slanted in favor of Intel. When discussing issues of heterogeneity, it is helpful to have processors with different characteristics, such as byte ordering and integer size.

4.  Solaris 10,  a  derivative  of  System  V  Release  4  from  Sun  Microsystems  (nowOracle), running on a 64-bit UltraSPARC IIi processor.

Changes from the Second Edition

One  of  the  biggest  changes  to  the  Single  UNIX  Specification  in  POSIX.1-2008  is the demotion of the STREAMS-related interfaces to obsolescent status. This is the first step

before these interfaces are removed entirely in a future version of the standard.  Because of this, I have reluctantly removed the STREAMS content from this edition of the book. This is an unfortunate change, because the STREAMS interfaces provided a nice contrast to the socket interfaces, and in many ways were more flexible.  Admittedly, I am not entirely unbiased when it comes to the STREAMS mechanism, but there is no debating the reduced role it is playing in current systems:

• Linux doesn’t include STREAMS in its base system, although packages (LiS and OpenSS7) are available to add this functionality.

• Although Solaris 10 includes STREAMS, Solaris 11 uses a socket implementation that is not built on top of STREAMS.

• Mac OS X doesn’t include support for STREAMS.

• FreeBSD doesn’t include support for STREAMS (and never did).

So with the removal of the STREAMS-related material, an opportunity exists to replace it with new topics, such as POSIX asynchronous I/O. In the second edition, the Linux version covered was based on the 2.4 version of the source.  In this edition, I have updated the version of Linux to 3.2. One of the largestarea of differences between these two versions is the threads subsystem. Between Linux 2.4 and Linux 2.6, the threads implementation was changed to the Native POSIX Thread Library (NPTL). NPTL makes threads on Linux behave more like threads on the other systems.

In total, this edition includes more than 70 new interfaces, including interfaces to handle asynchronous I/O, spin locks, barriers, and POSIX semaphores.  Most obsolete interfaces are removed, except for a few ubiquitous ones.

仔细看了一下目录,APUE 3rd 的API很丰富,但是也不是很全面,对于网络编程的接口和信号的接口,还有2.6以来的一些新接口的覆盖还是不全,推荐有时间结合:

The Linux Programming Interface

豆瓣地址:http://book.douban.com/subject/4292217/

下载地址:http://ishare.iask.sina.com.cn/f/14632131.html

一起看,或者作为APUE的替代都是可以的。

 

select/poll/epoll 对比

前两篇文章介绍了select,poll,epoll的基本用法,现在我们来看看它们的区别和适用场景。

首先还是来看常见的select和poll。对于网络编程来说,一般认为poll比select要高级一些,这主要源于以下几个原因:

  1. poll() 不要求开发者计算最大文件描述符加一的大小。
  2. poll() 在应付大数目的文件描述符的时候速度更快,因为对于select()来说内核需要检查大量描述符对应的fd_set 中的每一个比特位,比较费时。
  3. select 可以监控的文件描述符数目是固定的,相对来说也较少(1024或2048),如果需要监控数值比较大的文件描述符,就算所监控的描述符很少,如果分布的很稀疏也会效率很低,对于poll() 函数来说,就可以创建特定大小的数组来保存监控的描述符,而不受文件描述符值大小的影响,而且poll()可以监控的文件数目远大于select。
  4. 对于select来说,所监控的fd_set在select返回之后会发生变化,所以在下一次进入select()之前都需要重新初始化需要监控的fd_set,poll() 函数将监控的输入和输出事件分开,允许被监控的文件数组被复用而不需要重新初始化。
  5. select() 函数的超时参数在返回时也是未定义的,考虑到可移植性,每次在超时之后在下一次进入到select之前都需要重新设置超时参数。

  当然也不是说select就没有优点:

  1. select()的可移植性更好,在某些Unix系统上不支持poll()
  2. select() 对于超时值提供了更好的精度:微秒,而poll是毫秒。

epoll的优点:

1.支持一个进程打开大数目的socket描述符(FD)

  select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是1024/2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

2.IO效率不随FD数目增加而线性下降

  传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是”活跃”的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个”伪”AIO,因为这时候推动力在Linux内核。

3.使用mmap加速内核与用户空间的消息传递。

  这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap同一块内存实现的。 

  对于poll来说需要将用户传入的 pollfd 数组拷贝到内核空间,因为拷贝操作和数组长度相关,时间上这是一个O(n)操作,当事件发生,poll返回将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n)。

 

  这两天看到一个云风他们那里的bug就是因为使用的开源库中作者使用了非阻塞connect使用select() 来等待超时,但是并未检查FD_SETSIZE,当文件描述符数目大于这个数目之后就会出现内存越界错误,造成coredump。

 

I/O Mutiplexing poll 和 epoll

  上一篇介绍了select的基本用法,接着来学习一下poll和epoll的基本用法。首先来看poll:

#include <sys/poll.h>

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

  poll() 采用了struct pollfd 结构数组来保存关心的文件描述符,而不是像select一样使用三个fd_set ,pollfd结构体定义如下:

struct pollfd {

    int fd; /* file descriptor */

    short events; /* requested events to watch */

    short revents; /* returned events witnessed */

};

  每一个pollfd结构体指定了一个被监视的文件描述符,fds数组中可以存放多个pollfd结构,而且数量不会像select的FD_SETSIZE一样被限制在1024或者2048 。数组中每个pollfd结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,系统调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。我们可以设置如下事件:

  POLLIN:有数据可读。

  POLLRDNORM:有普通数据可读。

  POLLRDBAND:有优先数据可读。

  POLLPRI:有紧迫数据可读。

  ————————————————————

  POLLOUT:写数据不会导致阻塞。

  POLLWRNORM:写普通数据不会导致阻塞。

  POLLWRBAND:写优先数据不会导致阻塞。

  此外,revents域中还可能返回下列事件:

  POLLERR:指定的文件描述符发生错误。

  POLLHUP:指定的文件描述符挂起事件。

  POLLNVAL:指定的文件描述符非法。

  注意:只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。

  其中POLLIN | POLLPRI等价于select()的读事件,POLLOUT | POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM | POLLRDBAND,而POLLOUT则等价于POLLWRNORM。假如,要同时监视一个文件描述符是否可读和可写,我们可以设置events为POLLIN | POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

  timeout参数指定等待的毫秒数,无论I/O是否准备好,超时时间一到poll都会返回。timeout指定为负数值表示无限超时,UNPv1 中使用的INFTIM 宏貌似现在已经废弃,因此如果要设置无限等待,直接将timeout赋值为-1;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。

  成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1。

//pollEcho.cpp
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <stropts.h>
#include <netdb.h>

#define PORT 1314
#define MAX_LINE_LEN 1024

int main()
{
    struct sockaddr_in cli_addr, server_addr;
    socklen_t addr_len;
    int one,flags,nrcv,nwrite,nready;
    
    int listenfd,connfd;
    char buf[MAX_LINE_LEN],addr_str[INET_ADDRSTRLEN];
    
    std::vector<struct pollfd> pollfdArray;
    struct pollfd pfd;
    
    bzero(&server_addr, sizeof server_addr);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
    if( listenfd < 0)
    {
        printf("listen error: %s /n", strerror(errno));
        exit(1);
    }
       
    one = 1;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, &one, sizeof one);
    
    flags = fcntl(listenfd,F_GETFL,0);
    fcntl(listenfd, F_SETFL, flags | O_NONBLOCK);
    
    if(bind(listenfd,reinterpret_cast<struct sockaddr *>(&server_addr),sizeof(server_addr)) < 0)
    {
        printf("bind error: %s /n", strerror(errno));
        exit(1);
    }
    
    listen(listenfd, 100);
    
    pfd.fd = listenfd;
    pfd.events = POLLIN;
    
    pollfdArray.push_back(pfd);
    
    while(1)
    {
        nready = poll(&(*pollfdArray.begin()), pollfdArray.size(), -1);
        
        if( nready < 0)
        {
            printf("poll error: %s /n", strerror(errno));
        }
        
        if( pollfdArray[0].revents & POLLIN)
        {
            addr_len = sizeof cli_addr;
            connfd = accept(listenfd, reinterpret_cast<struct sockaddr *>(&cli_addr), &addr_len);
            
            if( connfd < 0)
            {
                if( errno != ECONNABORTED || errno != EWOULDBLOCK || errno != EINTR)
                {
                    printf("accept error: %s /n", strerror(errno));
                    continue;
                }
            }
            
            printf("recieve from : %s at port %d/n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port);
            
            flags = fcntl(connfd, F_GETFL, 0);
            fcntl(connfd,F_SETFL, flags | O_NONBLOCK);
            
            bzero(&pfd, sizeof pfd);
            
            pfd.fd = connfd;
            pfd.events = POLLIN;
            
            pollfdArray.push_back(pfd);
            
            if(--nready < 0)
            {
                continue;
            }
            
        }
        
        for( unsigned int i = 1; i < pollfdArray.size(); i++) // i from 1 not 0
        {
            pfd = pollfdArray[i];
            
            if(pfd.revents & (POLLIN | POLLERR))
            {
                memset(buf, 0, MAX_LINE_LEN);
                if( (nrcv = read(pfd.fd, buf, MAX_LINE_LEN)) < 0)
                {
                    if(errno != EWOULDBLOCK || errno != EAGAIN || errno != EINTR)
                    {
                        printf("read error: %s/n",strerror(errno));
                    }
                }
                else if( 0 == nrcv)
                {
                    close(pfd.fd);
                    pollfdArray.erase(pollfdArray.begin() + i);
                }
                else
                {
                    printf("nrcv: %s/n",buf);
                    nwrite = write(pfd.fd, buf, nrcv);
                    if( nwrite < 0)
                    {
                        if(errno != EAGAIN || errno != EWOULDBLOCK)
                            printf("write error: %s/n",strerror(errno));
                    }
                    printf("nwrite = %d/n",nwrite);
                }
                
            }
        }
    }
    return 0;
}

  以上代码操作的文件描述符都设置成为了非阻塞的状态,这也是为了更好的配合I/O multiplexing 的执行,试想如果read 或者 write 阻塞在某个描述符上,I/O multiplexing 就失去了真正的意义了,因为此时select/poll 函数就无法处理其它描述符产生的事件了。但是只要设置为非阻塞就够了吗? 这显然是还不够的,后面会专门写一篇文章对非阻塞的I/O multiplexing 进行完善。

———————————————————————————————————————————————————————–

  epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll的使用与select/poll不同,它是由一组系统调用组成:

#include<sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

  第一个函数 epoll_create() 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,其实size参数内核不会用到,只是开发者自己提醒自己的一个标记。epoll对监听的描述符数目没有限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大,在我的机器上这个值为:149197.

  第二个函数 epoll_ctl() 是epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:

  EPOLL_CTL_ADD:注册新的fd到epfd中;

  EPOLL_CTL_MOD: 修改已经注册的fd监听事件;

  EPOLL_CTL_DEL:  从epfd中删除一个fd;

  第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

  其中epoll_data_t 结构如下:

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

  注意这是一个union 结构。  

  events可以是以下几个宏的集合:
  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  EPOLLOUT:表示对应的文件描述符可以写;
  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  EPOLLERR:表示对应的文件描述符发生错误;
  EPOLLHUP:表示对应的文件描述符被挂断;
  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  这里介绍一下边沿触发和水平触发(epoll默认使用水平触发):

  LT 电平触发(高电平触发):
  EPOLLIN 事件
      内核中的某个socket接收缓冲区    为空    低电平
      内核中的某个socket接收缓冲区   不为空   高电平

  EPOLLOUT 事件
      内核中的某个socket发送缓冲区    不满    高电平
      内核中的某个socket发送缓冲区    满     低电平  

  ET 边沿触发:
      低电平 -> 高电平 触发
      高电平 -> 低电平 触发

  推荐使用默认的水平触发。

   第三个函数epoll_wait() 等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,返回的事件都保存在该events数组中,需要通过判断该数组中各个元素的状态来决定该如何处理,该maxevents告之内核这个events数组的大小。该函数返回需要处理的事件数目,如返回0表示已超时。

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
using namespace std;
#define PORT 1314
#define MAX_LINE_LEN 1024
#define EPOLL_EVENTS 1024

int main()   
{
    struct sockaddr_in cli_addr, server_addr;
    socklen_t addr_len;
    int one,flags,nrcv,nwrite,nready;
    
    int listenfd,epollfd,connfd;
    char buf[MAX_LINE_LEN],addr_str[INET_ADDRSTRLEN];
    
    struct epoll_event ev;
    std::vector<struct epoll_event> eventsArray(16);

    bzero(&server_addr, sizeof server_addr);
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
    if( listenfd < 0)
    {
        perror("socket open error! /n");
        exit(1);
    }
    
    
    one = 1;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, &one, sizeof one);
    
    flags = fcntl(listenfd,F_GETFL,0);
    fcntl(listenfd, F_SETFL, flags | O_NONBLOCK);
    
    if(bind(listenfd,reinterpret_cast<struct sockaddr *>(&server_addr),sizeof(server_addr)) < 0)
    {
        perror("Bind error! /n");
        exit(1);
    }
    
    listen(listenfd, 100);
    
    epollfd = epoll_create(EPOLL_EVENTS);
    
    if(epollfd < 0)
    {
        printf("epoll_create error: %s /n",strerror(errno));
        exit(1);
    }
    
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD,listenfd,&ev) < 0)
    {
        printf("register listenfd failed: %s",strerror(errno));
        exit(1);
    }
    
    while(1)
    {
        nready = epoll_wait(epollfd,&(*eventsArray.begin()),static_cast<int>(eventsArray.size()),-1);
        
        if(nready < 0)
        {
            printf("epoll_wait error: %s /n",strerror(errno));
        }
        
        for( int i = 0; i < nready; i++)
        {
            if(eventsArray[i].data.fd == listenfd)
            {
                addr_len = sizeof cli_addr;
                connfd = accept(listenfd, reinterpret_cast<struct sockaddr *>(&cli_addr),&addr_len);
                
                if( connfd < 0)
                {
                    if( errno != ECONNABORTED || errno != EWOULDBLOCK || errno != EINTR)
                    {
                        printf("accept socket aborted: %s /n",strerror(errno));
                        continue;
                    }
                }
                
                flags = fcntl(connfd, F_GETFL, 0);
                fcntl(connfd,F_SETFL, flags | O_NONBLOCK);
                
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                
                if(epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&ev) < 0)
                {
                    printf("epoll add error: %s",strerror(errno));
                }
                
                printf("recieve from : %s at port %d/n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port);
                
                if(--nready < 0)
                {
                    continue;
                }
        
            }
            else
            {
                ev = eventsArray[i];
                
                printf("fd = %d /n",ev.data.fd);
                
                memset(buf,0,MAX_LINE_LEN);
                
                if( (nrcv = read(ev.data.fd, buf, MAX_LINE_LEN)) < 0)
                {
                    if(errno != EWOULDBLOCK || errno != EAGAIN || errno != EINTR)
                    {
                        printf("read error: %s/n",strerror(errno));
                    }
                }
                else if( 0 == nrcv)
                {
                    close(ev.data.fd);
                    printf("close: %d fd /n",ev.data.fd);
                    eventsArray.erase(eventsArray.begin() + i);
                }
                else
                {
                    printf("nrcv, content: %s/n",buf);
                    nwrite = write(ev.data.fd, buf, nrcv);
                    if( nwrite < 0)
                    {
                        if(errno != EAGAIN || errno != EWOULDBLOCK)
                            printf("write error: %s/n",strerror(errno));
                    }
                    printf("nwrite = %d/n",nwrite);
                }
            }
        }
    }
    
    return 0;
}
  

  客户端的测试代码还是可以用前一篇文章提到的:nc localhost 1314 的方式来测试。

    I/O multiplexing 有三个方式可以完成,这三种方式的优劣和适用场合不同,后面会专门分析。

 

Socket 编程IO Multiplexing

   Linux Socket 编程中I/O Multiplexing 主要通过三个函数来实现:select, poll,epoll来实现。I/O Multiplexing,先构造一张有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已准备好可以进行I/O。本文具体介绍一下select 和poll的用法,给出简单的demo代码,简要分析一下这两个函数的使用易出错的地方。        

#include<sys/select.h>

int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds,fd_set *restrict exceptfds, struct timeval* restrict tvptr);

//Returns: count of ready descriptors, 0 on timeout, -1 on error

  中间三个参数readfds、writefds和exceptfds是指向描述符集的指针。这三个描述符集说明了我们关心的可读、可写或出于异常条件的各个描述符,设置为NULL则表示不关心。每个描述符集存放在一个fd_set数据类型中。这种数据类型为每一可能的描述符保持一位。描述符集的函数接口(可能实现为宏)包括:调用FD_ZERO将一个指定的fd_set变量的所有位设置为0;调用FD_SET设置一个fd_set变量的指定位;调用FD_CLR将一指定位清楚;调用FD_ISSET测试一指定位是否设置。

#include <sys/select.h>

int FD_ISSET(int fd, fd_set *fdset);

  //Returns: nonzero if fd is in set, 0 otherwise

void FD_CLR(int fd, fd_set *fdset);

void FD_SET(int fd, fd_set *fdset);

void FD_ZERO(fd_set *fdset);
  

 

  文件描述符集fdset中的文件描述符的个数是有限制的,最大值由FD_SETSIZE指定,一般为1024.

  Select 最后一个参数用于设置超时值,当select监听达到超时值时还未有关心的事件发生则返回,函数返回值为0.

struct timeval{

  long tv_sec;//second

  long tv_usec;//microsecond

}

 

  超时参数如果设置为 NULL 则无限等待。

  下面来是一个简单的select Echo server:

// simpleEcho.cpp
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <vector> #include <string.h> #include <stdlib.h> #include <fcntl.h> #define SEVER_PORT 1314 #define MAX_LINE_LEN 1024 using namespace std; int main() { struct sockaddr_in cli_addr, server_addr; socklen_t sock_len; vector<int> client(FD_SETSIZE,-1); fd_set rset,allset; int listenfd, connfd, sockfd, maxfd, nready, ix,maxid, nrcv,one; char addr_str[INET_ADDRSTRLEN],buf[MAX_LINE_LEN]; bzero(&server_addr,sizeof server_addr); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(SEVER_PORT); listenfd = socket(AF_INET,SOCK_STREAM,0); one = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&one, sizeof(one)); if(bind (listenfd ,(struct sockaddr *)&server_addr ,sizeof server_addr) < 0 ) { printf("socket bind error" ); return 0; } listen(listenfd ,10); FD_ZERO(&allset); FD_SET(listenfd ,&allset ); maxfd = listenfd ; maxid = -1 ; while(1 ) { rset = allset; //! nready = select (maxfd + 1, &rset,NULL,NULL,NULL); if(nready < 0 ) { printf("select error! /n" ); exit(1 ); } if(FD_ISSET (listenfd , &rset )) { sock_len = sizeof cli_addr; connfd = accept (listenfd ,(struct sockaddr *)&cli_addr , &sock_len); printf("recieve from : %s at port %d/n" , inet_ntop(AF_INET,&cli_addr .sin_addr ,addr_str ,INET_ADDRSTRLEN ),cli_addr .sin_port ); for(ix = 0 ; ix < static_cast< int>(client .size()); ix++) { if(client[ix] < 0 ) { client[ix] = connfd ; break; } } printf("client[%d] = %d/n" ,ix ,connfd ); if( FD_SETSIZE == ix) { printf("too many client! /n" ); exit(1 ); } if( connfd > maxfd) { maxfd = connfd; } FD_SET(connfd, &allset ); if(ix > maxid ) { maxid = ix; } if(--nready == 0) { continue; } } for(ix = 0 ; ix <= maxid; ix++) //<= { if((sockfd = client [ix ]) < 0) { continue; } if(FD_ISSET (sockfd ,&rset )) { if( 0 == (nrcv = read(sockfd,buf,MAX_LINE_LEN ))) { close(sockfd); client[ix] = -1 ; FD_CLR(sockfd ,&allset ); } else { printf("RECIEVE: %s /n" ,buf ); write(sockfd,buf,nrcv); } } if(--nready == 0) { break; } } } return 0; }

  在使用select 的时候要注意两点:

    第一个参数需要是当前所关心的文件描述符中最大的一个+1

    第二需要注意的是select的中间3个参数采用了“value-result”(UNP1的说法)的方式,设置了关心的文件描述符进行select,select返回之后对应描述的fdset中只有有事件发生的对应fd会被设置,其它关心但是没有事件发生的描述符将会从fdset中清除掉,如果不进行重新赋值,下次select就不会关注这些描述符了,因此上述代码中allset每次对rset进行复制。

   来看看如果只在while(1) 之前设置rset,在while(1) 中不在每次select之前赋值会发生什么,在控制台输入: strace ./simpleEcho,另外打开一个控制台窗口输入:nc localhost 1314,这作为一个连接Echo server 的 client,然后输入你想发往Echo Server内容。关键我们来看一下Echo server的情况:

Socket 编程IO Multiplexing

  可以看到 select 首先关注的文件描述符 fd == 3,该描述符是listenfd,然后有client连过来,select关注了 fd 3 和 4,4是accept函数打开的用于与client通信的描述符,当client向server写数据之后select关注的描述就只剩下 fd 4了,也就是当前处于连接状态的描述符,如果client主动关闭,select返回之后,下次监听就没有关注的描述符了,可见select函数的“value-result” 返回方式是这样工作的:每次只返回监听描述符中处于active的,其它处于监听的但是当前没有事件发生的描述符则会从监听的fdset中清除掉。因此在每次select之前需要给关注的fdset重新赋值。

  注1:在进行系统调用调试的时候 strace 是一个利器,简单使用方式如上面在运行程序之前加上 strace 即可。在调试代码逻辑的时候当然还是使用gdb了。

  注2Netcat 或者叫 nc 是 Linux 下的一个用于调试和检查网络工具包。可用于创建 TCP/IP 连接,最大的用途就是用来处理 TCP/UDP 套接字。

  

  select 什么时候会处于准备好并返回呢? UNPv1 上进行了详细介绍:

  下面四个条件任何一个满足的时候套件字准备好读:

  1. 套接口接受缓冲区的数据字节数大于等于套接口接受缓冲区的低潮限度当前值。对这样的套接口读操作将不阻塞并返回一个大于0的值(既准备好读入的数据量)。我们可以用套接口选项SO_RCVLOWAT来设置此低潮限度,对于TCP和UDP套接口,其缺省值为1。

  2. 连接的读这一半关闭(也就是接收了FIN的TCP连接)。对这样的套接口读操作将不阻塞并返回0(记文件结束符)。

  3. 套接口是一个监听的套接口且已完成的连接数为非0。正常情况下这样的套接口上的accpet不会被阻塞。

  4. 有一个套接口错误待处理。对这样的套接口操作将不阻塞并返回一个错误-1,errno设置成明确的错误条件。

 

  以下三个条件的任何一个满足时,套接口准备好写操作:

  1. 套接口发送缓冲区中可用空间的字节数大于等于套接口发送缓冲区低潮限度的当前值,且或者(i)套接口已连接,或者(ii)套接口不需要连接(例如UDP套接字)。这意味着,如果我们将这样的套接口设置为非阻塞,写操作将不阻塞且返回一个正值(例如由传输层传入的字节数)。我们可以用套接口选项SO_SNDLOWAT来设置此低潮限度,对于TCP和UDP套接口其缺省值为2048.

  2. 连接的写这一半关闭,对这样的套接口写操作将产生信号SIGPIPE。

  3. 有一个套接口错误待处理。对这样的套接口操作写操作将不阻塞且返回一个错误-1,errno设置成明确的错误条件。这些待处理的错误也可通过指定套接口选项SO_ERROR调用getsockopt来取得并清除。

  

  如果一个套接口存在带外数据或者仍处于带外标记,那他有异常条件待处理。

   poll留到下一篇吧……

  但,I/O multiplexing 就是这样用的吗?