Saturday, June 01, 2013

android dhcp

http://blog.csdn.net/new_abc/article/details/7898543

http://download.csdn.net/detail/new_abc/4520361这里有一个介绍dhcp的文档,比较详细
看一下dhcp的过程:
Android DHCP客户端的代码在system\core\libnetutils目录下
  1. int do_dhcp(char *iname)  
  2. {  
  3.     if (ifc_set_addr(iname, 0)) {  
  4.         printerr("failed to set ip addr for %s to 0.0.0.0: %s\n", iname, strerror(errno));  
  5.         return -1;  
  6.     }  
  7.   
  8.     if (ifc_up(iname)) {  
  9.         printerr("failed to bring up interface %s: %s\n", iname, strerror(errno));  
  10.         return -1;  
  11.     }  
  12.     sleep(2);  
  13.     return dhcp_init_ifc(iname);  
  14. }  

这里的iname是网口名,如“eth0",ifc_set_addr设置一个全局socket的地址为传下来的网口名字,ifc_up设置网口标志为启动状态。然后主要的工作在dhcp_init_ifc:
  1. int dhcp_init_ifc(const char *ifname)  
  2. {  
  3.     dhcp_msg discover_msg;  
  4.     dhcp_msg request_msg;  
  5.     dhcp_msg reply;  
  6.     dhcp_msg *msg;  
  7.     dhcp_info info;  
  8.     int s, r, size;  
  9.     int valid_reply;  
  10.     uint32_t xid,last,now;  
  11.     unsigned char hwaddr[6];  
  12.     struct pollfd pfd;  
  13.     unsigned int state;  
  14.     unsigned int timeout;  
  15.     int if_index;  
  16.       
  17.     xid = (uint32_t) get_msecs();  
  18.     last = (uint32_t) get_msecs();  
  19.       
  20.     if (ifc_get_hwaddr(ifname, hwaddr)) { //获取mac地址  
  21.         return fatal("cannot obtain interface address");  
  22.     }  
  23.       
  24.     if (ifc_get_ifindex(ifname, &if_index)) {  
  25.         return fatal("cannot obtain interface index");  
  26.     }  
  27.       
  28.     s = open_raw_socket(ifname, hwaddr, if_index);  
  29.   
  30.     timeout = TIMEOUT_INITIAL;  
  31.     state = STATE_SELECTING;  
  32.     info.type = 0;  
  33.     goto transmit;  
  34.   
  35.     for (;;) {  
  36.         pfd.fd = s;  
  37.         pfd.events = POLLIN;  
  38.         pfd.revents = 0;  
  39.     now = (uint32_t) get_msecs();  
  40.     if (now > (last + TIMEOUT_INITIAL))  
  41.         goto transmit;  
  42.     //  LOGD("lijj timeout = %d", timeout);  
  43.         r = poll(&pfd, 1, timeout);  
  44.         if (r == 0) {  
  45. #if VERBOSE  
  46.             printerr("TIMEOUT\n");  
  47. #endif  
  48.             if (timeout >= TIMEOUT_MAX) {  
  49.                 printerr("timed out\n");  
  50.                 if ( info.type == DHCPOFFER ) {  
  51.                     printerr("no acknowledgement from DHCP server\nconfiguring %s with offered parameters\n", ifname);  
  52.                     return ifc_configure(ifname, &info);  
  53.                 }  
  54.                 errno = ETIME;  
  55.                 close(s);  
  56.                 return -1;  
  57.             }  
  58.             timeout = timeout * 2;  
  59.   
  60.         transmit:  
  61.             size = 0;  
  62.             msg = NULL;  
  63.             switch(state) {  
  64.             case STATE_SELECTING:  
  65.                 msg = &discover_msg;  
  66.                 size = init_dhcp_discover_msg(msg, hwaddr, xid);//构建dhcp DISCOVER消息  
  67.                 break;  
  68.             case STATE_REQUESTING:  
  69.                 msg = &request_msg;  
  70.                 size = init_dhcp_request_msg(msg, hwaddr, xid, info.ipaddr, info.serveraddr);//构建请求消息  
  71.                 break;  
  72.             default:  
  73.                 r = 0;  
  74.             }  
  75.             if (size != 0) {  
  76.                 r = send_message(s, if_index, msg, size);//发送消息  
  77.                 if (r < 0) {  
  78.                     printerr("error sending dhcp msg: %s\n", strerror(errno));  
  79.                 }  
  80.             last = (uint32_t) get_msecs();  
  81.             }  
  82.             continue;  
  83.         }  
  84.   
  85.         if (r < 0) {  
  86.             if ((errno == EAGAIN) || (errno == EINTR)) {  
  87.                 continue;  
  88.             }  
  89.             return fatal("poll failed");  
  90.         }  
  91.   
  92.         errno = 0;  
  93.         r = receive_packet(s, &reply);  
  94.         if (r < 0) {  
  95.             if (errno != 0) {  
  96.                 LOGD("receive_packet failed (%d): %s", r, strerror(errno));  
  97.                 if (errno == ENETDOWN || errno == ENXIO) {  
  98.                     return -1;  
  99.                 }  
  100.             }  
  101.             continue;  
  102.         }  
  103.   
  104. #if VERBOSE > 1  
  105.         dump_dhcp_msg(&reply, r);  
  106. #endif  
  107.         decode_dhcp_msg(&reply, r, &info);//解析返回的dhcp消息  
  108.   
  109.         if (state == STATE_SELECTING) {  
  110.             valid_reply = is_valid_reply(&discover_msg, &reply, r);  
  111.         } else {  
  112.             valid_reply = is_valid_reply(&request_msg, &reply, r);  
  113.         }  
  114.         if (!valid_reply) {  
  115.             printerr("invalid reply\n");  
  116.             continue;  
  117.         }  
  118.   
  119.         if (verbose) dump_dhcp_info(&info);  
  120.   
  121.         switch(state) {  
  122.         case STATE_SELECTING:  
  123.             if (info.type == DHCPOFFER) {  
  124.                 state = STATE_REQUESTING;  
  125.                 timeout = TIMEOUT_INITIAL;  
  126.                 xid++;  
  127.                 goto transmit;  
  128.             }  
  129.             break;  
  130.         case STATE_REQUESTING:  
  131.             if (info.type == DHCPACK) {  
  132.                 printerr("configuring %s\n", ifname);  
  133.                 close(s);  
  134.                 return ifc_configure(ifname, &info);  
  135.             } else if (info.type == DHCPNAK) {//不能向客户提供这个网络地址或参数  
  136.                 printerr("configuration request denied\n");  
  137.                 close(s);  
  138.                 return -1;  
  139.             } else {  
  140.                 printerr("ignoring %s message in state %d\n",  
  141.                          dhcp_type_to_name(info.type), state);  
  142.             }  
  143.             break;  
  144.         }  
  145.     }  
  146.     close(s);  
  147.     return 0;  
  148. }  

这里大概过程是:
1、进行一些初始化后转到goto transmit;
2、此时state为STATE_SELECTING,所以初始化discover消息,接着调用send_message发送discover消息,然后回到for循环,阻塞在下一次poll,直到超时,如果中间一直未收到回应,则不停的发discover消息,收到回应消息则r>0;
3、调用receive_packet接收消息
4、decode_dhcp_msg解析消息,相应的信息保存在dhcp_info结构中
5、判断回应的消息是否为DHCPOFFER,是则将状态改为STATE_REQUESTING,跳转到transmit
6、发送REQUESTING消息,然后阻塞,等待回应
7、接收消息,decode_dhcp_msg解析消息,相应的信息保存在dhcp_info结构中
8、判断回应的消息是否为DHCPNAK,是则关闭套接字,调用ifc_configure设置相应地址信息,如果消息为DHCPNAK,则说明服务器不能向客户提供这个网络地址或参数,返回 error

基本的消息处理流程就是这样,再来看一下构建discover消息
  1. int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid)  
  2. {  
  3.     uint8_t *x;  
  4.   
  5.     x = init_dhcp_msg(msg, DHCPDISCOVER, hwaddr, xid);  
  6.   
  7.     *x++ = OPT_PARAMETER_LIST;//由n个option组成  
  8.     *x++ = 4;  
  9.     *x++ = OPT_SUBNET_MASK;  
  10.     *x++ = OPT_GATEWAY;  
  11.     *x++ = OPT_DNS;  
  12.     *x++ = OPT_BROADCAST_ADDR;  
  13.   
  14.     *x++ = OPT_END;  
  15.   
  16.     return DHCP_MSG_FIXED_SIZE + (x - msg->options);  
  17. }  

  1. static void *init_dhcp_msg(dhcp_msg *msg, int type, void *hwaddr, uint32_t xid)  
  2. {  
  3.     uint8_t *x;  
  4.   
  5.     memset(msg, 0, sizeof(dhcp_msg));  
  6.   
  7.     msg->op = OP_BOOTREQUEST;  
  8.     msg->htype = HTYPE_ETHER;  
  9.     msg->hlen = 6;  
  10.     msg->hops = 0;  
  11.   
  12.     msg->flags = 0; // htons(FLAGS_BROADCAST);  
  13.   
  14.     msg->xid = xid;  
  15.   
  16.     memcpy(msg->chaddr, hwaddr, 6);  
  17.   
  18.     x = msg->options;  
  19.   
  20.     *x++ = OPT_COOKIE1;//起标识作用,标识这是一个dhcp消息  
  21.     *x++ = OPT_COOKIE2;  
  22.     *x++ = OPT_COOKIE3;  
  23.     *x++ = OPT_COOKIE4;  
  24.   
  25.     *x++ = OPT_MESSAGE_TYPE; //DHCP协议交互的控制报文类型  
  26.     *x++ = 1;                   //DHCP Discover  
  27.     *x++ = type;  
  28.   
  29.     return x;  
  30. }  

这里比较简单,构建request消息也是一样的
我们可以在discover或者request消息中添加自己的option,让dhcp服务器收到后进行相应的解析
如在discover消息中:
  1. int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid)  
  2. {  
  3.     uint8_t *x;  
  4.     int length;  
  5.     x = init_dhcp_msg(msg, DHCPDISCOVER, hwaddr, xid);  
  6.   
  7.     *x++ = OPT_PARAMETER_LIST;//由n个option组成  
  8.     *x++ = 4;  
  9.     *x++ = OPT_SUBNET_MASK;  
  10.     *x++ = OPT_GATEWAY;  
  11.     *x++ = OPT_DNS;  
  12.     *x++ = OPT_BROADCAST_ADDR;  
  13. #ifdef TEST_DHCP  
  14.     /***************************** 
  15.         标识  长度  数据 
  16.     *****************************/  
  17.       
  18.     length = strlen("THIS is a Test");  
  19.     *x++ = 188;  
  20.     *x++ = length;  
  21.     memcpy(x,"THIS is a Test",length);  
  22.     x += length;  
  23. #endif  
  24.     *x++ = OPT_END;  
  25.   
  26.     return DHCP_MSG_FIXED_SIZE + (x - msg->options);  
  27. }  

我们加了一个Tag为188的选项,注意这里option的格式。然后重新编译一下dhcp,抓个包,我们可以看到
没有加时
可以看到多了一个option.


Thursday, January 24, 2013

android WIFI 架構和控制流程

本文轉自: http://neilchen0710.pixnet.net/blog/post/84945078-android-wifi-%E6%9E%B6%E6%A7%8B%E5%92%8C%E6%8E%A7%E5%88%B6%E6%B5%81%E7%A8%8B


一、WIFI的基本架構
    1wifi用戶空間的程式和庫:
         external/wpa_supplicant/
       
生成庫libwpaclient.so和守護進程wpa_supplicant
    2hardware/libhardware_legary/wifi/wifi管理庫。
    3JNI部分:
         frameworks/base/core/jni/android_net_wifi_Wifi.cpp
    4JAVA部分:
         frameworks/base/services/java/com/android/server/
         frameworks/base/wifi/java/android/net/wifi/
    5WIFI Settings應用程式位於:
       packages/apps/Settings/src/com/android/settings/wifi/
   6WIFI 驅動模組  wlan.ko
        wpa_supplicant通過wireless_ext 介面和驅動通信
   7WIFI 硬體模組 
二、WIFIAndroid中如何工作

   Android使用一個修改版wpa_supplicant作為daemon來控制WIFI,代碼位於
   external/wpa_supplicantwpa_supplicant是通過socket
   hardware/libhardware_legacy/wifi/wifi.c
通信。UI通過android.net.wifi package
   
frameworks/base/wifi/java/android/net/wifi/)發送命令給wifi.c
   
相應的JNI實現位於frameworks/base/core/jni/android_net_wifi_Wifi.cpp
   
更高一級的網路管理位於frameworks/base/core/java/android/net
  
三、配置Android支援WIFI

   BoardConfig.mk中添加:
      BOARD_HAVE_WIFI := true
      BOARD_WPA_SUPPLICANT_DRIVER := WEXT
 
   
這將在external/wpa_supplicant/Android.mk設置WPA_BUILD_SUPPLICANTtrue
   
默認使用驅動driver_wext.c
   
如果使用定制的wpa_supplicant驅動(例如 madwifi),可以設置:
       BOARD_WPA_SUPPLICANT_DRIVER := MADWIFI
  
四、使能wpa_supplicant調試信息

   默認wpa_supplicant設置為MSG_INFO,為了輸出更多資訊,可修改:
   1
、在common.c中設置wpa_debug_level = MSG_DEBUG;
   2
、在common.c中把#define wpa_printf宏中的
      if ((level) >= MSG_INFO)
      
改為
      if ((level) >= MSG_DEBUG)

五、配置wpa_supplicant.conf
 
   wpa_supplicant
是通過wpa_supplicant.conf中的ctrl_interface=來指定控制socket的,應該在
   AndroidBoard.mk中配置好複製到$(TARGET_OUT_ETC)/wifi(也就是
   /system/etc/wifi/wpa_supplicant.conf
   
這個位置會在init.rc中再次檢測的。
   
一般的wpa_supplicant.conf配置為:
      ctrl_interface=DIR=/data/system/wpa_supplicant GROUP=wifi
      update_config=1
      fast_reauth=1 
   
有時,驅動需要增加:
      ap_scan=1
   如果遇到AP連接問題,需要修改ap_scan=0來讓驅動連接,代替wpa_supplicant
 
   
如果要連接到non-WPA or open wireless networks,要增加:
      network={
              key_mgmt=NONE
      }

六、配置路徑和許可權

   Google修改的wpa_supplicant要運行在wifi用戶和組下的。代碼可見wpa_supplicant/os_unix.c
   中的os_program_init()函數。
 
   
如果配置不對,會出現下面錯誤:
      E/WifiHW  (  ): Unable to open connection to supplicant on
      "/data/system/wpa_supplicant/wlan0": No such file or directory will appear.

   
確認init.rc中有如下配置:
       mkdir /system/etc/wifi 0770 wifi wifi
       chmod 0770 /system/etc/wifi
       chmod 0660 /system/etc/wifi/wpa_supplicant.conf
       chown wifi wifi /system/etc/wifi/wpa_supplicant.conf
       # wpa_supplicant socket
       mkdir /data/system/wpa_supplicant 0771 wifi wifi
       chmod 0771 /data/system/wpa_supplicant
       #wpa_supplicant control socket for android wifi.c
       mkdir /data/misc/wifi 0770 wifi wifi
       mkdir /data/misc/wifi/sockets 0770 wifi wifi
       chmod 0770 /data/misc/wifi
       chmod 0660 /data/misc/wifi/wpa_supplicant.conf

   如果系統的/system目錄為唯讀,那應該使用路徑/data/misc/wifi/wpa_supplicant.conf
  
七、運行wpa_supplicantdhcpcd
 
   
init.rc中確保有如下語句:
      service wpa_supplicant /system/bin/logwrapper /system/bin/wpa_supplicant -dd
                   -Dwext -iwlan0 -c /data/misc/wifi/wpa_supplicant.conf
         user root
         group wifi inet
      socket wpa_wlan0 dgram 660 wifi wifi
         oneshot
      service dhcpcd /system/bin/logwrapper /system/bin/dhcpcd -d -B wlan0
         disabled
         oneshot

   根據所用的WIFI驅動名字,修改wlan0為自己驅動的名字。
   
七、編譯WIFI驅動為modulekernel built in

   1、編譯為module
      
BoardConfig.mk中添加:
         WIFI_DRIVER_MODULE_PATH := "/system/lib/modules/ar6000.ko"
         WIFI_DRIVER_MODULE_ARG := ""  #for example nohwcrypt
         WIFI_DRIVER_MODULE_NAME := "ar6000"  #for example wlan0
         WIFI_FIRMWARE_LOADER := ""        
 
   2
、編譯為kernel built in 
     1
)在hardware/libhardware_legacy/wifi/wifi.c要修改interface名字,
     2
)在init.rc中添加:
        setprop wifi.interface "wlan0"
     3
)在hardware/libhardware_legacy/wifi/wifi.c中當insmod/rmmod時,
        
直接return 0
 
八、WIFI需要的firmware

   Android不使用標準的hotplug binaryWIFI需要的firmware要複製到/etc/firmware
 
   
或者複製到WIFI驅動指定的位置,然後WIFI驅動會自動載入。

九、修改WIFI驅動適合Android

   Google修改的wpa_supplicant要求SIOCSIWPRIV ioctl發送命令到驅動,及接收資訊,例如signal
   strength, mac address of the AP, link speed等。所以要正確實現WIFI驅動,需要從
   SIOCSIWPRIV ioctl返回RSSI (signal strength)MACADDR資訊。

   如果沒實現這個ioctl,會出現如下錯誤:
     E/wpa_supplicant(  ): wpa_driver_priv_driver_cmd failed
                               wpa_driver_priv_driver_cmd RSSI len = 4096
     E/wpa_supplicant(  ): wpa_driver_priv_driver_cmd failed
     D/wpa_supplicant(  ): wpa_driver_priv_driver_cmd LINKSPEED len = 4096
     E/wpa_supplicant(  ): wpa_driver_priv_driver_cmd failed
     I/wpa_supplicant(  ): CTRL-EVENT-DRIVER-STATE HANGED
   
十、設置dhcpcd.conf
 
   
一般/system/etc/dhcpcd/dhcpcd.conf的配置為:
      interface wlan0
      option subnet_mask, routers, domain_name_server

Monday, January 14, 2013

EnhanceIO: New Solid State Drive Caching For Linux

http://www.phoronix.com/scan.php?page=news_item&px=MTI3Mzc

Posted by Michael Larabel on January 13, 2013

A commercial company has opened up their Linux driver that is based upon their SSD (Solid-State Drive) caching software product. This code is designed to use SSDs as cache devices for traditional rotating hard drives. This new SSD caching driver is based upon Facebook's Flashcache

Flashcache is the project Facebook open-sourced in 2011 that is a kernel-level solution for providing a block cache for Linux that supports multiple caching models. Flashcache can be used to accelerate reads and writes from slower rotational media by caching the data on a solid-state drive. 

Based upon Flashcache, STEC Inc has opened up their EnhanceIO SSD caching software for Linux. EnhanceIO uses SSDs as cache devices for traditional HDDs and works with any block device whether it be an actual physical device, a disk partition, a RAID-ed DAS device, SAN volumes, and device mapper volumes. 

EnhanceIO supports caching modes of read-only, write-through, and write-back. The cache replacement policies are random, FIFO, and LRU. 

STEC Inc didn't simply make a couple of changes to Flashcache and call it something new, but rather they have made some significant and original improvements. Over Flashcache, EnhanceIO adds a new write-back enhance that's designed from scratch, transparent cache support not using the Linux device mapper, large I/O support, a smaller memory footprint, supports loadable replacement policies, optimal alignment of data blocks on SSDs, improved device failure handling, and code optimizations. 

The EnhanceIO code was originally opened in late December while on Saturday the work was just announced on the Linux kernel mailing list. "STEC is happy to announce hosting of our EnhanceIO SSD caching software on github. We would like to invite kernel hackers to try it. We'll appreciate your valuable feedback to help us improve it to the standards of Linux kernel source code. We hope to eventually submit it for a possible inclusion in Linux kernel." 

The GPL-licensed code to EnhanceIO can be found on GitHub.


Saturday, September 08, 2012

從77年開始有趣的房價歷史新聞

http://www.mobile01.com/topicdetail.php?f=356&t=2792199&r=2&last=38314716

這是從聯合報歷史資料庫看到的房價新聞,從77、78年間開始到81年間,新聞資料很多,無法詳細列舉,僅列出摘要供大家參考。

整體來說,房價約在79年產生變化開始明顯下跌,但並沒有崩跌,80、81年以後,也是逐步緩跌,但偶而還是會有高房價出現來測試買氣拉抬房價,買賣雙方就是不斷的拉鋸。

有趣的是,在民國77年,國民所得不高的情況下,竟然1坪也賣到40萬元,記得95年左右,在本波房地產沒起漲前,臺北市地區有許多預售案1坪開價還不到40萬元。

房價其實也是商品,會漲也會跌,沒有只會漲的神話,至於在漲跌之間,就看個人的能力,在適當的時間進入買房就好。


(文章太長且為避免違規,修剪一下,請見諒)
*************************************

**當時投資客與建商也是台北房價炒完以後,南下炒房。

77年5月11日的聯合晚報1版報導:

台北客南下炒地皮 高雄及台中也都有屢創新高的建案推出

*************************************

77年6月13日經濟日報7版報導:

台北市建案競爭 每坪逼近四十萬元

*************************************

台北縣的房價也屢創新高。

77年9月15日經濟日報7版報導:

大台北地區的房市上演撐竿跳﹗利多消息一波波 永和地區的房價也屢攀高峰

*************************************

當時許多人也被逼的去外地買房。

77年9月17日經濟日報7版報導:

不論台北市區﹑中永和地區﹑內湖等地﹐房價均居高不下﹐中產階級購屋只有往南或更偏遠的地方了﹗

*************************************

78年4月7日經濟日報18版報導:

房價打不死!買氣嚇不退﹗大台北地區十餘預售個案調高售價﹐照樣好賣

*************************************

到處都創新高啊。

78年5月7日經濟日報12版報導:

台北市南區房價創新天價 一樓店面每坪128萬元 

*************************************

78年仍預估79年房價會繼續上漲。

78年12月1日經濟日報18版報導:

79年房價上漲成份仍居多

*************************************

79年1月12日經濟日報報導:

大台北地區房價一年漲一倍,近三年來,北投區漲幅達五倍,大安區平均每坪單價47萬

*************************************

79年後房價開始反轉。

79年5月2日聯合晚報5版報導:

房市湧現低於市價3成行情


*************************************

79年5月7日聯合報5版報導:

五年來單月交易量最低紀錄 大台北預售屋上月只賣出100間

原因是賣主仍迷戀高價位時代 ,拚命開高,「有行無市」

*************************************

79年8月2日經濟日報18版報導:

上半年大台北房價明顯下滑 預售屋每坪降至廿六萬元以下 

*************************************

79年11月22日聯合晚報5版報導:

房市平均跌幅16% 大台北房市慘!三峽淡水掉4成,但前年(指77年)進場投資客,長期抱著還是贏

*************************************


80年時,淡水已經在炒捷運及跨海大橋的議題了。

80年3月11日聯合晚報5版報導:

淡水房市 跌幅達三成,未來要看捷運及跨海大橋的興建計畫

*************************************

房價雖有下跌,但建商還是會不斷推出高價房來測試買氣的底線。

80年4月3日聯合晚報5版報導:

房價見風轉舵又漲了 部分業者嚐了甜頭抬高售價 高房價的‘不合理’情況再現

*************************************

80年8月8日聯合晚報5版報導:

淡水房子在前幾年極盛時期曾經飆到1坪30萬元 現在預售價格只有15萬元

*************************************

三不五十還是會有高房價出現。

80年12月31日聯合晚報5版報導:

台北房價又上漲 台北縣預售屋已找不到5百萬以下的房子 台北市也出現45萬一坪高價位預售案

*************************************

81年2月9日聯合報13版報導:

台北市信義計畫區推出每坪五十萬 一戶四千萬的高價屋 買主不知在哪? 

*************************************

81年5月12日經濟日報19版報導:

因政策面打壓及房屋供給量過大 預售屋滯銷 建商將過寒冬並退場觀望


*************************************

81年5月13日聯合報11版報導:

利空一波波 房市慘淡淡 銷售率探底

原因是政府抑制房價措施不斷 行情已呈疲軟 央行調高重貼現率後 再雪上加霜

Wednesday, September 05, 2012

一人一半,才是(伴)

一人一半,才是(伴) 文╱王偉忠

陶子重義氣,她要我做什麼,我就做什麼。她要我談一談婚姻感情,就談吧!反正我也到了開口說話會帶點道理,但年輕人聽不進去的年齡。

我跟太太戀愛八年結婚,婚後十七年,總共認識了二十五年。這麼長的時間日夜相伴,身旁偶爾沒有她,感覺很爽,倘若此後一輩子沒有她,萬萬不可。

像前一陣子看到一則意見調查,問年輕女孩,家人、父母、孩子、老公與事業,如果硬要抉擇,會先放棄哪個?

女孩選了選,先放棄了事業,然後家人、父母,剩下老公與孩子難以抉擇,最後選擇放棄孩子、留下老公,理由是家人、父母、孩子最終都會離開身邊,但老公會是終身伴侶。很殘酷、但也很真實。

中國字的寓意深遠,「伴」,就是一人一半,湊在一起才完整。

現今許多人適婚卻不婚,老人家看了奇怪,過去從沒發生過類似狀況。這現象代表五、六十年來沒有戰亂、年輕人長大過程中享有家庭溫暖與親情支援,因此認為單 身也能過得很好,不論敗犬或是單身貴族,不覺得非要個伴,是歷史上首度可以一個人過日子的平安歲月。衰老未至,沒有歲月的壓力,等年齡到了,開始覺得孤 獨、害怕孤獨,想找個人分享,還是需要個伴。

若要進入婚姻,套一句不負責任的老生常談,要靠緣份,除了緣份以及感性的感覺,還要理性的選擇。婚姻確實需要理性,如果女兒論及婚嫁,我一定要求要看看對方家庭,什麼樣的家庭會養出什麼樣的孩子,當然,也有破碎家庭的孩子更努力維繫自己的幸福家庭,但一定要仔細觀察。

而另一個老生常談就是婚姻需要兩個人有相同的價值觀,但這不是說「我喜歡的你一定要喜歡」,而是「我不討厭你喜歡的」,就可以了,兩人可妥協,可退讓,願意試著接觸自己本來討厭的,看看是否真那麼不能接受,有這樣願意妥協的心情,比較容易維繫感情。

而婚姻與愛情最大的不同,在於願不願意改變。願意為了對方改變自己,是真愛,從頭到尾都不想改變自己,這段感情充其量只是對方愛你。

戀愛是短暫的交會後很想在一起而開始,很想閃的結束。開始時乾柴烈火,講究原汁原味,眼前的他什麼都好,是全天下最酷的、最美的,不需要改,但等到愛情疲了,才發現這人酷到不近人情、美得過於臭美、連刺青的位置不對,趕緊推給個性不合閃人。因此戀愛講究的是如何好聚好散

進入婚姻,當然還是會有許多衝突,學習重點是「相處的藝術」。男生該學的第一課就是上廁所必須掀馬桶蓋,一開始改變很不習慣,後來融入身體,像吃飯喝水一樣自然。

但最近老婆說不只要掀蓋子,尿完還希望我拿衛生紙擦擦馬桶周圍留下來的「遺跡」,按照過去的脾氣,一定就「老子愛….」,但現在的我會聽太太的意見,因為「愛」字裡有個心,不是光用腦想著該送什麼禮物給她,要用心。

太太也改變不少,她不喜歡戶外活動,婚前知道我愛潛水、跟著背起十幾公斤重的空氣瓶跳進海底求生,這麼愛美的她怕晒黑、塗防晒油塗滿臉,一回頭、嚇一跳,怎麼來了個歌仔戲花旦跟我一起玩。

婚後我又想潛水,她就說「不必了」,理由是家裡有孩子,不宜從事太危險的活動。

以前一個人在台北發展,要自己照顧自己,還要爭名奪利,不自私很難在短時間之內成就自己。結婚之後學著喊太太的媽媽「媽!」,有食物,不能先放進自己嘴 巴,要顧著妻小先吃。我開始懂得心疼與珍惜,因為太太很好,捨不得讓她不舒服、捨不得讓她傷心,這些捨不得讓我自然學會了讓、學會了愛,也自然的改變自 己,從自私變成大方。

婚姻的路是每天類似的風景,同樣的過程、淡淡的,要相處得好,真得靠慧根,所以找對象不能光想找個腿長奶大的辣妹,或是像金城武的帥哥,要找個讓自己想起他來心裡甜甜的,回頭一看,那人就在燈火闌珊處,這就是最好的對象。

而所謂另一半,也不是一開始就契合,是在修正中不斷的磨合,你多一點我少一點,像拼圖一樣拼在一起,才能一起過一輩子。

當然,歲月無情,人生最終還是分離。像我媽媽十六歲嫁給爸爸,相守相愛了一輩子,爸爸十多年前過世之後,媽媽真像少了一半,常凝望遠方,像爸爸就在天的那 一邊,令人感傷。但人生如果少了婚姻、少了隨之而來的酸甜苦辣生老病死,少了孩子延續家的價值與感情,真會少掉很多滋味。